如何在 ls 输出中区分文件和目录?我想处理文件并进入目录,但是,我只得到了所有文件的名称列表:
for i in ls B
do
echo $i
done
答案1
来自ls
手册页你可以看哪些条目是使用目录
-F, --classify
append indicator (one of */=>@|) to entries
所以如果你使用
for i in $(ls -F B) ; do
echo $i
done
您应该看到目录附加了内容/
,而其他文件没有。
但是,如果你想进入目录,最好使用test
for f in $(ls B) ; do
if [ -d $f ] ; then
recurse_into_directory
elif [ -f $f ]
process_file
else
echo "$f: neither regular file nor directory"
fi
done
答案2
您写道“想要处理文件并进入目录”,因此直接将其ls
作为解决方案可能为时过早。准确了解“处理文件并进入目录”的含义将有助于提供最佳解决方案。
不过,这里有几个常见的用例:
递归操作单个文件
假设您想要对从当前目录开始并持续到每个子目录的符合某些条件的每个文件执行某些操作。
例如:查找每个带.txt
扩展名的文件的行数。获取单个文件行数的命令是wc -l $filename
。(如果您为其提供多个文件名,它将分别输出每个文件名的行数,然后输出总数。)
这就是解决单个文件问题的方法——这始终是你在继续之前必须回答的第一个问题——但是,如何对所有文件进行递归操作呢?这部分问题可以通过命令(find
Unix 目录遍历命令)来解决。
find
详细学习命令可能很困难,但对于像这样的简单情况,它相当容易。首先要知道的是,每个find
命令都采用以下格式:
find DIR [PREDICATE, ..]
DIR
是起始目录(在本例中,.
始终是当前工作目录)。 APREDICATE
是一个表达式,find
用于决定当考虑一个文件或目录时下一步该做什么,或者做该文件或目录的一些内容。
基本算法find
如下:尝试当前正在检查的项目(文件或目录)上的第一个(命令行中最左边的)谓词。如果谓词为真,则尝试命令行中的下一个谓词。继续,直到尝试了所有给定的谓词。如果谓词为假,则停止处理该项目并重新开始处理下一个项目(再次从第一个谓词开始)。
如果正在检查的项目是目录,则一旦到达最后一个谓词或谓词为假,find
将继续处理目录内的项目。这有两个主要例外:
谓词
-prune
可用于选择性地禁用此功能;如果-prune
达到谓词并且当前项目是目录,或者该
-maxdepth=N
选项(不是谓词,它出现DIR
在命令行之前)可用于限制find
搜索的深度;如果当前目录N
比起始目录深一个或多个级别,那么无论哪种情况,目录的内容(以及递归的子内容)都是不是检查后,下一个项目将会与当前项目是文件而不是目录一样。
说到:如果正在检查的项目是一个文件,则“下一个项目”是同一目录中的下一个条目,或者,如果目录中没有剩余的项目,则当前目录将“弹出”,并且继续处理,下一个项目是进入目录时的下一个项目。
“处理项目”是什么意思?这意味着在命令行中从左到右尝试每个谓词,直到一个谓词为假,或者所有谓词都已尝试。
(在这一点上, 的不同版本之间存在分歧find
。在许多较新的版本中,例如在 Linux 上发现的版本,如果最后一个谓词为真且不是“动作”谓词,则find
假定您的意思是某物,因此它的行为就像-print
是给定了谓词以导致打印出路径名一样。在旧版本的 中find
,情况并非如此,并且此类项目的处理结果将为 nil。
举例来说:最简单的命令find .
没有任何谓词。在较新的版本中find
,这将导致从当前目录开始并递归进行的所有路径名列表,直到所有路径名都已打印。在较旧的版本中find
,相同的命令将需要同样长的时间才能运行(它必须根据不存在的谓词递归检查所有文件),但将绝对输出没有什么。
在结束处理谓词这个话题之前,我要指出,到目前为止,我的解释听起来好像谓词的唯一可能性是将它们进行逻辑上的“与”运算。事实并非如此,因为
- 还有一个
-o
对两个谓词进行“或”运算的谓词(实际上,也有一个-a
“与”谓词,但很少需要,因为正如我上面所写,这是默认行为); find
允许使用括号(由于 shell 转义规则,通常写\(
为 和\)
)将多个谓词分组为一个表达式;并且- 有一个否定运算符,通常写为
\!
。
解决了所有这些问题后,我们现在可以回到如何获取每个带后缀的文件的行数的问题.txt
:
- 如上所述,获取文件行数的命令是
wc -l
。 - 有一个谓词可用于对 当前正在检查的文件运行命令
find
。它是-exec CMD ;
,包括分号(必要时必须转义),并且 的文本中将用当前正在检查的路径名CMD
替换任何出现的标记。{}
- 另一个谓词让我们检查文件的后缀:
-name PATTERN
。因此,在这种情况下,当我们想要带有.txt
扩展名的文件时,我们将其用作*.txt
模式。
因此,了解了所有这些之后,我们可以编写的命令是:
find . -name '*.txt' -exec wc -l {} \;
(我们*.txt
在分号前使用引号和反斜杠,以防止 shell 将这些字符解释为特殊字符,以便find
看到它们。)这将递归检查每个如此命名的文件的行数。
这里有一个小问题,根据具体情况,你可以随意忽略它:如果你有一个目录命名为以.txt
? 结尾的东西,你会得到类似下面的内容:
$ find . -name '*.txt' -exec wc -l {} \;
42 ./myfile.txt
wc: ./foo.txt: Is a directory
0 ./foo.txt
1 ./foo.txt/bar.txt
为了解决这个问题,您必须添加另一个谓词,-type f
以告诉find
仅对-exec
普通文本文件执行谓词:
$ find . -type f -name '*.txt' -exec wc -l {} \;
42 ./myfile.txt
1 ./foo.txt/bar.txt
-type f
(您可能想知道出现在谓词之前还是之后是否重要-name '*.txt'
。其实这并不重要,因为目录总是会下降到其中,除非存在-prune
或-maxdepth
,如前所述。)
请注意以上是可以ls
与 Bash 或 Zsh shell 的高级功能结合使用。但这些解决方案更难解释和正确实施,所以我假设你提到的ls
是过早实施。(见XY 问题。
收集文件列表,然后对它们进行操作
我提到过,如果给定多个文件名,wc -l
则给出文件计数,然后是总数。但上述解决方案没有得到总数,因为wc
对每个名为的文件运行一次*.txt
。但如果您想要那个总数怎么办?
在这种情况下,您可以使用ls
,但您会面临一个问题:如果您的任何文件名可能包含空格或其他 shell 特殊的字符,您可能会收到错误,甚至无意中运行您不想运行的命令。
因此,再次强调,最好使用find
。 的较新版本find
(大多数情况下,我之前提到的那些版本会-print
为您插入,如果您省略了它)具有以下功能:-exec
像以前一样使用谓词,但不是以分号结尾,而是以加号 ( +
) 结尾。因此:
$ find . -type f -name '*.txt' -exec wc -l {} \+
42 ./myfile.txt
1 ./foo.txt/bar.txt
43 total
对于缺少此功能的版本find
,您可以将其find
与另一个程序结合使用,xargs
。xargs
获取其输入并使用给定的输入作为命令的参数来运行命令。因此,我们将使用它来复制我们的第一个命令:
$ find . -type f -name '*.txt' -print | xargs wc -l
42 ./myfile.txt
1 ./foo.txt/bar.txt
43 total
但是,如果其中一个文件名包含空格,此命令仍然存在问题:
$ ls
My Spacey File.txt foo.txt myfile.txt rakudo-info.md
$ find . -type f -name '*.txt' -print | xargs wc -l
42 ./myfile.txt
wc: ./My: No such file or directory
wc: Spacey: No such file or directory
wc: File.txt: No such file or directory
1 ./foo.txt/bar.txt
43 total
在这种情况下,wc
看到每一个单词文件名我的太空文件.txt作为单独的参数。为了解决这个问题,我们使用 的功能find
和 的对应功能,xargs
该功能使用空字符(\0
,这在文件名中是非法的)作为分隔符而不是换行符:
$ find . -type f -name '*.txt' -print0 | xargs -0 wc -l
42 ./myfile.txt
1 ./My Spacey File.txt
1 ./foo.txt/bar.txt
44 total
谓词-print0
告诉find
发送以空值分隔的输出;-0
选项xargs
对其输入执行相同的操作。
最后警告
如果您拥有大量文件,或者所有文件名的总字符数非常大,则可能会遇到系统允许的参数数量或大小限制。在这种情况下,和-exec ... \+
的谓词都会拆分列表并多次运行命令,以便每个文件名都使用一次。find
xargs
在现代系统上,这个限制足够大,至少在文件名达到数千个之前你不需要担心它。