为什么不命令“ls | 档案“工作?
我一直在研究命令行,并了解到|
(管道)用于将命令的输出重定向到另一个命令的输入。 那么为什么命令ls | file
ls | file
不起作用?
file
输入是一个或多个文件名,如file filename1 filename2
ls
输出是文件夹中的目录和文件列表,所以我想ls | file
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
命令有一些错误
根本问题是file
需要文件名作为命令行参数,而不是stdin。 当你写ls | file
ls | file
ls
的输出作为输入传递给file
。 不作为参数,作为输入。
有什么不同?
-
命令行参数是指在命令后写入标志和文件名,如
cmd arg1 arg2 arg3
。 在shell脚本中,这些参数可用作变量$1
,$2
,$3
等。在C中,您可以通过char **argv
和int argc
main()
int argc
参数访问它们。 -
标准输入stdin是一个数据流。 当没有给出任何命令行参数时,某些程序如
cat
或wc
从stdin读取。 在shell脚本中,您可以使用read
来获取单行输入。 在C中,您可以在各种选项中使用scanf()
或getchar()
。
file
通常不会从标准输入读取。 它希望至少有一个文件名作为参数传递。 这就是为什么它在你写ls | file
时打印出来的用法 ls | file
,因为你没有传递参数。
您可以使用xargs
将stdin转换为参数,如ls | xargs file
ls | xargs file
。 不过,正如terdon提到的那样 ,解析ls
是一个坏主意。 最直接的方法是:
file *
因为,正如你所说, file
的输入必须是文件名 。 然而, ls
的输出只是文本。 它恰好是文件名列表并没有改变它只是文本而不是硬盘驱动器上文件位置的事实。
当您看到屏幕上显示的输出时,您看到的是文本。 无论该文本是诗还是文件名列表,都不会对计算机产生影响。 它只知道它是文本。 这就是为什么你可以将ls
的输出传递ls
文本为输入的程序(虽然你真的,真的不应该 ):
$ ls / | grep etc etc
因此,要使用将文件名列为文本(例如ls
或find
)的命令输出作为获取文件名的命令的输入,您需要使用一些技巧。 典型的工具是xargs
:
$ ls file1 file2 $ ls | xargs wc 9 9 38 file1 5 5 20 file2 14 14 58 total
正如我之前所说,你真的不想解析ls
的输出。 像find
这样的东西更好(print0在每个文件名之后打印\0
而不是newilne, xargs
的-0
允许它处理这样的输入;这是让你的命令与包含换行符的文件名一起工作的技巧):
$ 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
得知’|’ (管道)用于将命令的输出重定向到另一个命令的输入。
它不会“重定向”输出,但会获取程序的输出并将其用作输入,而文件不接受输入,而是将文件名作为参数 ,然后对其进行测试。 重定向不传递这些文件名作为参数既不管道 ,也不传递你正在做的事情。
如果您有一个列出要测试的所有文件的文件,则可以使用--files-from
选项从文件中读取文件名,否则只需将路径作为参数传递给文件。
接受的答案解释了为什么管道命令不能直接工作,并且使用file *
命令,它提供了一个简单,直接的解决方案。
我想建议另一种可能在某个时候派上用场的替代方案。 诀窍是使用反引号(`)
字符。 这里将详细解释反引号。 简而言之,它接受反引号中包含的命令的输出,并将其替换为剩余命令中的字符串。
因此, find `ls`
将获取ls
命令的输出,并将其替换为find
命令的参数。 这比公认的解决方案更长,更复杂,但是在其他情况下,这种变体可能会有所帮助。
ls
通过管道的输出是一个固定的数据块,其中0x0a将每一行分开 – 即换行字符 – 并且file
将其作为一个参数获取,其中它希望一次一个地处理多个字符。
作为一般规则,永远不要使用ls
为其他命令生成数据源 – 有一天它会管道进入rm
,然后你就麻烦了!
最好使用循环,例如for i in *; do file "$i" ; done
for i in *; do file "$i" ; done
for i in *; do file "$i" ; done
,可以预测产生你想要的输出。 如果文件名包含空格,则引号存在。
如果要使用管道来提供file
使用选项-f
,后面通常后跟文件名,但也可以使用单个连字符-
从stdin读取,所以
$ ls cow.pdf some.txt $ ls | file -f - cow.pdf: PDF document, version 1.4 some.txt: ASCII text
使用连字符的技巧-
与许多标准命令行工具一起工作(尽管有时也是如此),所以总是值得一试。
工具xarg
function更强大,在大多数情况下只有在参数列表太长时才需要(详见本文 )。
它的工作原理如下所示
ls | xargs file
它对我来说会更好
这应该也有效:
file $(ls)
这里也讨论过: https : //unix.stackexchange.com/questions/5778/whats-the-difference-between-stuff-and-stuff