如何以编程方式判断文件名是否与一组大括号扩展的 glob 模式中的一个匹配?

如何以编程方式判断文件名是否与一组大括号扩展的 glob 模式中的一个匹配?

我想知道字符串是否$string与全局模式匹配$pattern$string可能是也可能不是现有文件的名称。我怎样才能做到这一点?

假设我的输入字符串采用以下格式:

string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

我想找到一个 bash 习惯用法来确定是否与、或任何其他任意全局模式$string匹配。这是我到目前为止所尝试过的:$pattern1$pattern2

  1. [[ "$string" = $pattern ]]

    这几乎有效,除了被$pattern解释为字符串模式而不是全局模式。

  2. [ "$string" = $pattern ]

    这种方法的问题是被扩展,然后在和 的扩展$pattern之间执行字符串比较。$string$pattern

  3. [[ "$(find $pattern -print0 -maxdepth 0 2>/dev/null)" =~ "$string" ]]

    此方法有效,但前提是$string包含存在的文件。

  4. [[ $string =~ $pattern ]]

    这不起作用,因为=~运算符会$pattern被解释为扩展正则表达式,而不是全局或通配符模式。

答案1

这个问题没有通用的解决方案。原因是,在 bash 中,大括号扩展(即{pattern1,pattern2,...}和文件名扩展(又名 glob 模式)被视为独立的事物,并在不同条件下和不同时间进行扩展。以下是 bash 执行的扩展的完整列表:

  • 大括号扩展
  • 波形符扩展
  • 参数和变量扩展
  • 命令替换
  • 算术展开
  • 分词
  • 路径名扩展

由于我们只关心其中的一个子集(可能是大括号、波形符和路径名扩展),因此可以使用某些模式和机制以可控的方式限制扩展。例如:

#!/bin/bash
set -f

string=/foo/bar

for pattern in /foo/{*,foo*,bar*,**,**/*}; do
    [[ $string == $pattern ]] && echo "$pattern matches $string"
done

运行此脚本会生成以下输出:

/foo/* matches /foo/bar
/foo/bar* matches /foo/bar
/foo/** matches /foo/bar

这是有效的,因为set -f禁用了路径名扩展,因此语句中仅出现大括号扩展和波形符扩展for pattern in /foo/{*,foo*,bar*,**,**/*}。然后[[ $string == $pattern ]],在执行大括号扩展后,我们可以使用测试操作 来测试路径名扩展。

答案2

我不相信{bar,baz} shell 全局模式(虽然肯定/foo/ba[rz]是)但如果你想知道是否$string匹配$pattern,你可以这样做:

case "$string" in 
($pattern) put your successful execution statement here;;
(*)        this is where your failure case should be   ;;
esac

你可以做任意多的事情:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac

答案3

正如帕特里克指出的,你需要一种“不同类型”的模式:

[[ /foo/bar == /foo/@(bar|baz) ]]


string="/foo/bar"
pattern="/foo/@(bar|baz)"
[[ $string == $pattern ]]

那里不需要引号。

答案4

如果它是关于检查是否$string在由存储的 glob 扩展产生的文件路径中$pattern(请记住,正如其他人所说,{foo,bar}这不是 glob 运算符),那么使用zsh,您可以执行以下操作:

if ()(($#)) $~pattern(NY1e['[[ $REPLY = $string ]]']); then
  print -r -- "$string is among the result of the $pattern glob expansion"
fi

使用bash,您始终可以使用循环:

among() (
  string=$1 pattern=$2 IFS=
  shopt -s nullglob extglob
  for file in $pattern@(); do
    [[ "$string" = "$file" ]] && return
  done
  false
)
if among "$string" "$pattern"; then
  printf '%s\n' "$string is among the result of the $pattern glob expansion"
fi

extglob启用后可以启用 ksh 扩展运算符的子集,您可以使用诸如 之类的东西foo/@(foo|bar)。我们添加@()到上面的模式以强制全局扩展;没有它,among foo foo即使foo不存在也会返回 true。但这意味着以/don结尾的模式不起作用(如among /bin/ '/*/')。

在 中ksh,只要noglob未启用且未braceexpand禁用,除了 split+glob 之外,在参数扩展时也会执行大括号扩展。ksh93还有一个~(N)glob 运算符相当于zsh/bashnullglob,所以你可以这样做:

function among {
  typeset string="$1" pattern="$2" IFS= file
  set -o braceexpand +o noglob
  for file in ~(N)$pattern; do
    [[ "$string" = "$file" ]] && return
  done
  false
}

among foo/bar 'foo/{bar,baz}'只要文件存在就会返回true foo/bar

请注意,ksh 的扩展 glob 运算符在其他扩展的结果中时不会被识别(以便按照 POSIX 要求a='@(x)'; echo $a输出)。@(x)

请注意,在所有这些中,即使字符串与模式匹配,among .foo '*'or among /etc/issue '*'or也会返回 false。among /usr/local/bin '/*/bin'

相关内容