为什么命令“ls | file”不起作用?

Ian*_*anC 32 command-line ls pipe file-command

我一直在研究命令行并了解到|(pipeline) 旨在将命令的输出重定向到另一个命令的输入。那么为什么命令ls | file不起作用呢?

file 输入是多个文件名之一,例如 file filename1 filename2

ls输出是文件夹上的目录和文件列表,所以我认为ls | file应该显示文件夹上每个文件的文件类型。

但是,当我使用它时,输出是:

    Usage: file [-bcEhikLlNnprsvz0] [--apple] [--mime-encoding] [--mime-type]
        [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
    file -C [-m magicfiles]
    file [--help]
Run Code Online (Sandbox Code Playgroud)

由于file命令的使用存在一些错误

Joh*_*man 70

根本问题是file期望文件名作为命令行参数,而不是标准输入。当你写ls | file的输出ls被作为输入传递给file. 不是作为参数,作为输入。

有什么不同?

  • 命令行参数是在命令后写入标志和文件名时,如cmd arg1 arg2 arg3. 在shell脚本这些参数可以作为变量$1$2$3,等在C你会通过访问它们char **argvint argc参数main()

  • 标准输入 stdin 是一个数据流。一些程序在没有给出任何命令行参数时喜欢catwc从 stdin 读取。在 shell 脚本中,您可以read用来获取单行输入。在 C 中,您可以在各种选项中使用scanf()getchar()

file通常不从标准输入读取。它期望至少有一个文件名作为参数传递。这就是为什么它会在您编写时打印出用法ls | file,因为您没有传递参数。

您可以使用xargs将 stdin 转换为参数,如ls | xargs file. 尽管如此,正如terdon 提到的,解析ls是一个坏主意。最直接的方法很简单:

file *
Run Code Online (Sandbox Code Playgroud)

  • @Braiam> 确实浪费和危险。但它有效,并且如果 OP 正在学习使用重定向,那么将它与更好的选项进行比较是很好的。为了完整起见,我还可以提到`file $(ls)`,它也以另一种方式起作用。 (4认同)
  • 其次,使用 _ls_ 来制作文件名列表似乎是一个坏主意,因为文件名上接受的特殊字符可能会在 _ls_ 上产生误导性的输出。由于它使用 _newlines_ 作为文件名之间的分隔符,并且文件名可以包含 _newlines_ 和其他特殊字符,因此最终输出可能不准确。 (3认同)
  • 或者强制 `file` 从其输入中获取文件名,使用 `ls | 文件 -f -`。仍然是一个坏主意。 (2认同)
  • @Braiam> 这就是重点。然后将 `ls` 的输出传送到 `file` 的标准输入中。试试看。 (2认同)
  • 我认为在阅读了所有答案之后,我对这个问题有了一个更大的了解,尽管我认为我需要进一步阅读才能真正理解这一切。首先,显然使用管道和重定向不会将输出解析为 _arguments_,而是解析为 _STDIN_。我仍然需要进一步阅读以更好地理解,但是进行肤浅的搜索 _arguments_ 似乎将文本解析为数组中的程序,而 _STDIN_ 就像一种汇集文件或输出信息的方式(并非所有程序都被设计为使用这个“池化”) (2认同)

ter*_*don 18

因为,正如你所说,输入file必须是filenamesls但是,的输出只是文本。它恰好是一个文件名列表并不会改变它只是文本而不是硬盘驱动器上文件位置的事实。

当您看到打印在屏幕上的输出时,您看到的是文本。无论该文本是一首诗还是一个文件名列表,对计算机来说都没有区别。它只知道它是文本。这就是为什么您可以将 的输出传递ls给将文本作为输入的程序(尽管您真的,真的不应该):

$ ls / | grep etc
etc
Run Code Online (Sandbox Code Playgroud)

因此,要使用将文件名作为文本(例如lsfind)列出的命令的输出作为采用文件名的命令的输入,您需要使用一些技巧。典型的工具是xargs

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total
Run Code Online (Sandbox Code Playgroud)

但是,正如我之前所说,您真的不想解析ls. 类似的东西find更好(在每个文件名之后print0打印 a\0而不是 newilne 并且-0ofxargs让它处理此类输入;这是使您的命令与包含换行符的文件名一起使用的技巧):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total
Run Code Online (Sandbox Code Playgroud)

这也有自己的方式来做到这一点,xargs根本不需要:

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total
Run Code Online (Sandbox Code Playgroud)

最后,您还可以使用 shell 循环。但是,请注意,在大多数情况下,xargs速度会更快、效率更高。例如:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
Run Code Online (Sandbox Code Playgroud)

  • @terdon 我认为在这种情况下这是一个严重的错误。“file(1) 将要操作的文件列表作为命令行参数,而不是标准输入”是理解为什么 OP 的命令不起作用的基础,而这种区别通常是 shell 脚本的基础;你掩盖它并没有给他们任何好处。 (5认同)
  • 这个答案无法解释标准输入和命令行参数之间的区别,因此,尽管比公认的答案更重要,但出于同样的原因,它仍然具有严重的误导性。 (3认同)

Bra*_*iam 6

了解到'|' (pipeline) 旨在命令的输出重定向到另一个命令的输入。

它不会“重定向”输出,而是获取程序的输出并将其用作输入,而 file 不接受输入而是将文件名作为参数,然后对其进行测试。重定向不会将这些文件名作为参数传递,管道也不会,越晚您在做什么。

--files-from如果您有一个列出要测试的所有文件的文件,您可以使用该选项从文件中读取文件名,否则只需将文件的路径作为参数传递。


小智 6

接受的答案解释了为什么管道命令不能立即工作,并且通过该file *命令,它提供了一个简单、直接的解决方案。

我想建议另一种可能在某个时候派上用场的替代方案。诀窍是使用反引号(`)字符。此处对反引号进行了详细解释。简而言之,它获取包含在反引号中的命令的输出,并将其作为字符串替换到剩余的命令中。

因此,find `ls`将获取ls命令的输出,并将其替换为find命令的参数。这比公认的解决方案更长、更复杂,但它的变体在其他情况下可能会有所帮助。


Mar*_*ams 5

ls通过管道的输出是一个实心数据块,每行以 0x0a 分隔 - 即换行符 - 并将file其作为一个参数获取,它期望多个字符一次处理一个。

作为一般规则,永远不要使用ls为其他命令生成数据源 - 有一天它会通过管道 .. 进入rm,然后你就有麻烦了!

最好使用循环,例如for i in *; do file "$i" ; done它会以可预测的方式产生您想要的输出。如果文件名带有空格,则有引号。

  • 更简单:`文件*` ;-) (8认同)
  • 第一段介于误导和直接废话之间。换行没有相关性。由于错误的原因,第二段是正确的。解析 ls 很糟糕,但不是因为它可能以某种方式神奇地“管道”到 rm。 (5认同)
  • @IanC 我真的不能强调解析`ls` 的输出是一个[非常非常糟糕的主意](http://mywiki.wooledge.org/ParsingLs)。不仅因为您可能会将其传递给诸如 `rm` 之类的有害内容,更重要的是因为它会破坏任何非标准文件名。 (3认同)
  • @DewiMorgan 该网站主要针对非技术受众,因此在此处传播/鼓励不良习惯有害无益。在 unix.SE 或其他技术社区,其用户拥有知识/方法可以非常靠近他们的脚而不是自己射击脚,您的观点可能成立(关于其他做法),但在这里它不会使您的评论看起来很聪明。 (2认同)