前言:每隔几天就会出现一个这样的问题,用 很容易解决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]
GNUsed
或为 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 版本不同,它显然依赖于不存在控制字符且未:=
出现在第二个文件中),只需包含可以组成要在正则表达式 arg 为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.
请注意,上述警告将打印到 stderr,而不是 stdout,因此它们不会扰乱您的输出:
$ 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.