为什么命令“ ls | not” 文件”的工作?


32

我一直在研究命令行,并了解到|(管道)旨在将命令的输出重定向到另一个命令的输入。那么,为什么命令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]

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


2
如果使用plain ls,则表明您希望使用该file命令处理当前目录中的所有文件。...那么为什么不简单地做file *一下:,它将为每个文件,文件夹回复一行。
Knud Larsen

file *是最聪明的方法,我只是想知道为什么使用ls输出无效。疑问已清除:)
IanC '16

6
前提是有缺陷的:“文件输入是其他文件名之一,例如文件filename1 filename2”。这不是输入。这些是命令行参数,如@John Kugelman在下面指出的那样。
Monty Harder

3
切线地,解析ls通常不是一个好主意。
kojiro '16

Answers:


71

根本问题是file期望文件名作为命令行参数,而不是stdin。当您写入时,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通常不会从stdin读取。它期望至少一个文件名作为参数传递。这就是为什么在您编写时会打印出用法的原因ls | file,因为您没有传递参数。

您可以使用xargs将stdin转换为参数,如中所示ls | xargs file。但是,正如terdon所提到的那样,解析ls是一个坏主意。最直接的方法是:

file *

2
或使用强制file从其输入获取文件名ls | file -f -。仍然是一个坏主意。
频谱

2
@Braiam>这就是重点。并将ls输出通过管道传输到file的stdin。试试看。
频谱

4
@Braiam>确实是浪费和危险。但这是可行的,如果OP正在学习使用重定向,最好将它与更好的选项进行比较。为了完整起见file $(ls),我还可以提及,它也可以通过另一种方式起作用。
频谱

2
我认为阅读完所有答案后,我会对问题有更全面的了解,尽管我认为我需要进一步阅读才能真正理解所有内容。首先,显然使用管道和重定向不会将输出解析为参数,而是解析为STDIN。为了更好地理解它,我仍然需要进一步阅读,但是做一个肤浅的搜索参数似乎是将文本解析为数组中的程序,而将STDIN看作是一种将信息池化为文件或输出的方式(并非所有程序都旨在与这个“池”一起工作)
IanC

3
其次,使用ls列出文件名列表似乎不是一个好主意,因为文件名可以接受特殊字符,但这些字符最终可能会在ls上产生误导性的输出。由于它使用换行符作为文件名之间的分隔符,并且文件名可以包含换行符和其他特殊字符,因此最终输出可能不准确。
IanC '16

18

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

当您看到输出打印在屏幕上时,您看到的就是文本。该文本是一首诗还是文件名列表,对计算机没有影响。它所知道的只是文本。这就是为什么您可以将输出传递ls给将文本作为输入的程序的原因(尽管您确实应该这样做):

$ ls / | grep etc
etc

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

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total

