我需要以编程方式将全局模式(如../smth*/*
、 或/etc/cron*/
)扩展为文件列表。最好的方法是什么?
答案1
只需让它在数组声明的右侧展开即可:
list=(../smth*/) # grab the list
echo "${#list[@]}" # print array length
echo "${list[@]}" # print array elements
for file in "${list[@]}"; do echo "$file"; done # loop over the array
nullglob
请注意,需要设置shell 选项。
默认情况下未设置。
它会导致不匹配的 glob 扩展为空,而不是导致错误(在zsh
或中bash -O failglob
)或按字面传递(所有其他类似 Bourne 的 shell)。
将其设置bash
为
shopt -s nullglob
或在zsh
或yash
与
set -o nullglob
尽管在zsh
(nullglob
最初来自的地方),您宁愿使用(N)
glob 限定符来避免更改全局设置:
list( ../smth*/(N) )
ksh93 等效项:
list=( ~(N)../smth*/ )
答案2
compgen
是一个 Bash 内置程序,您可以将转义 (!) 模式传递给它,它会输出匹配项,根据是否存在返回 true 或 false。如果您需要从变量/脚本参数传递全局模式,这尤其有用。
glob_pattern='../smth*/*'
while read -r file; do
# your thing
echo "read $file"
done < <(compgen -G "$glob_pattern" || true)
添加|| true
可防止错误返回导致compgen
任何问题。此方法避免了没有匹配的问题,并且不需要更改 nullglob 选项。
如果您需要数组中的项目,只需在files=()
循环之前和files+=("$file")
循环内部初始化一项即可。然后,您可以通过简单地使用 来检查数组的长度来查看是否有任何匹配项if [[ ${#files[@]} -gt 0 ]]; then
。
答案3
我想使用标准输入(管道),以防生成的命令超出命令行长度限制。以下命令对我有用:
echo "../smth*/*" "/etc/cron*/" | xargs -n1 -I{} bash -O nullglob -c "echo {}" | xargs -n1
或者对于全局列表:
cat huge_glob_list.txt | xargs -n1 -I{} bash -O nullglob -c "echo {}" | xargs -n1
答案4
最近我也有同样的问题。我发现解决方案非常简单:(并且它符合 POSIX 标准。)
- 设置
$IFS
为空字符串,禁用通过空格字符进行单词分割。 - 然后只需取消引用变量即可让它扩展全局。
示例代码如图所示for循环:
pattern='some * dir/my file *'
unset old_IFS ; [ -n "${IFS+x}" ] && old_IFS=${IFS} ; IFS=''
IFS=''
for f in ${pattern} ; do
IFS=${old_IFS} ; [ -z "${old_IFS+x}" ] && unset IFS
printf 'Filenames: %s \n' "${f}"
done
请注意,我不会通过POSIX 中未定义的shopt -s nullglob
方式设置 nullglob。shopt
如果未找到全局模式,则该模式将扩展到自身。Filenames: some dir/my file *
打印在上面的代码中。if [ -e "${f}" ]; then ...
如有必要,添加支票很容易。
可以使用相同的方法来设置位置参数还。
pattern='some * dir/my file *'
unset old_IFS ; [ -n "${IFS+x}" ] && old_IFS=${IFS} ; IFS=''
set -- ${pattern}
IFS=${old_IFS} ; [ -z "${old_IFS+x}" ] && unset IFS
unset old_IFS
printf '[%s]\n' "$@"
请注意,我们不能将其变成 one-liner IFS='' command set -- ${pattern}
。这一行不会禁用分词。
它可用于功能参数,但不建议这样做。的恢复$IFS
必须位于函数的第一条语句处,该语句风格不对称,很容易被遗忘。
func() {
IFS=${old_IFS} ; [ -z "${old_IFS+x}" ] && unset IFS
unset old_IFS
printf '[%s]\n' "$@"
}
pattern='some * dir/my file *'
unset old_IFS ; [ -n "${IFS+x}" ] && old_IFS=${IFS} ; IFS=''
func $pattern
就我个人而言,我更喜欢传递$pattern
到函数中,然后传递set -- $pattern
到函数内部。但如果函数还带有其他位置参数,则并不总是可能的。
func() {
unset old_IFS ; [ -n "${IFS+x}" ] && old_IFS=${IFS} ; IFS=''
set -- ${pattern}
IFS=${old_IFS} ; [ -z "${old_IFS+x}" ] && unset IFS
unset old_IFS
printf '[%s]\n' "$@"
}
pattern='some * dir/my file *'
func $pattern
此方法适用于模式和文件路径:
- 如果它们包含空白字符,实际上是所有字符,并且
- 如果它们包含全局字符,请使用转义
\*
来匹配文字*
,并且 - 当 glob 字符位于目录路径组件和/或文件名组件中时。 (如果需要在目录路径中使用 glob,则无法使用 轻松实现
find
。)