扩展全局模式的最佳方式?

扩展全局模式的最佳方式?

我需要以编程方式将全局模式(如../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

或在zshyash

set -o nullglob

尽管在zshnullglob最初来自的地方),您宁愿使用(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。)

相关内容