我有以下简化的 bash 脚本
#!/bin/bash
files=("$@")
if [ "X$files" = "X" ]; then
files=$HOME/print/*.pdf;
fi
for file in "${files[@]}"; do
ls "$file";
done
如果我将参数(文件名)作为参数传递,此脚本将打印正确的文件名。另一方面,如果我不传递参数,它将打印
/home/user/print/*.pdf: No such file or directory
为什么在这种情况下文件名没有扩展,如何修复?请注意,我使用files=("$@")
and"${files[@]}"
结构是因为我读到它比通常的“files=$*”更受青睐。
答案1
您指定files
为标量变量而不是大批多变的。
在
files=$HOME/print/*.pdf
你正在分配一些细绳/home/highsciguy/print/*.pdf
就像$files
标量(又名字符串)变量一样。
使用:
files=(~/print/*.pdf)
或者
files=("$HOME"/print/*.pdf)
反而。 shell 会将该通配模式扩展为文件路径列表,并将每个文件路径分配给$files
大批。
glob 的扩展是在赋值时完成的。
您不必使用非标准 sh 功能,并且您可以通过编写它来使用您的系统sh
而不是这里:bash
#!/bin/sh -
[ "$#" -gt 0 ] || set -- ~/print/*.pdf
for file do
ls -d -- "$file"
done
set
是分配"$@"
位置参数数组。
另一种方法可能是存储通配模式在标量变量中:
files=$HOME/print/*.pdf
并让 shell 在$files
变量扩展时扩展 glob。
IFS= # disable word splitting
for file in $files; do ...
在这里,因为$files
没有引用(通常不应该这样做),所以它的扩展受到分词(我们在这里禁用)和通配符/文件名生成的影响。
因此*.pdf
将扩展到匹配文件列表。但是,如果$HOME
包含通配符,它们也可以扩展,这就是为什么仍然最好使用数组变量。
答案2
您可能在没有数组的旧 shell 中看到过类似files=$*
和的东西files=~/print/*.pdf
,然后是ls $files
。
不在双引号内的变量替换将变量的值解释为以空格分隔的 shell 通配符模式列表,这些通配符模式将被替换为匹配的文件名(如果有)。例如, after files=~/print/*.pdf
,ls $files
扩展为ls
与参数/home/highsciguy/print/bar.pdf
,/home/highsciguy/print/foo.pdf
等类似的内容。在这种情况下files=$*
,此赋值将传递给脚本的参数连接起来,中间有空格,然后ls $files
将它们拆分回来。
如果您的文件名包含空格或通配符,所有这些都会失败,这就是为什么您不应该这样做。请改用数组。
files=("$@")
if ((${#files[@]} == 0)); then
files=("$HOME"/print/*.pdf)
fi
注意
- 所有数组赋值都需要将数组值括在括号中:
var=(…)
。 - 要测试数组是否为空,请检查其长度。当是索引 0 的元素未设置的数组或空字符串
"$files"
时为空。files
还有[ "X$foo" = "X" ]
一种过时的方法来测试是否$foo
为空:所有现代 shell 都能[ -n "$foo" ]
正确实现。在 bash 中,您可以使用[[ -n $foo ]]
.
在不支持数组的 shell 中,实际上只有一个数组:shell 或当前函数的位置参数。在这里,您实际上并不需要数组files
,事实上使用位置参数会更容易。
#!/bin/sh
if [ "$#" -eq 0 ]; then
set -- ~/print/*.pdf
fi
for file do …