이 "sed" 검색 및 바꾸기 명령을 어떻게 개선할 수 있나요?

이 "sed" 검색 및 바꾸기 명령을 어떻게 개선할 수 있나요?

대부분의 Debian/Ubuntu 시스템에 설치된 기본 도구를 사용하여 디렉터리에서 여러 파일을 재귀적으로 검색하고 바꾸는 방법은 무엇입니까?

Stack*에는 이 질문에 대한 답변을 찾을 수 있는 여러 답변이 있습니다.여기또는여기. 그러나 이 모든 것들은 본질적으로 부족합니다. 가능한 입력의 일부 "간단한" 하위 집합을 제외하고는 올바른 솔루션을 제공하지 않습니다.

, 및 에 대한 매뉴얼 페이지를 검색하고 자세히 살펴본 후 grep, 이것은 제가 구축할 수 있었던 최고의 "검색 및 바꾸기" 명령입니다.xargssed불다:

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-rgrep -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")

관련 정보