循环遍历`ls`导致bash shell脚本

Dan*_*ite 130 bash shell shell-script

有没有人有一个模板 shell 脚本来ls为目录名称列表做一些事情并遍历每个目录并做一些事情?

我打算做ls -1d */以获取目录名称列表。

Gen*_*mer 150

正如@shawn-j-goff 和其他人所建议的那样,编辑不使用 ls 来使用 glob 。

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

for f in *; do
  echo "File -> $f"
done
Run Code Online (Sandbox Code Playgroud)

您可以替换*with*.txt或任何其他返回列表(文件、目录或与此相关的任何内容)的 glob、生成列表的命令,例如$(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
Run Code Online (Sandbox Code Playgroud)

或者更好的是,如果您使用 Bash,您可以使用 Bash 参数扩展而不是生成子shell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done
Run Code Online (Sandbox Code Playgroud)

  • 如果文件名中有空格怎么办? (27认同)
  • 不幸的是,正如 Daniel 所说,如果任何文件或文件夹的名称中包含空格或换行符,则此答案中的代码将中断。它显示了对 `for` 循环非常常见的误用,以及尝试解析 `ls` 输出的典型陷阱。@DanielA.White,如果没有帮助(或可能具有误导性),您*可能*考虑不接受这个答案,因为就像您说的那样,您正在对目录采取行动。Shawn J. Goff 的回答应该为您的问题提供更强大和有效的解决方案。 (8认同)
  • -∞ [你不应该解析 `ls` 输出](http://mywiki.wooledge.org/ParsingLs),[你不应该在 `for` 循环中读取输出](http://mywiki.wooledge. org/BashFAQ/001),你应该使用 `$()` 而不是 `` 和 [Use More Quotes™](http://mywiki.wooledge.org/Quotes)。我确信@CoverosGene 的意思很好,但这太糟糕了。 (5认同)
  • 如果你真的想使用 `ls`,一个更好的选择是 `ls -1 | 读行时;做东西; 完成`。至少那个不会因为空格而中断。 (3认同)

Sha*_*off 77

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

有两种非常好的迭代文件的方法。在这里,我只是echo用来演示如何使用文件名;不过,你可以使用任何东西。

第一个是使用 shell 的本机通配功能。

for dir in */; do
  echo "$dir"
done
Run Code Online (Sandbox Code Playgroud)

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

如果您想递归进入子目录,那么除非您的 shell 具有一些扩展的通配符功能(例如bash's globstar。如果您的 shell 没有这些功能,或者如果您想确保您的脚本可以工作),否则这不会发生在各种系统上,下一个选项是使用find.

find . -type d -exec echo '{}' \;
Run Code Online (Sandbox Code Playgroud)

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

-exec参数的语法看起来有点滑稽。find在之后接受第一个参数-exec并将其视为要运行的程序,并且每个后续参数都将其作为传递给该程序的参数。有两个特殊参数-exec需要查看。第一个是{}; 这个参数被替换为前面部分find生成的文件名。第二个是;,它让我们find知道这是传递给程序的参数列表的结尾;find需要这个,因为您可以继续使用更多用于find和不用于 exec 程序的参数。的原因\是外壳也处理;特别是 - 它代表一个命令的结束,所以我们需要对它进行转义,以便 shell 将它作为参数提供给find而不是自己使用它;让外壳不特殊对待它的另一种方式是把它放在引号:';'工作只是以及\;用于这一目的。

  • +1 当您需要生成文件列表并在命令中使用它时,这绝对是要走的路。find -exec 受限于只能运行单个命令。使用循环,您可以畅所欲言。 (2认同)

小智 28

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

 for i in $(ls); do echo "$i"; done; 
Run Code Online (Sandbox Code Playgroud)

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

 IFS=$'\n';for file in $(ls); do echo $i; done
Run Code Online (Sandbox Code Playgroud)

最后,根据你在做什么,你甚至可能不需要 ls:

 for i in *; do echo "$i"; done;
Run Code Online (Sandbox Code Playgroud)

  • 文件名也可以包含换行符。打破 `\n` 是不够的。建议解析 `ls` 的输出从来都不是一个好主意。 (6认同)

Ole*_*nge 6

如果你安装了 GNU Parallel http://www.gnu.org/software/parallel/你可以这样做:

ls | parallel echo {} is in this dir
Run Code Online (Sandbox Code Playgroud)

要将所有 .txt 重命名为 .xml:

ls *.txt | parallel mv {} {.}.xml
Run Code Online (Sandbox Code Playgroud)

观看 GNU Parallel 的介绍视频以了解更多信息:http : //www.youtube.com/watch?v=OpaiGYxkSuQ