저는 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
수도 있습니다 . 를 생성하지 않는 특별한 방식으로 입력이 참조될 것으로 예상하므로 없이를 사용하지 마십시오 .-print0
xargs -0
xargs
-0
find
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}}
' {} +