머리말: 이런 문제는 며칠에 한 번씩 나옵니다. 해결하기는 쉽지만 sed
설명하는 데는 시간이 걸립니다. 나중에 이 일반적인 해결책을 참조하고 특정 상황에 대한 적응만 설명할 수 있도록 이 질문과 답변을 작성하고 있습니다. 자유롭게 기여해 주세요.
변수 정의가 포함된 파일이 있습니다. 변수는 대문자나 밑줄로 구성되며 _
해당 값은 에 위치합니다 :=
. 이러한 값에는 다른 변수가 포함될 수 있습니다. 이것은 Gnom.def
:
NAME:=Gnom
FULL_NAME:=$FIRST_NAME $NAME
FIRST_NAME:=Sman
STREET:=Mainstreet 42
TOWN:=Nowhere
BIRTHDAY:=May 1st, 1999
form.txt
그런 다음 템플릿 형식이 포함된 또 다른 파일이 있습니다 .
$NAME
Full name: $FULL_NAME
Address: $STREET in $TOWN
Birthday: $BIRTHDAY
Don't be confused by $NAMES
이제 양식( $
및 식별자로 표시됨)의 변수를 다른 파일의 정의로 재귀적으로 대체하여 다음 텍스트를 얻을 수 있는 스크립트가 필요합니다.
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES
마지막 줄은 변수의 하위 문자열이 실수로 바뀌지 않도록 하는 것입니다.
답변1
이런 종류의 문제를 해결하기 위한 기본 아이디어는 두 개의 파일을 sed
.예비 공간의 sed
. 그런 다음 다른 파일의 각 행에 예약된 공간이 추가되고 추가된 정의에서 반복되는 모든 변수가 대체됩니다.
스크립트는 다음과 같습니다.
sed '/^[A-Z_]*:=.*/{H;d;}
G
:b
s/$\([A-Z_]*\)\([^A-Z_].*\n\1:=\)\([^[:cntrl:]]*\)/\3\2\3/
tb
P
d' Gnom.def form.txt
이제 자세히 설명합니다.
/^[A-Z_]*:=.*/{H;d;}
이는 정의를 예약된 공간으로 수집합니다. /^[A-Z_]*:=.*/
변수 이름과 시퀀스로 시작하는 모든 행을 선택합니다 :=
. 이 줄에서 {}
다음 명령을 실행합니다. H
예약된 공간에 추가하고 d
삭제한 후 다시 시작하면 인쇄되지 않습니다.
정의 파일의 모든 행이 이 패턴을 따르는지 확인할 수 없거나 다른 파일의 행이 지정된 패턴과 일치할 수 있는 경우 나중에 설명하는 대로 이 섹션을 조정해야 합니다.
G
이 시점에서 스크립트는 두 번째 파일의 행만 처리합니다. 예약된 G
공간은 패턴 공간에 추가되므로 패턴 공간의 모든 정의를 줄바꿈으로 구분하여 처리해야 합니다.
:b
그러면 사이클이 시작됩니다.
s/$\([A-Z_]*\)\([^A-Z_].*\n\1:=\)\([^[:cntrl:]]*\)/\3\2\3/
핵심부품 교체입니다. 이제 우리는 다음과 같은 것을 가지고 있습니다
At the $FOO<newline><newline>FOO:=bar<newline>BAR:=baz
----==================--- ###
패턴 공간에서. (세부사항: 첫 번째 정의 앞에는 두 개의 개행 문자가 옵니다. 하나는 예약된 공간에 추가하여 생성되고 다른 하나는 버퍼 공간에 추가하여 생성됩니다.)
----
밑줄 친 부분을 표시하려면 일치 항목을 사용하세요 $\([A-Z_]*\)
. 이렇게 하면 \(\)
나중에 문자열을 다시 참조할 수 있습니다.
\([^A-Z_].*\n\)
===
역참조 이전의 모든 항목인 밑줄 친 부분과 일치합니다 \1
. n개의 변수 문자로 시작하면 변수의 하위 문자열이 일치하지 않습니다. 역참조를 개행 문자로 묶고 :=
정의된 하위 문자열이 일치하지 않는지 확인하세요.
마지막으로 \([^[:cntrl:]]*\)
일치하는 ###
부분인 정의입니다. 정의에 제어 문자가 없다고 가정합니다. 가능하다면 [^\n]
GNU를 사용 sed
하거나 POSIX에 대한 해결 방법을 만들 수 있습니다 sed
.
이제 $
변수 이름과 변수 이름은 변수 값으로 바뀌고 \3
중간 부분과 정의는 그대로 유지됩니다 \2\3
.
tb
대체가 이미 이루어진 경우 t
명령은 마커를 반복하여 b
다른 대체를 시도합니다.
P
더 이상 대체가 가능하지 않으면 대문자는 P
첫 번째 개행까지 모든 것을 인쇄합니다(따라서 정의 부분은 인쇄되지 않습니다).
d
패턴 공간이 삭제되고 다음 루프가 시작됩니다. 완벽한.
한정
FOO:=$BAR
정의 파일에 포함 하고BAR:=$FOO
스크립트를 영원히 반복하는 것과 같은 불쾌한 작업을 수행할 수 있습니다 . 이를 방지하기 위해 처리 순서를 정의할 수 있지만 이로 인해 스크립트를 이해하기가 더 어려워집니다. 스크립트에 바보 증명이 필요하지 않으면 그대로 두십시오.정의에 제어 문자가 포함될 수 있는 경우
G
개행 문자를 다른 문자로 바꾸고y/\n#/#\n
인쇄하기 전에 이를 반복할 수 있습니다. 더 나은 해결책을 모르겠습니다.정의 파일에 다른 형식의 줄이 포함될 수 있거나 다른 파일에 정의된 형식의 줄이 포함될 수 있는 경우 정의 파일의 마지막 줄 또는 다른 줄로 두 파일 사이에 고유한 구분 기호를 사용해야 합니다. 파일 또는
sed
다른 파일 간에 전달되는 별도의 파일로 사용됩니다. 그런 다음 구분선이 충족될 때까지 정의를 수집한 다음 다른 파일의 줄을 반복하는 루프가 있습니다.
답변2
sed 스크립트와 비교하기 위해 POSIX awk 스크립트는 다음과 같습니다.
$ cat tst.awk
BEGIN { FS=":=" }
NR==FNR {
map["$"$1] = $2
next
}
{
mappedWord = 1
while ( mappedWord ) {
mappedWord = 0
head = ""
tail = $0
while ( match(tail,/[$][[:alnum:]_]+/) ) {
word = substr(tail,RSTART,RLENGTH)
if ( word in map ) {
word = map[word]
mappedWord = 1
}
head = head substr(tail,1,RSTART-1) word
tail = substr(tail,RSTART+RLENGTH)
}
$0 = head tail
}
print
}
$ awk -f tst.awk Gnom.def form.txt
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES
스크립트는 사용하는 문자나 문자열에 대해 신경 쓰지 않습니다( :=
두 번째 파일에 존재하지 않고 존재하지 않는 제어 문자에 분명히 의존하는 sed 버전과 달리). match()
, 현재 [$][[:alnum:]_]+
.
재귀적 정의가 주어지면 위의 접근 방식은 실패하지만 원하는 경우 다음과 같은 간단한 조정을 통해 이를 감지하고 보고하고 현명하게 처리할 수 있습니다.
$ head Gnom.def form.txt
==> Gnom.def <==
FOO:=$BAR
BAR:=$FOO
NAME:=Gnom
FULL_NAME:=$FIRST_NAME $NAME
FIRST_NAME:=Sman
STREET:=Mainstreet 42
TOWN:=Nowhere
BIRTHDAY:=May 1st, 1999
==> form.txt <==
$NAME
Full name: $FULL_NAME
Address: $STREET in $TOWN
Birthday: $BIRTHDAY
Don't be confused by $NAMES
testing recursive $FOO
testing recursive $BAR
$ cat tst.awk
BEGIN { FS=":=" }
NR==FNR {
map["$"$1] = $2
next
}
{
mappedWord = 1
iter = 0
delete mapped
while ( mappedWord ) {
if ( ++iter == 100 ) {
printf "%s[%d]: Warning: Breaking out of recursive definitions.\n", FILENAME, FNR | "cat>&2"
break
}
mappedWord = 0
head = ""
tail = $0
while ( match(tail,/[$][[:alnum:]_]+/) ) {
word = substr(tail,RSTART,RLENGTH)
if ( word in map ) {
word = map[word]
mappedWord = 1
}
head = head substr(tail,1,RSTART-1) word
tail = substr(tail,RSTART+RLENGTH)
}
$0 = head tail
for (word in mapped) {
mapped[word]++
}
}
print
}
$ awk -f tst.awk Gnom.def form.txt
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES
testing recursive $BAR
testing recursive $FOO
form.txt[6]: Warning: Breaking out of recursive definitions.
form.txt[7]: Warning: Breaking out of recursive definitions.
위의 경고는 stdout이 아닌 stderr로 인쇄되므로 출력이 엉망이 되지 않습니다.
$ awk -f tst.awk Gnom.def form.txt 2>err
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES
testing recursive $BAR
testing recursive $FOO
$ cat err
form.txt[6]: Warning: Breaking out of recursive definitions.
form.txt[7]: Warning: Breaking out of recursive definitions.