当路径作为参数给出时,“For”循环不会迭代列表

Luk*_*keB 2 bash shell-script wildcards

我正在练习shell 脚本,并尝试制作一个简单的脚本,该脚本将目录作为参数,循环遍历其中的每个文件并打印出其名称和大小。

\n
#!/bin/bash\n# A practice shell script to try and display a list of file names\n# and their sizes using the output of ls -l and cut.\ndeclare -i index\nexport index=0\nexport name=""\nexport size=0\n\nfor file in $1 ; do\n   index+=1\n   name=`basename $file`\n   size=`ls -l $file | cut -d " " -f 5`\n   echo "$index: $name, size: $size bytes"\ndone\n
Run Code Online (Sandbox Code Playgroud)\n

当我./*作为参数给出时,它会针对一个文件执行此操作,仅此而已。但是,如果我编辑上面的代码并将其放在./*的位置$1,它就会工作并循环当前目录中的所有文件。

\n

为什么\xe2\x80\x99 不会做同样的事情,什么时候$1应该等于./*

\n

Adm*_*Bee 10

原因是调用脚本的 shell 在传递给脚本./* 之前扩展了通配模式。这意味着,如果您的通配模式与例如匹配file1.txtfile4.txt则将脚本调用为

./my_script.sh ./*
Run Code Online (Sandbox Code Playgroud)

实际上会被解释为

./my_script.sh file1.txt file2.txt file3.txt file4.txt
Run Code Online (Sandbox Code Playgroud)

这些将是 shell 脚本看到的参数。

如需进一步阅读,请查看Bash 参考手册中有关 shell 扩展顺序的部分。

有两种可能性可以解决该问题:

  1. 如果您确定始终想要迭代给定目录中的所有文件,请将目录作为参数传递,然后迭代
    for f in "$1"/*
    do
        # operations on "$f"
    done
    
    Run Code Online (Sandbox Code Playgroud)
  2. 或者,如果您确定只传递要操作的文件名,请迭代整个参数列表,如下所示
    for f in "$@"
    do
        # operations on "$f"
    done
    
    Run Code Online (Sandbox Code Playgroud)

如果您想通过将 glob 模式传递到脚本中来实现这一点 - 这当然是一个有趣的练习 - 这也是可能的(请参阅 @ilkkachu 的评论)。正如 @fra-san 在评论中提到的,该方法具有优点 - 它可以为脚本使用增加更多灵活性,并且它规避了 shell 命令行参数的限制(参见“参数列表太长”;尽管 RAM 会仍然限制结果文件名列表的长度) - 但需要您格外小心。

  • 您可以通过将参数括在引号(单引号或双引号)中或使用反斜杠转义 glob 字符来防止 shell 扩展 glob:
    ./my_script.sh "./*"
    ./my_script.sh './*'
    ./my_script.sh ./\*
    
    Run Code Online (Sandbox Code Playgroud)
  • $1 在脚本内部,您将引用不带引号的位置参数,以便它实际上由 shell 解释(这是我们经常想要避免的)。
  • 由于“解释”不仅涉及扩展(见上文),还涉及分词,因此您需要将输入字段分隔符 IFS设置为空字符串,以确保不会发生分词。
  • 循环for看起来像
    IFS=
    for f in $1
    do
        # Operations on "$f"
    done
    
    Run Code Online (Sandbox Code Playgroud)

关于脚本的一些一般注意事项:

  • 始终引用 shell 变量,特别是当它们包含文件名时,否则您的脚本将偶然发现其中包含空格或其他甚至更奇特的字符的文件名 - 请记住,即使换行符也是文件名允许的字符(恶心)!
  • 由于类似的原因,强烈建议不要ls解析的输出。如果您想识别特定文件的属性,该工具是更好的选择。为了确定文件的大小,例如,您可以使用 stat
    size=$(stat --printf="%s" "$f")
    
    Run Code Online (Sandbox Code Playgroud)
  • 建议使用“新”样式进行命令替换$( ... )而不是旧的“反引号样式” ` ... `
  • 使用 来检查 shell 脚本是一个好习惯shellcheck,在许多 Linux 发行版中也可以作为独立工具使用,以防止这种(和其他)可能的错误源。