不过,正如我之前所说,您确实不想解析的输出ls。诸如此类find的更好(在每个文件名后print0打印a \0而不是newilne,-0of xargs允许它处理这样的输入;这是使命令与包含换行符的文件名一起工作的技巧:

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

这样做也有其自己的方式,完全不需要xargs

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

最后,您还可以使用Shell循环。但是,请注意,在大多数情况下,xargs它将更快,更高效。例如:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2

附带的问题是,file除非给出明确的-占位符file foo,否则似乎不会真正读取stdin :compare echo foo | file,和echo foo | file -;实际上,这可能是在OP情况下出现用法消息的原因(即,不是真的因为的输出ls是“简单文本”,而是因为to的参数列表file为空)
steeldriver

@steeldriver是的。对于所有需要文件而不是文本作为输入的程序,都是这样的AFAIK。他们只是默认情况下忽略标准输入。请注意,echo foo | file -它实际上不是file在文件foo上运行,而是在stdin流上运行。
terdon

好吧cat,除了stdin -之外,还有像这样的奇怪的鸭子(?!),除了给定文件参数时,我想呢?
steeldriver '16

3
该答案无法解释stdin和命令行参数之间的区别,因此,尽管比已接受的答案更切合实际,但由于相同的原因,它仍然会产生严重的误导。
zwol

5
@terdon我认为在这种情况下这是一个严重的错误。“文件(1)取文件的列表作为命令行参数进行操作,而不是标准输入”是根本理解为什么OP的命令没有工作,而这一区别是根本性一般以shell脚本; 您不会通过掩饰自己来帮忙。
zwol

6

了解到“ |” (管道)用于将输出从命令重定向到另一个命令的输入。

它不“重定向”输出,而是将程序的输出用作输入,而file不将输入而是将filenames作为参数,然后对其进行测试。重定向不会将这些文件名作为参数传递,两个管道都不会传递参数,这是您稍后执行的操作。

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


6

可接受的答案说明了为什么pipe命令不能立即工作的问题,并且通过该file *命令,它提供了一个简单,直接的解决方案。

我想提出一个可能在某个时候派上用场的替代方法。诀窍是使用反引号(`)字符。此处将对反向标记进行详细说明。简而言之,它采用反引号中包含的命令输出,并将其作为字符串替换为其余命令。

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


我正在读一本关于在Linux上使用命令行的书(怀疑来自于我在Linux上进行试验),巧合的是,我刚刚读到了有关“命令替换”的信息。您可以使用$(command)command(在我的手机上找不到反斜杠代码)在bash中扩展命令的输出,并将其用作其他命令的参数。确实很有用,即使在这种情况下(使用ls)使用它,由于某些文件名上的特殊字符,仍然会导致一些问题。
IanC '16

@IanC不幸的是,大多数有关bash的书籍和教程都是垃圾,被不良实践,不推荐使用的语法,细微的bug污染;(唯一的)值得信赖的参考文献是bash开发人员,即手册和freenode上的#bash IRC频道(也请查看频道主题中链接的资源)。
ignis '16

1
有时使用命令替换确实很有帮助,但是在这种情况下,这很不合理-尤其是对于ls。


5

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

作为一般规则,切勿使用ls其他命令生成数据源-有一天它会通过管道插入.. rm然后您就麻烦了!

最好使用循环,例如这样for i in *; do file "$i" ; done可以预期地产生所需的输出。如果文件名带有空格,则使用引号。


8
更容易:file *;-)
Wayne_Yux

3
@IanC我实在不敢强调解析输出ls是一个非常非常糟糕的主意。不仅因为您可能会将其传递给诸如的有害内容rm,更重要的是因为它会破坏任何非标准文件名。
terdon

5
第一段介于误导和胡说八道之间。换行没有关联。第二段是正确的,原因有误。解析ls不好,但这不是因为它可能以某种方式神奇地“输送”到rm。
约翰·库格曼

1
是否rm从标准输入中获取文件名?我觉得不是。而且,作为一般规则,ls自Unix诞生以来,一直是使用Unix管道的数据源的主要示例之一。这就是为什么它的输出是管道时默认为每行一个简单的没有属性或修饰符的文件名,而输出是终端时通常使用默认格式的原因。
davidbak

2
@DewiMorgan该网站主要针对非技术受众,因此,在此处传播/鼓励不良习惯有害无益。在unix.SE或其他技术社区上,这些用户的知识/手段可以非常靠近自己的脚而不用自己射击双脚,您的观点可能成立(关于其他做法),但这并不能使您的评论看起来很聪明。
ignis '16

4

如果要使用管道来馈送,请file使用-f通常带有文件名的选项,但也可以使用单个连字符-从stdin中读取,因此

$ ls
cow.pdf  some.txt
$ ls | file -f -
cow.pdf:       PDF document, version 1.4
some.txt:        ASCII text

带连字符的技巧-可用于许多标准命令行实用程序(尽管--有时是有效的),因此始终值得尝试。

该工具xarg功能更强大,大多数情况下仅在参数列表过长时才需要(有关详细信息,请参阅此帖子)。


什么时候--?我从未见过。--通常是“标志结束”指示器。
约翰·库格曼

是的,但是我在程序员以这种方式使用的几个实例中发现了它。我不记得确切位置(如果我这样做会添加注释),但我记得当我发现了这件事我说出了诅咒和诅咒这些人肯定NSFW ;-)
deamentiaemundi

2

它可以像下面这样使用命令

ls | xargs file

对我来说会更好


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.