전역이 선택되지 않은 경우 Perl 정규식 전역 대체

전역이 선택되지 않은 경우 Perl 정규식 전역 대체

저는 Ubuntu 11.04를 사용하고 있으며 텍스트 파일에서 특정 "태그"를 검색하고 이를 동일한 이름의 템플릿 파일에서 미리 작성된 일부 조각으로 바꾸는 작은 스크립트를 작성했습니다.

검색되는 텍스트 파일에는 각 태그의 인스턴스가 2개만 있습니다. 첫 번째는 일반 텍스트이고 두 번째는 각 버전에 대해 별도의 스니펫이 포함된 html 버전입니다.

스크립트는 다음과 같습니다.

for f in `ls -1 .templates/template_text`;
do
    g=`cat .templates/template_text/$f`
    find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/";
done

for f in `ls -1 .templates/template_html`;
do
    g=`cat .templates/template_html/$f`
    find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/g";
done

첫 번째 정규식에서 "전역"을 지정하지 않았음에도 불구하고 여전히 두 태그를 모두 대체하는 문제가 발생했습니다. 이것이 내가 Perl을 호출하는 방식 때문인지, 버그인지, 아니면 다른 것 때문인지는 확실하지 않습니다.

어떤 도움이라도 대단히 감사하겠습니다.

업데이트: Perl 대신 sed를 사용하여 스크립트가 작동하도록 할 수 있었습니다.

for f in `ls -1 .templates/template_text`;
do
    g=`cat .templates/template_text/$f`
    h=`cat .templates/template_html/$f`
    find to_process/ -type f -print0 | xargs -0 -I {} sed -i -e "0,/$f/s/$f/$g/" -e "0,/$f/s/$f/$h/" {}
done

그러나 Perl 명령을 사용하여 작동시키는 방법에는 여전히 관심이 있습니다.

답변1

이는 Perl이 텍스트 파일을 한 번에 한 줄씩 읽고 각 줄에 교체 패턴을 적용하기 때문입니다. 따라서 태그가 다른 줄에 여러 번 나타나면 모두 교체됩니다.

파일의 첫 번째 항목만 바꾸려면 -0입력 레코드 구분 기호를 널 문자로 설정하고 대체를 수행하기 전에 perl이 전체 ​​파일을 읽도록 하는 옵션을 추가할 수 있습니다.

답변2

s/$f/$g/$f각 줄에서 첫 번째로 나타나는 by를 바꿉니다 . 전체 파일에서 첫 번째 항목 $g만 바꾸려면 이렇게 말해야 합니다. $f이것이 당신이 하는 일입니다 sed( 첫 번째 발생을 포함하여 최대로 대체 0,/$f/ s/$f/$g/) . Perl에서는 좀 더 장황하지만 이해하기 쉬운 방식으로 작성할 수 있습니다(참고: 아래 인용 문제 참조).$f$g$f

perl -i -pe 'if ($n==0) {s/$f/$g/; $n=1;} elsif ($n==1) {s/$f/$h/; $n=2}'

코드에는 여러 가지 인용 문제가 있습니다. 파일 이름에 공백, 와일드카드 또는 인쇄할 수 없는 문자(예: 현재 로케일에 존재하지 않는 바이트 시퀀스)가 포함되어 있으면 문제가 발생합니다. 다행히도 이러한 문제는 해결하기 쉽습니다.

첫째, 몇 가지 일반적인 쉘 질문입니다."$foo"변수 대체 및 명령 대체 에는 항상 큰따옴표를 사용하십시오."$(foo)"왜 인용하지 않은 채로 두어야 하는지 알지 않는 한. 묶지 않으면 결과는 공백이 포함된 별도의 단어로 분할되며 각 단어는 glob 패턴으로 처리됩니다. 따라서 변수에 공백으로 구분된 glob 패턴 목록이 포함되지 않는 한 이를 큰따옴표로 묶습니다. 또한 $(…)대신에 `…`내부적으로 중첩된 따옴표를 사용하는 것이 좋습니다 . 이는 동일하지만 `…`신뢰할 수 없으며 `혼동하기 쉽습니다 .'

구문 분석되지 않은 출력 ls. 디렉터리의 모든 파일에 대해 작업을 수행해야 하는 경우 셸에는 사용할 수 있는 내장 구성인 globbing이 있습니다. 대신 $(ls /path/to/directory), /path/to/directory/*이렇게 하면 디렉터리 경로가 포함된 파일 이름이 생성됩니다. 이는 거의 항상 필요한 것이며, 그렇지 않은 경우 cd미리 호출하거나 디렉터리 전체 또는 일부를 제거할 수 있습니다. 아래에서는 을 사용하는데 ${f#*/*/}, 이는 $f가장 짧은 접두사 일치를 제거하는 것을 의미합니다.*/*/

for f in .templates/template_text/*; do
  g=$(cat "$f")
  h=$(cat ".templates/template_html/${f#*/*/}")
  find to_process/ -type f …
done

를 이용하면 find보다 간단한 구성을 사용할 수도 있지만 작품 과 결합할 -exec수도 있습니다 . 를 생성하지 않는 특별한 방식으로 입력이 참조될 것으로 예상하므로 없이를 사용하지 마십시오 .-print0xargs -0xargs-0find

find to_process/ -type f -exec perl … {} +

다음 문제는 sed 또는 perl 정규식에 문자열을 직접 삽입 하려는 것입니다 $f. 이것은 잘못된 것입니다. 이 변수에는 따옴표 로 묶인 구분 기호가 있는 정규식이 포함되어 있지 않습니다 ( 두 경우 모두). sed를 사용하면 문자열을 한 번 인용하고 in 앞과 in 및 앞에 백슬래시를 추가 해야 합니다 . Perl을 사용하면 더 쉬운 방법이 있습니다. 환경을 통해 값을 전달하고 Perl에게 정규식이 아닌 문자열이 있음을 알려주는 것입니다.$g$h//*.\[$f\&/$g$h

export f g h
find to_process/ -type f -exec perl -i -e '
    if ($n==0) {s/\Q$ENV{f}/$ENV{g}/; $n=1;}
    elsif ($n==1) {s/\Q$ENV{f}/$ENV{h}/; $n=2}}
' {} +

관련 정보