대부분의 Debian/Ubuntu 시스템에 설치된 기본 도구를 사용하여 디렉터리에서 여러 파일을 재귀적으로 검색하고 바꾸는 방법은 무엇입니까?
Stack*에는 이 질문에 대한 답변을 찾을 수 있는 여러 답변이 있습니다.여기또는여기. 그러나 이 모든 것들은 본질적으로 부족합니다. 가능한 입력의 일부 "간단한" 하위 집합을 제외하고는 올바른 솔루션을 제공하지 않습니다.
, 및 에 대한 매뉴얼 페이지를 검색하고 자세히 살펴본 후 grep
, 이것은 제가 구축할 수 있었던 최고의 "검색 및 바꾸기" 명령입니다.xargs
sed
불다:
grep -ErlIZ -- '<OldPattern>' . | xargs -0rL1 sed -ri 's/<OldPattern>/<NewPattern>/g'
(참고로 저는 유용하고 고급 쉘 기능을 최대한 활용하고 싶기 때문에 크게 걱정하지 않습니다.하지만POSIX 또는 이식성과 관련하여 - 저는 또한 Mac에서 가장 오래된 GNU 도구 버전에 대해서는 별로 신경 쓰지 않습니다.)
이 한 줄에는 다음과 같은 몇 가지 특징이 있습니다.
- 안전을 위해 바이너리를 명시적으로 무시하십시오(그러나 이것이 실제로 필요한지는 확실하지 않습니다).
grep | xargs
후보 파일을 필터링하고 거대한 디렉토리에서 우수한 성능을 제공하는 데 사용됩니다 .- 대시( )로
-
시작하는 패턴을 허용합니다. - 공백이 있는 경로 허용
- 검색 패턴에서 정규식 캡처 그룹 허용
그러나 기능 세트의 단점으로 인해 sed
정규식 엔진은 항상탐욕스러운그리고 이 동작을 비활성화하는 옵션은 없습니다(추악한 해결 방법만 해당). 이는 적어도 어떤 경우에는 한 줄에 하나만 대체할 수 있음을 의미합니다(필요한 경우 몇 가지 예를 보여줄 수 있습니다).
while
필요한 만큼 여러 번 실행되도록 루핑을 사용합니다.진짜가능한 모든 대안을 다룹니다.
while FILES="$(grep -ErlI -- '<OldPattern>' .)"; do
echo "$FILES" | xargs -rL1 sed -ri 's/<OldPattern>/<NewPattern>/g'
done
그러나 지금Bash는 널 바이트를 저장할 수 없습니다이므로 옵션을 grep -Z
제거해야 합니다. 이로 인해 공백이 포함된 경로와의 호환성이 감소한다고 생각합니다.xargs -0
while
공백이 있는 경로를 지원하기 위해 루프 솔루션을-Z
, 옵션과-0
결합 할 수 있습니까 ?아니면... 다른 구축 방법이 있지만 더 나은 방법이 있을 수도 있습니다.강한그리고믿을 수 있는검색 및 바꾸기 명령을 사용하시겠습니까? (간결함이 특징이므로 최대한 한 줄에 가깝게 유지하세요)
편집하다sed
: 비루핑 버전에서 탐욕스러운 정규식이 문제가 되는 예를 추가합니다 .
다음 입력줄을 사용하세요.
set(requires "gstreamer-1.5 gstreamer-base-1.5 gstreamer-sdp-1.5 libjsonrpc")
패턴은 (gst.*)1\.5
다음과 일치합니다.
set(requires "[gstreamer-1.5 gstreamer-base-1.5 gstreamer-sdp-1.5] libjsonrpc")
욕심이 많기 때문에 처음부터 gst
끝까지 가져옵니다 1.5
. 교체가 다음과 같다고 가정하면 \1AAA
( \1
캡처 그룹)은 유지되고 AAA
원래 문자 대신 해당 문자만 인쇄됩니다 1.5
. 결과는 다음과 같습니다:
set(requires "gstreamer-1.5 gstreamer-base-1.5 gstreamer-sdp-AAA libjsonrpc")
따라서 해당 줄에서 가능한 모든 일치 항목을 실제로 바꾸려면 명령을 총 3번 실행해야 합니다. 루프 while
버전은 검색 패턴이 더 이상 발견되지 않을 때까지 모든 것을 반복해서 실행하며, 이 시점에서 교체 작업이 완료됩니다.실제로이미 마쳤어.
답변1
성공할 때마다 계속해서 교체를 실행하려면 다음을 사용 sed
하여 조건부 루프를 사용하면 됩니다 t
.
grep -ErlIZ -- '<OldPattern>' . |
xargs -r0 sed -Ei -e :1 -e 's/<OldPattern>/<NewPattern>/g' -e t1
sed
효율성을 위해 파일당 하나씩 실행하는 대신 최대한 많은 파일을 전달하며 , GNU 시스템 외부보다 사용하기가 더 간편하고 일관성이 sed
있습니다 .-E
-r
grep -E
bash
변수에 NUL을 저장할 수 있는 방법은 없지만 배열을 사용하여 파일 목록을 저장할 수 있습니다.
배쉬 4.4+의 경우:
readarray -td '' files < <(grep -ErlIZ -- '<OldPattern>' .)
그런 다음 다음을 출력할 수 있습니다.
((${#files[@])) && printf '%s\0' "${files[@]}" | xargs -r0 ...
아니면 임시 파일을 사용하세요. Linux에서는 다음을 수행할 수 있습니다.
exec 3<<EOF # creates a deleted empty temp file opened on fd 3
EOF
grep -ErlIZ -- '<OldPattern>' . > /dev/fd/3 || exit
# and later:
while xargs -r0a /dev/fd/3 ...; do...
exec 3<&- # file was already deleted, closing it means its data is now
# reclaimed.
아마도 (gst.*)1\.5
다음과 같아야 합니다: 예를 들어 (\<gst[^[:space:]]*)-1\.5\>
변수 부분에 공백 문자가 포함되지 않고 일치하지 않기를 원하는 경우.tagst-1.11.51
이 예에서 탐욕스럽지 않은 연산자를 사용하는 것은 아마도 별로 도움이 되지 않을 것입니다. Perl과 같은 것이 gst.*?1.5
여전히 gstreamer-1.3 foobar-1.5
일치합니다.set(requires "gstreamer-1.3 foobar-1.5 gstreamer-sdp-AAA libjsonrpc")