替换多个文件中的多个字符串
Posted
技术标签:
【中文标题】替换多个文件中的多个字符串【英文标题】:Replacing multiple strings in multiple files 【发布时间】:2019-01-07 13:03:27 【问题描述】:我有一个文件,其中包含以下格式的正则表达式列表和替换文字字符串:
OLD_REGEXP_1 NEW_STRING_1
OLD_REGEXP_2 NEW_STRING_2
...
我想将多个文件*.txt
中与OLD_REGEXP_X
匹配的所有字符串替换为NEW_STRING_X
。
我相信这是一个常见的问题,之前应该有人做过类似的事情,但我只是找不到用 bash 编写的现有解决方案。
例如:
Tom Thompson
Billy Bill&Ted
goog1e\.com google.com
https?://www\.google\.com https://google.com
输入:
Tom and Billy are visiting http://www.goog1e.com
预期输出:
Thompson and Bill&Ted are visiting https://google.com
主要挑战是:
要替换的字符串由 POSIX 扩展正则表达式描述,而不是文字,并且任何不是 POSIX ERE 元字符的字符,包括经常被某些工具用作正则表达式分隔符的/
,必须被视为字面意思。
替换字符串是文字,可以包含任何文字字符,包括像 &
和 \1
这样的字符,它们经常在替换字符串中用作反向引用元字符,但在这种情况下必须是文字。
替换必须按照它们在映射文件中出现的顺序发生,因此如果我们在映射文件中按该顺序有 A->B 和 B->C,并且 A 出现在要更改的文本文件中,那么输出将包含“C”代替“A”,而不是“B”。
【问题讨论】:
新旧字符串是否也可能包含特殊字符,如*
、+
、[
、]
、(
、)
、&
等?
@anubhava,是的,旧字符串也可能包含?
,!
那么它们是正则表达式还是文字字符串?在后一种情况下,您需要反斜杠或以其他方式中和*
、[
等(但不是特别是!
,或者,取决于sed
方言,甚至必须是?
)
我认为我们总是可以将旧字符串视为正则表达式。但我不太确定/
在sed
或awk
中的工作原理,所以无论如何我都在逃避它们。
每一个存在的旧字符串都应该被新字符串替换,如果新字符串以后匹配到另一个旧字符串,也应该以同样的方式处理。
【参考方案1】:
您可以将替换列表文件转换为sed
脚本文件,然后让sed
为您完成这项工作。
用 gnu sed 试试这个:
sed -i -f <(sed -r 's/^(\S*) (.*)/s@\1@\2@/g' listfile) *.txt
【讨论】:
我使用的是 macOS,上面写着sed: illegal option -- r
更改为 -E
可能会有所帮助。
你能解释一下<(sed -r 's/^(\S*) (.*)/s@\1@\2@/g' listfile)
吗?我想知道它是如何工作的。
\S
也不能很好地移植,尽管它似乎可以在 Mac OS sed
中工作。
当旧字符串或新字符串包含@
s(尝试将电子邮件地址包含为旧字符串或新字符串)以及新字符串包含&
或\<digit>
时,将失败。它还依赖 bash 进行进程替换,所以你应该这么说。它还依赖于-r
的 GNU sed 和特定的 -i
语法(您确实提到过),但是您可以调整该语法以也与 OSX sed 一起使用,或者只是转义括号,这样您就不需要 ERE 并删除-i
和 -r/-E 然后它在任何 sed 中的行为都相同。【参考方案2】:
鉴于您到目前为止告诉我们的内容,并考虑到 cmets 中所说的所有内容以及问题中的内容以及我能想到的所有可能的字符串,这些字符串当前未包含在您的示例中,但可能发生(不包括字符串包含空格的 - 你必须告诉我们如何在 mapfile 中识别旧的和新的来处理它),听起来这就是你需要的:
$ cat mapfile
Tom Thompson
Billy Bill&Ted
goog1e\.com google.com
https?://www\.google\.com https://google.com
$ cat textfile
Tom and Billy are visiting http://www.goog1e.com
awk '
NR==FNR
old[NR] = $1
gsub(/&/,RS,$2)
new[NR] = $2
next
for (i=1; i in old; i++)
gsub(old[i],new[i])
gsub(RS,"\\&")
print
' mapfile textfile
Thompson and Bill&Ted are visiting https://google.com
上面将“旧字符串”视为正则表达式,将“新字符串”视为没有反向引用的文字字符串,并严格按照输入文件中定义的顺序应用替换。
第一个 gsub() 将替换字符串中的每个 &
转换为记录分隔符(因为我们在记录内操作,所以不能存在),因此第二个 gsub() 不会将 &
s 处理为新字符串就像一个反向引用,然后第三个 gsub() 只是将 RS 放回 &
s。
以上内容可以在任何 UNIX 系统上的任何 shell 中使用任何 awk。
【讨论】:
如果问题一开始很清楚,我会这样写。 ++以上是关于替换多个文件中的多个字符串的主要内容,如果未能解决你的问题,请参考以下文章