遍历`ls`会导致bash shell脚本


93

是否有人使用模板外壳程序脚本来ls处理目录名称列表,并循环遍历每个目录并执行某项操作?

我正在计划ls -1d */获取目录名称列表。

Answers:


109

如@ shawn-j-goff和其他建议所建议,不要编辑在全局范围内使用ls。

只需使用一个for..do..done循环:

for f in *; do
  echo "File -> $f"
done

你可以替换*使用*.txt或(与此有关的文件,目录或其它)返回一个列表中的任何其他的水珠,生成一个列表中的命令,例如$(cat filelist.txt),或者实际上与列表替换它。

do循环内,您只需引用带有美元符号前缀的循环变量即可($f在上面的示例中如此)。您可以执行echo此操作,也可以执行其他任何操作。

例如,.xml将当前目录中的所有文件重命名为.txt

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

甚至更好的是,如果您正在使用Bash,则可以使用Bash参数扩展而不是生成子shell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

22
如果文件名中有空格怎么办?
丹尼尔·怀特

4
不幸的是,正如Daniel所说,如果任何文件或文件夹的名称中包含空格或换行符,则此答案中的代码将中断。它显示了一个非常常见的for循环滥用,以及尝试解析的输出的典型陷阱ls。@ DanielA.White,如果它对您没有帮助(或可能会引起误解),则可以考虑不接受此答案,因为就像您说的那样,您正在处理目录。Shawn J. Goff的答案应为您的问题提供更可靠且有效的解决方案。
slhck

4
-∞ 您不应该解析ls输出不应该for循环读取输出,应该使用$()而不是``和Use More Quotes™。我敢肯定@CoverosGene的意思很好,但这太糟糕了。
l0b0

1
如果您真的想使用一个更好的选择lsls -1 | while read line; do stuff; done。至少一个空格不会中断。
伊曼纽尔·乔鲍德

1
mv -v您不需要echo "moved $x -> $t"
DmitrySandalov '16

68

使用of的输出ls获取文件名不是一个好主意。它可能会导致故障,甚至导致危险的脚本。这是因为文件名可以包含除/和以外的任何字符null,并且ls不使用这些字符中的任何一个作为分隔符,因此,如果文件名包含空格或换行符,则会得到意外的结果。

有两种遍历文件的非常好的方法。在这里,我只是简单echo地演示了如何使用文件名进行操作;您可以使用任何东西。

第一种是使用Shell的本地Globing功能。

for dir in */; do
  echo "$dir"
done

Shell扩展*/for循环读取的单独参数。即使文件名中包含空格,换行符或其他任何奇怪的字符,for也会将每个完整名称视为一个原子单位;它不会以任何方式解析列表。

如果你想递归进入子目录,那么这不会做,除非你的shell有一些扩展通配符功能(如bashglobstar。如果你的shell不具备这些功能,或者如果你想确保你的脚本将工作在各种系统上,那么下一个选择是使用find

find . -type d -exec echo '{}' \;

在此,该find命令将调用echo并传递文件名的参数。它为找到的每个文件执行一次此操作。与前面的示例一样,这里没有解析文件名列表。相反,文件名将完全作为参数发送。

-exec参数的语法看起来有些滑稽。find接受第一个参数-exec,并将其作为要运行的程序,并将每个后续参数作为传递给该程序的参数。有两个特殊的参数-exec需要看。第一个是{}; 此参数将替换为之前部分find生成的文件名。第二个是;,它让我们find知道这是传递给程序的参数列表的末尾;find之所以需find要这样做,是因为您可以继续使用更多的参数,这些参数是针对执行程序的,而不是针对执行程序的。的原因\是外壳程序也处理;特别是-它代表命令的结尾,因此我们需要对其进行转义,以便外壳程序将其作为自变量find而不是自己使用。使外壳不被特别对待的另一种方法是将其用引号引起来:';'\;此目的一样有效。


2
+1这绝对是您需要生成文件列表并在命令中使用它时要采用的方法。find -exec受只能运行单个命令的限制。通过循环,您可以充分利用自己的内心。
MaQleod

+1。希望更多的人会使用find。有魔术可以做到,甚至-exec可以解决公认的局限性。该-print0选项对于与结合使用也很有价值xargs
ghoti

loop选项不会显示隐藏目录。查找选项不会显示符号链接。
jinawee

15

对于带有空格的文件,您必须确保引用该变量,例如:

 for i in $(ls); do echo "$i"; done; 

或者,您可以更改输入字段分隔符(IFS)环境变量:

 IFS=$'\n';for file in $(ls); do echo $i; done

最后,根据您的操作,您甚至可能不需要ls:

 for i in *; do echo "$i"; done;

在第一个示例中很好地使用了subshel​​l
Jeremy L

为什么IFS在分配后和新字符之前需要$?
安迪·伊瓦内斯

3
文件名也可以包含换行符。中断\n不足。建议您解析的输出绝不是一个好主意ls
ghoti


4

只是为了增加CoverosGene的答案,这是一种只列出目录名称的方法:

for f in */; do
  echo "Directory -> $f"
done

1

为什么不将IFS设置为换行符,然后捕获ls数组中的输出?将IFS设置为换行符应该可以解决文件名中包含有趣字符的问题;使用起来ls很好,因为它具有内置的排序工具。

(在测试中,我无法将IFS设置为,\n但是将其设置为换行符退格键即可,如此处其他地方所述):

例如(假设ls传入了所需的搜索模式$1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

这在OS X上特别方便,例如,要捕获按创建日期(从最旧到最新)排序的文件列表,ls命令为ls -t -U -r


2
文件名也可以包含换行符,当允许用户命名自己的文件时,它们通常会包含换行符。中断\n不足。唯一有效的解决方案使用带路径名扩展的for循环或find
ghoti

传输文件名列表的唯一可靠方法是用NUL字符将它们分开,因为这是绝对不包含在文件路径中的唯一文件名。
glglgl 2015年

-3

这就是我的方法,但是可能有更有效的方法。

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

6
坚持用管道代替文件:>ls | while read i; do echo filename: $i; done
杰里米L,2009年

凉。我应该说,您也可以在两个命令之间使用$ EDITOR filelist.txt。您可以在编辑器中做很多事情,比在命令行上做起来容易。与这个问题无关。
TREE

您的解决方案根本无法解决包含换行符和其他花哨内容的文件名的问题。
glglgl 2015年
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.