问题描述
我一直在研究命令行,并了解到|
(管道)是为了将命令的输出重定向到另一个命令的输入。那么为什么命令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
命令时出现一些错误
最佳解决思路
根本问题是file
期望文件名为命令行参数,而不是stdin。当您编写ls | file
时,ls
的输出将作为输入传递给file
。不作为参数,作为输入。
有什么不同?
-
命令行参数是在命令后写入标志和文件名时,如
cmd arg1 arg2 arg3
中所示。在shell脚本中,这些参数可用作变量$1
,$2
,$3
等。在C中,您可以通过char **argv
和int argc
参数访问main()
。 -
标准输入stdin是一个数据流。当没有给出任何命令行参数时,某些程序如
cat
或wc
从stdin读取。在shell脚本中,您可以使用read
获取单行输入。在C中,您可以在各种选项中使用scanf()
或getchar()
。
file
通常不会从标准输入读取。它希望至少有一个文件名作为参数传递。这就是为什么它在你编写ls | file
时会输出用法,因为你没有传递参数。
您可以使用xargs
将stdin转换为参数,如ls | xargs file
中所示。仍然,作为terdon mentions,解析ls
是一个坏主意。最直接的方法是:
file *
次佳解决思路
因为,正如您所说,file
的输入必须是文件名。但是,ls
的输出只是文本。它恰好是文件名列表并没有改变它只是文本而不是硬盘驱动器上文件位置的事实。
当您看到屏幕上打印的输出时,您看到的是文本。无论该文本是诗还是文件名列表,都不会对计算机产生影响。它只知道它是文本。这就是为什么你可以将ls
的输出传递给以文本为输入的程序(尽管你是really, really shouldn’t):
$ 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
第三种解决思路
learned that ‘|’ (pipeline) is meant to redirect the output from a command to the input of another one.
它不是”redirect”的输出,而是取一个程序的输出并将其用作输入,而文件不接受输入,而是filenames as arguments,然后进行测试。重定向不会将这些文件名作为piping执行的参数传递,后者正在执行的操作。
如果您有一个列出要测试的所有文件的文件,则可以使用--files-from
选项从文件中读取文件名,否则只需将路径作为参数传递给文件。
第四种思路
接受的答案解释了为什么管道命令不能直接工作,并且使用file *
命令,它提供了一个简单,直接的解决方案。
我想建议另一种可能在某个时候派上用场的替代方案。诀窍是使用反引号(`)
字符。重复详细解释了here的反引号。简而言之,它接受反引号中包含的命令的输出,并将其替换为剩余命令中的字符串。
因此,find `ls`
将获取ls
命令的输出,并将其替换为find
命令的参数。这比公认的解决方案更长,更复杂,但是在其他情况下,这种变体可能会有所帮助。
第五种思路
ls
通过管道的输出是一个固定的数据块,每行分隔0x0a – 即一个换行字符 – 而file
将其作为一个参数,它希望一次一个地处理多个字符。
作为一般规则,永远不要使用ls
为其他命令生成数据源 – 有一天它会管道进入rm
然后你就麻烦了!
最好使用一个循环,例如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
功能更强大,在大多数情况下仅在argument-list太长时才需要(有关详细信息,请参阅this post)。
第七种思路
它的工作原理如下所示
ls | xargs file
它对我来说会更好