如何在Bash数组中正确使用星号(*)并实现带通配符的命令执行
这个问题我之前踩过一模一样的坑!核心原因是Bash处理通配符的时机和引号的作用冲突了~
你原来的写法失败,是因为给/tmp/hello/*套了双引号,Bash会把星号*当成一个普通的字符,而不是通配符来解析。所以数组里存的就是字面意义上的/tmp/hello/*,执行的时候cp会去寻找名字正好是*的文件,自然找不到就报错了。
下面给你几种正确的实现方式,你可以根据自己的使用场景来选:
方法1:直接裸写通配符(最适合固定路径场景)
如果你的通配符路径是固定的,不需要动态生成,那赋值给数组的时候千万不要加引号,让Bash在赋值阶段就自动展开通配符:
mkdir -p /tmp/hello echo "hello" > /tmp/hello/hello.txt echo "world" > /tmp/hello/world.txt mkdir -p /tmp/world declare -a my_array=() my_array[0]="cp" my_array[1]=/tmp/hello/* # 这里去掉双引号! my_array[2]="/tmp/world" # 现在执行就会成功了 "${my_array[@]}"
这时候Bash会把/tmp/hello/*直接展开成该目录下的所有文件,数组里的元素就变成了cp、/tmp/hello/hello.txt、/tmp/hello/world.txt、/tmp/world,和你直接敲cp /tmp/hello/* /tmp/world的效果完全一致。
方法2:用eval延迟展开(适合动态通配符场景)
如果你的通配符是动态生成的(比如来自变量、用户输入),或者你想在执行命令的时候再展开通配符,可以用eval来强制重新解析整个命令行:
mkdir -p /tmp/hello echo "hello" > /tmp/hello/hello.txt echo "world" > /tmp/hello/world.txt mkdir -p /tmp/world declare -a my_array=() my_array[0]="cp" my_array[1]="/tmp/hello/*" my_array[2]="/tmp/world" # 用eval执行,会自动展开通配符 eval "${my_array[@]}"
不过要特意提醒你:eval会完全解析整个命令字符串,如果数组里的内容来自不可信的输入(比如用户随便输入的内容),可能会执行恶意代码,所以这个方法一定要谨慎使用。
方法3:用compgen安全展开通配符(最安全的方式)
如果你想更安全地处理通配符,尤其是当通配符路径可能来自不可信来源时,可以用compgen提前把通配符展开成具体的文件路径,再放进数组:
mkdir -p /tmp/hello echo "hello" > /tmp/hello/hello.txt echo "world" > /tmp/hello/world.txt mkdir -p /tmp/world declare -a my_array=() my_array[0]="cp" # 用compgen -G展开通配符,把结果追加到数组 mapfile -t my_array < <(compgen -G "/tmp/hello/*") my_array+=("/tmp/world") "${my_array[@]}"
compgen -G只会匹配符合通配符的文件,不会执行任何额外的命令,所以安全性很高,适合处理不确定的通配符路径。
总结一下:
- 固定路径的通配符,直接裸写赋值给数组就行;
- 动态场景可以用
eval,但要时刻警惕安全风险; - 追求安全的话,优先用
compgen提前展开通配符。
备注:内容来源于stack exchange,提问作者stackbiz

