Xargs将字符串追加到文件中。

3

我有一个目录里面有多个文本文件。最后我想要添加一个字符串。

例如:目录中的文本文件列表。

  • a.txt
  • b.txt
  • c.txt

获取它们路径的命令:

find -name "*txt"

然后我尝试发送

'echo "example text" > <filename>

所以我尝试运行以下内容:

find -name "*txt" | xargs echo "example_text" >>

这条命令失败了。
我想偶尔追加一些文本到文件中,由于文件名经常变化,所以我想使用xargs。

1
xargs 是一个可执行文件。>> 是一个 shell 指令。Shell 指令在可执行文件运行之前被处理。 - Charles Duffy
此外,find | xargs 存在严重的潜在安全漏洞,除非您在 find 端使用 -print0 并在 xargs 端使用 -0:因为当没有使用 -0 或 GNU 扩展 -d 时,xargs 在不使用 shell 的输入解析时会出现问题,但是 find 不会为该解析编写转义输出,因此生成恶意文件名的人可以注入任意名称。 - Charles Duffy
如果您需要更多的参考资料,超出了这里给出的答案,请参阅使用Find,特别是“批量操作”和“复杂操作”部分。 - Charles Duffy
顺便说一句,使用find . -name而不是find -name更具可移植性(适用于非GNU版本的find);省略告诉find从哪里开始搜索的参数的能力是一个非标准扩展。 - Charles Duffy
5个回答

2
因为>>是一个shell指令,如果你想通过xargs使用它,你需要让xargs启动一个shell。正如Shawn的回答所示,在许多情况下,一个shell glob就足够了,你根本不需要使用find;但如果你确实想使用find,它可以正确地与或不与xargs一起使用。
如果你坚持使用xargs,即使它并不是最好的工具......
find . -name "*.txt" -print0 | xargs -0 sh -c '
  for arg in "$@"; do echo "example_text" >>"$arg"; done
' _

去掉xargs,只使用find(使用-exec ... {} +以获得与xargs相同的性能优势):

find . -name "*.txt" -exec sh -c '
  for arg in "$@"; do echo "example_text" >>"$arg"; done
' _ {} +

在上述两种情况中,_代替了$0,因此后面的参数变成了$1,以此类推,在扩展"$@"时进行迭代。


非常感谢,这真的很有帮助。只是出于好奇,我真的想通过xargs使它工作。 - Shine Cardozo
为什么不直接使用 tee -a ... 而不是 for... 循环呢? - F. Hauri - Give Up GitHub
@F.Hauri,...呵呵,将所有的输出文件传递给一个单独的tee实例?这是个好主意。 - Charles Duffy

2
在多个文件中追加字符串,使用tee -a命令!Unix命令tee专为这种操作而设计,并且速度更快!
find . -type f -name '*.txt' -exec tee -a <<<'Foo bar baz' {} >/dev/null +

但是,只有在执行tee一次时,herestring才能起作用!(感谢Charles Duffy's comment)!
查看进一步使用globstar

如果你真的想使用xargs

find . -type f -name '*.txt' -print0 |
    xargs -0 sh -c 'echo "Foo bar baz"|tee -a "$@" >/dev/null ' _

“但是真的需要使用find吗?”
“如果所有文件都在同一个目录下:”
tee -a <<<'Foo bar baz' >/dev/null *.txt

另外,在 [ŧag:bash] 下,使用 globstarshopt -s globstar):
tee -a <<<'Foo bar baz' >/dev/null **/*.txt

Here's the translated text:herestring 不是 -exec 的参数(实际上是 shell 在启动 find 前做的事情),所以只能读取一次。因此,第一个示例在文件名列表太长需要将其拆分为两个独立的 execve() 调用时会失败。第二个示例是安全的(对于任何数量的文件都能正确工作),最后一个示例将明确失败而不是悄悄地只附加到第一批文件,至少比第一个示例有所改进。 - Charles Duffy
@CharlesDuffy 正确的(我错过了这个)!!答案已编辑!无论如何,这项工作必须使用 tee -a 完成! - F. Hauri - Give Up GitHub

2

xargs在这里并不适合。也许可以像循环文件名一样处理。

for file in *.txt; do
    echo "example_text" >>"$file"
done

如果OP想要find的递归行为,他们可能需要使用shopt -s globstar,然后使用for file in **/*.txt - Charles Duffy
@CharlesDuffy 是的,我也考虑过,但他们的问题似乎只显示一个文件目录,而不是树形结构。 - Shawn
谢谢回复。 - Shine Cardozo

0

正如许多人指出的那样,xargs 不是一个合适的选择,因此您可以简单地使用 find 并通过管道传输到循环中的 read 来轻松实现您想要的功能,如下所示。

find . -name "*.txt" | while read fname; do echo "example_text">>$fname; done

这种方法不能正确处理所有可能的文件名。为了使其更加稳健,可以在find命令中添加“-print0”,并使用while IFS= read -r -d '' fname; do echo "example_text">>"$fname"; done命令。虽然对于某些较旧的bash版本来说,在引用"$fname"时可能没有必要,但有些版本还是会出现“bad redirection”错误,因为文件名会被分割或者匹配成多个单词。清除IFS可以修复名称以空格开头或结尾的问题,并使用“-r”参数来修复包含反斜杠的名称的支持。 - Charles Duffy
另一类无法正确处理find输出迭代的文件名是包含换行符的文件 -- 要创建这样的文件,请尝试运行touch $'hi\nworld.txt' - Charles Duffy

-1

bash 的角度来看,您的命令分为三个部分:

  • 管道字符之前 (find -name "*txt")
  • 管道和重定向之间 (xargs echo "example_text")
  • 重定向之后 ( )

bash 尝试打开重定向后提供的输出文件,但由于您没有提供任何内容,bash 无法打开“nothing”并失败。

要解决此问题,您需要给 xargs 提供一种方法将所需行添加到文件中(而不是要求 bash 重定向 xargs 的输出)。一个可行的方法可能是启动执行该操作的子 shell:

find -name "*txt" | xargs -I{} bash -c 'echo "example_text" >> {}'

"xargs -I{} bash -c '...{}...'" 是一个非常容易引发Shell注入漏洞的开放门。 - Charles Duffy
如果有人创建了一个名为 $(rm -rf ~).txt 的文件会发生什么?或者更糟的是,$(rm -rf ~)'$(rm -rf ~)'.txt,这样即使您尝试用单引号括起来{}以保护命令,它也会被扩展。 - Charles Duffy
因此,从xargs传递参数到sh的_secure_方式是通过在参数列表中作为源代码解析的内容之外的方式进行传递。 - Charles Duffy
完全同意。我只是模仿问题试图执行的行为。xargs一开始就不是正确的工具,而且基础代码存在几个问题。我只是假装解释代码为什么不能按预期工作。 - Poshi

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