Amb*_*jak 13 bash sed awk string replace
我正在寻找一种方法来用具体的值替换模板文件中的占位符字符串,使用常见的 Unix 工具(bash、sed、awk,也许是 perl)。替换在单次通过中完成很重要,也就是说,已经扫描/替换的内容不得考虑用于另一次替换。例如,这两次尝试都失败了:
echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA
echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA
Run Code Online (Sandbox Code Playgroud)
在这种情况下,正确的结果当然是 BA。
一般来说,该解决方案应该等同于从左到右扫描输入以寻找与给定替换字符串之一的最长匹配,并且对于每个匹配,执行替换并从输入中的那个点继续(没有已经读取的输入也不应该考虑进行匹配的替换)。实际上,细节并不重要,只是替换的结果永远不会被完全或部分地考虑用于另一个替换。
注意我只是在寻找正确的通用解决方案。请不要提出对某些输入(输入文件、搜索和替换对)失败的解决方案,尽管它们看起来不太可能。
ric*_*ici 12
好的,一个通用的解决方案。以下 bash 函数需要2k参数;每对包含一个占位符和一个替换。您可以适当地引用字符串以将它们传递给函数。如果参数数量是奇数,则将添加一个隐式空参数,这将有效地删除最后一个占位符的出现。
占位符和替换都不能包含 NUL 字符,但是您可以使用标准的 C\转义符,例如\0如果您需要NULs (因此\\如果您需要 a ,则需要编写\)。
它需要标准的构建工具,这些工具应该存在于类似 posix 的系统(lex 和 cc)上。
replaceholder() {
local dir=$(mktemp -d)
( cd "$dir"
{ printf %s\\n "%option 8bit noyywrap nounput" "%%"
printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
} | lex && cc lex.yy.c
) && "$dir"/a.out
rm -fR "$dir"
}
Run Code Online (Sandbox Code Playgroud)
\如果需要,我们假设在参数中已经转义了,但我们需要转义双引号(如果存在)。这就是第二个 printf 的第二个参数所做的。由于lex默认操作是ECHO,我们无需担心。
示例运行(带有怀疑的时间;它只是一台便宜的商品笔记本电脑):
$ time echo AB | replaceholder A B B A
BA
real 0m0.128s
user 0m0.106s
sys 0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null
real 0m0.118s
user 0m0.117s
sys 0m0.043s
Run Code Online (Sandbox Code Playgroud)
对于较大的输入,为 提供优化标志可能很有用cc,并且对于当前 Posix 兼容性,最好使用c99. 一个更加雄心勃勃的实现可能会尝试缓存生成的可执行文件,而不是每次都生成它们,但它们的生成成本并不高。
编辑
如果您有tcc,您可以避免创建临时目录的麻烦,并享受更快的编译时间,这将有助于正常大小的输入:
treplaceholder () {
tcc -run <(
{
printf %s\\n "%option 8bit noyywrap nounput" "%%"
printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
} | lex -t)
}
$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null
real 0m0.039s
user 0m0.041s
sys 0m0.031s
Run Code Online (Sandbox Code Playgroud)