如何在Linux上使用exec选项避免通过find传递的命令

7

让我将我的问题分解成最简单的例子。

创建一个包含一行文本的测试文件。

[root@myserver ] /tmp> echo "test ReplaceMe DoNotReplaceMe" > /tmp/daj.txt

我们有一个现有的find命令,用于在所有匹配的文件中替换文本(在这个例子中,我简化了这个命令,只在一个文件上工作,并剥离了它所做的其他事情)。
问题是它会在出现"ReplaceMe"的任何地方进行替换,而不仅仅是它单独成词时才替换。
[root@myserver ] /tmp> find /tmp/daj.txt -exec sh -c 'f="{}"; sed -e 's/ReplaceMe/#DONE#/gi' "${f#.}" ' \;
test #DONE# DoNot#DONE#

我已经写了一个新的sed命令,只有在"ReplaceMe"是单独的单词时才进行替换,而不是在它是另一个单词的子字符串时进行替换。这个命令的输出是正确的。

[root@myserver ] /tmp> cat /tmp/daj.txt | sed -e 's/\(\W\)\(ReplaceMe\)\(\W\)/\1#DONE#\3/gi'    
test #DONE# DoNotReplaceMe

当我尝试将更新后的sed命令与find命令结合使用时,它就会出错。看起来我遇到了转义问题,但是我添加额外的转义并没有解决它。

[root@myserver ] /tmp> find /tmp/daj.txt -exec sh -c 'f="{}"; sed -e 's/\(\W\)\(ReplaceMe\)\(\W\)/\1#DONE#\3/gi' "${f#.}" ' \;
sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `f="/tmp/daj.txt"; sed -e s/(W)(ReplaceMe)(W)/1#DONE#3/gi "${f#.}" '

有没有办法在我的sed命令中使用转义字符,以便我可以通过find运行它,或者我必须寻找替代解决方案?
更新:我们正在运行的完整的find命令会输出文件名和权限,然后将sed的输出导入到md5sum。以下是它运行并匹配多个文件的示例:
[root@myserver ] ~> find /tmp -regex '.*daj.*\.txt' -printf '%p %m ' -exec sh -c 'f="{}"; sed  -e 's/ReplaceMe/#DONE#/gi' "${f#.}" | md5sum' \;
/tmp/daj2.txt 644 d52bbd311552234b761bcae694c2055a  -
/tmp/daj.txt 644 d52bbd311552234b761bcae694c2055a  -

2
不要在以root身份登录时试验shell脚本,这是很危险的。 - Jonathan Leffler
这是一个明智的建议,但是请注意,这只是一个个人开发服务器,很容易重新安装,而且我正在编辑一个以root身份运行的现有脚本。关于以root身份运行脚本的问题可以等待另一个Stackoverflow问题... :-) - Dan J
2个回答

3

这并没有回答你关于转义序列的问题,但是解决了这个问题。我基本上会像这样使用 xargssed

$ find ~/tmp/data.txt | xargs sed -e 's/\<replaceme\>/1234/'
1234 in this sentance
donotreplaceme in this sentance
$ 

并且data.txt文件的内容:

replaceme in this sentance
donotreplaceme in this sentance

另外,如果您的文件名中包含空格,则使用-print0参数会告诉查找器以null结尾的字符串形式输出文件列表。否则,查找器将把文件名中的空格解释为文件名的末尾。然后,在使用xargs时,您需要使用-0参数告诉xargs输入是以null结尾的字符串列表。下面是示例:

find /somedir -print0 | xargs -0 command

4
如果你要使用这种方式的 findxargs,我强烈建议你在 find 命令的末尾添加 -print0 并在 xargs 命令的开头添加 -0,这样它就知道以 NUL 作为分隔符。否则,你的代码将会在文件名中存在空格(和其他一些情况)时出现严重问题。 - SiegeX
@SiegeX:是的,这是真的。我已经编辑了答案以反映这一点。 - sashang
1
顺便尝试一下s/sentance/sentence/ - user562374

3

在shell中,不应该直接使用{},而应该将文件名作为shell参数传递。此外,如果您想要限制匹配整个单词,则可以在sed命令中使用\<word\>

更新

find /tmp -regex '.*daj.*\.txt' -printf '%p %m ' -exec sh -c "sed  -e 's/\<ReplaceMe\>/#DONE#/gi' \$@ | md5sum" _ {} \;

输出

$ find . -regex '.*daj.*\.txt' -printf '%p %m ' -exec sh -c "sed  -e 's/\<ReplaceMe\>/#DONE#/gi' \$@ | md5sum" _ {} \;
./daj2.txt 664 ea324b4721ed037dbc2402ded4446005  -
./daj.txt 664 0bbb9104da99c1c1187a2a35e6ac0e9b  -

谢谢。有没有办法更改此命令,以便逐个运行每个文件上的exec命令,而不是将所有文件传递给单个sed命令?请参阅原问题的更新以获取更多详细信息。 - Dan J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接