为什么在处理带空格的文件名时不需要将“\”{}\“”传递给 xargs?

Why don't I need to pass "\"{}\"" to xargs when handling filenames with spaces?

我有以下问题:

我想使用 xargs-find 结构来查找文件。 区别在于,我不想将 find 用作 command1,而是用作 command2 in:

command1 | xargs command2

如果文件名中有空格就会出现问题

例如:

如果我正在尝试:

echo 01.Here Comes The Night Time II.flac | xargs -pi find ~/Multimedia/Musik/flac/ -name "\""{}"\""

find ~/Multimedia/Musik/flac/ -name "01.Here Comes The Night Time II.flac" ?...yes

什么都找不到。 同样使用 xargs 的 -0 选项也不起作用。

如果我从 xargs 复制并粘贴交互式打印请求,将找到该文件:

find ~/Multimedia/Musik/flac/ -name "01.Here Comes The Night Time II.flac"

~/Multimedia/Musik/flac/Arcade Fire/Reflektor (CD 2)/01.Here Comes The Night Time II.flac

我 "feed" 管道的方式有问题,或者我在 find 命令中包含 " 的方式有问题(我通过反复试验发现),还是其他什么?

您似乎对程序在内部启动的方式以及 shell 如何解释命令感到困惑。

在unix中,启动一个程序涉及三个参数:

  1. 一个文件名。这是一个包含程序路径的字符串 运行.
  2. 字符串列表。按照惯例,我们称这些为 "command line arguments".
  3. 另一个字符串列表。按照惯例,我们称这些为 "the environment",但在 OS 级别,它只是另一个字符串列表。然而,所有 programs/libraries 共同对用户和应用程序程序员隐藏此信息。

当您在 shell 中键入命令时,会发生很多事情,但在最简单的情况下,它只是一堆 space 分隔的单词:

$ foo bar baz

$ 表示 shell 提示符,而不是您键入的内容。)

shell将这一行拆分为三个词(foobarbaz)并将第一个解释为程序名(将在 PATH 变量中列出的目录中查找)。让我们假设 PATH 列出 /usr/bin 并且确实有一个 /usr/bin/foo 程序。

现在 shell 启动程序如下(伪代码):

exec("/usr/bin/foo", ["foo", "bar", "baz"], [...])

即我们 运行 /usr/bin/foo 中的可执行文件,将三个字符串的列表作为参数传递。 ([...] 代表环境,从现在开始我们将忽略它。)

如果改为这样做会怎样?

$ foo "bar baz"

引号会影响 shell 将行拆分为单词的方式。特别是,引号中的 " " (a space) 不作为分隔符,而是按字面意思使用。这给了我们一个二元素列表 (foo, bar baz)。请注意,引号不是单词本身的一部分。

这在内部转换为以下调用:

exec("/usr/bin/foo", ["foo", "bar baz"], [...])

同样,第二个参数只包含一个 space。没有嵌入引号。

那么像

这样的命令会发生什么
$ xargs -pi find ~/Multimedia/Musik/flac/ -name "\""{}"\""

?

这将再次被 shell 解析为单词列表。 ~ 替换为您的主目录的名称。 "\"" 只是 \"'"' 的一种复杂的书写方式(即文字 " 字符)。我们最终得到的列表是 xargs-pifind/home/madZeo/Multimedia/Musik/flac/-name"{}"。这转换为以下调用:

exec("/usr/bin/xargs", ["xargs", "-pi", "find", "/home/madZeo/Multimedia/Musik/flac/", "-name", "\"{}\""], [...])

注意最后一个参数是 4 个字符的字符串 "{}"

xargs 将其第一个参数 (-pi) 视为选项规范。特别是,-i 告诉它用从标准输入读取的当前值替换参数列表中的 {}

xargs 然后从它的标准输入中读取一行,这(因为你的 echo 管道)给出 01.Here Comes The Night Time II.flac.

这将代替 {},生成列表 find/home/madZeo/Multimedia/Musik/flac/-name"01.Here Comes The Night Time II.flac"xargs 然后像这样调用 find

exec("/usr/bin/find", ["find", "/home/madZeo/Multimedia/Musik/flac/", "-name", "\"01.Here Comes The Night Time II.flac\""], [...])

这告诉 find 查找名称以 "(引号字符)开头的文件。不存在这样的文件,因此失败。


修复方法是像这样编写命令:

$ xargs -pi find ~/Multimedia/Musik/flac/ -name {}

这最终结束了运行宁

exec("/usr/bin/find", ["find", "/home/madZeo/Multimedia/Musik/flac/", "-name", "01.Here Comes The Night Time II.flac"], [...])

,这就是你想要的。

问题是 xargs 运行 直接是它的子命令(find)。它不会构建一个新的命令行,由 shell 重新解析。它不会在 space 上拆分其传入参数,它不会解释引号,它不关心 "special" 个字符,如 $*\.它只是获取给定的单词列表,用当前输入替换任何出现的子字符串 {},然后执行它。

如果您天真地接受这个最终命令并将其粘贴到您的 shell 中,它将进行分词、删除引号等操作,从而导致不同的结果。