Bash glob 参数只显示第一个文件而不是所有文件

Posted

技术标签:

【中文标题】Bash glob 参数只显示第一个文件而不是所有文件【英文标题】:Bash glob parameter only shows first file instead of all files 【发布时间】:2015-06-09 17:12:55 【问题描述】:

我想运行这个命令行脚本

$ script.sh   lib/* ../test_git_thing

我希望它处理 /lib 文件夹中的所有文件。

FILES=$1
for f in $FILES
do
  echo "Processing $f file..."
done

目前它只打印第一个文件。如果我使用 $@,它会给我所有的文件,还有我不想要的最后一个参数。有什么想法吗?

【问题讨论】:

如果您要投反对票,那么说明原因的评论会非常有用... 不要这样做:SOURCE_FILES="$@:2"; for FILE in $SOURCE_FILES; do。如果任何文件名中有空格或全局字符,那将不起作用。你不需要临时变量,所以你可以写:for file in "$@:2"; do。如果你真的想要一个临时的,使用一个数组而不是一个简单的变量:source_files=("$@:2"); for file in "$source_files[@]"; do 请将您自己的答案添加为单独的答案,而不是对问题进行编辑——这样您的问题就可以被赞成/反对/&c。与您的答案分开。 【参考方案1】:

正如正确指出的,命令行上的lib/* 正在扩展为lib 中的所有文件。为防止扩展,您有 2 个选项。 (1) 引用您的意见:

$ script.sh 'lib/*' ../test_git_thing

或者 (2),关闭文件通配。但是,set -f 选项将禁用 shell 中的路径名扩展,但它将禁用所有路径名扩展(在脚本中设置它并没有帮助,因为扩展是由 shell 在将参数传递给脚本之前完成的)。在您的情况下,最好引用输入或将第一个参数作为目录名称传递,并在脚本中添加扩展:

DIR=$1
for f in "$DIR"/*

【讨论】:

set -f 禁用通配; nullglob 是忽略不匹配模式的 shell 选项,而不是将其视为文字字符串。 没错。谢谢你的收获。 @chepner set -f 尽管在即时脚本方面受到与关闭通配符相同的限制,但如果调用而不是逐个脚本地调用,它会禁用所有路径名扩展。反过来肯定会更容易......【参考方案2】:

当您调用“script.sh lib/*”时,参数列表将在命令行展开,您的脚本将被调用,并将 lib/ 中的所有文件作为参数。由于您只在脚本中引用 $1,因此它只打印第一个文件。您需要在命令行上转义通配符,以便将其传递给您的脚本以执行通配符。

【讨论】:

另一种选择是忽略最后一个参数还是在全局参数周围加上引号? 赞成解释全球正在扩大。理解这一点真的很有帮助。【参考方案3】:

在 bash 和 ksh 中,您可以像这样遍历除最后一个以外的所有参数:

for f in "$@:1:$#-1"; do
  echo "$f"
done

在zsh中,你可以做类似的事情:

for f in $@[1,$#-1]; do
  echo "$f"
done

$# 是参数的数量,$@:start:length 是 bash 和 ksh 中的子字符串/子序列表示法,而 $@[start,end] 是 zsh 中的子序列。在所有情况下,下标表达式都被计算为算术表达式,这就是$#-1 起作用的原因。 (在zsh中,你需要$#-1,因为$#-被解释为“$-的长度”。)

在所有三个 shell 中,您都可以使用带有标量变量的 $x:start:length 语法来提取子字符串;在 bash 和 ksh 中,您可以使用 $a[@]:start:length 和数组来提取值的子序列。

【讨论】:

接受这个,因为它有助于获得最佳解决方案。我最终更改了顺序,只是忽略了第一个参数,然后将剩余的参数用作文件数组。工作得很好!谢谢。【参考方案4】:

这回答了给定的问题,没有使用非 POSIX 功能,也没有解决方法,例如禁用通配符。

您可以使用循环找到最后一个参数,然后在处理文件列表时将其排除。在此示例中,$d 是目录名称,而 $f 与原始答案中的含义相同:

#!/bin/sh
if [ $# != 0 ]
then
        for d in "$@"; do :; done
        if [ -d "$d" ]
        then
                for f in "$@"
                do
                        if [ "x$f" != "x$d" ]
                        then
                                echo "Processing $f file..."
                        fi
                done
        fi
fi

此外,最好同时测试"$f" 是否是一个文件,因为如果找不到匹配项,shell 通常会将通配符传递给参数列表。

【讨论】:

以上是关于Bash glob 参数只显示第一个文件而不是所有文件的主要内容,如果未能解决你的问题,请参考以下文章

测试 glob 在 Bash 中是不是有任何匹配项

自学Python:查看文件夹及子目录所有文件路径

用于提取目录中所有 .tgz 的 Bash 脚本[重复]

显示当前目录而不是父目录中的所有隐藏文件

linux 继续学习bash特性(20170420)

Bash:查找传递的所有参数的长度