在查找和循环的变量中使用 bash 扩展 globs 文件掩码

Using bash extended globs file masks in variables in find and loop

我正在尝试使用变量中的预设文件掩码来匹配文件。

mat $ ls -lQ /tmp/Mat
total 0
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:32 "testfile1"
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:33 "testfile1.gz"
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:33 "testfile2"
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:33 "testfile2.gz"
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:38 "testfile2.gz#id=142"
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:34 "testfile2test"
-rw-rw-r-- 1 Mat Mat 0 Mar  3 14:34 "testfile2test.gz"
mat $ file_mask=*file2*
mat $ ls /tmp/Mat/$file_mask?(.gz)
testfile2.gz  testfile2test.gz

我正在尝试获取:testfile2 testfile2.gz testfile2test testfile2.gz

总结结果:

tl;dr

  • 由于 bash 的 3.x 版本中存在 错误,OP 出现意外行为 =] 与某些扩展的 glob 模式有关,即 with shopt -s extglob in effect.

  • 但是,即使没有错误,代码也无法按预期工作,因为 globbing 模式 *file2*?(.gz)实际上与 *file* 相同 - 它将匹配具有 any 后缀的文件,而不仅仅是 .gz.

  • 只匹配包含 file2 根本没有 没有 后缀的名称,,如果他们有[至少]一个,[最后]后缀为.gz,使用*([^.])file2*([^.])?(*.gz)(这在 bash 3.x 中也能正常工作)。请注意,与 OP 的模式一样,这 需要使用 shopt -s extglob.

  • 激活扩展的 globbing

假设OP的意图如下:

仅匹配包含 file2 [在第一个后缀之前,如果有的话] eitherno 后缀的名称,,如果他们有[至少]一个,[最后]后缀为.gz

例如,匹配文件 file2 file2-asome-file2file2.gzfile2-a.gzfile2.tar.gz,但不匹配 file2.no(因为它有一个不是'.gz'的[last]后缀)。

虽然 一个 bash 3.x 影响 *?(...) 等模式的错误 - 见下文 - 有没有充分的理由使用 *?(...),因为它实际上与 * 相同,因为 * 匹配 any 序列字符数,包括后缀
下面的解决方案不受影响。

不能使用*来仅匹配文件名的([first]后缀之前的部分),因为 * 匹配 任何 字符串,无论是否是后缀的一部分。

因此,必须使用扩展的 glob *([^.]),它匹配包含任何字符的任意长度的字符串 除了 .(一个句点)。

此外,为了说明一个文件名可能有 多个 后缀,可选的 .gz 模式匹配部分应该是 ?(*.gz) .

放在一起:

注意:shopt -s extglob 必须有效才能使命令生效。

# Create test files; note the addition of "testfile2.tar.gz", which SHOULD 
# match, and "testfile2.no", which should NOT match:
$ touch "testfile1" "testfile1.gz" "testfile2" "testfile2.gz" "testfile2.gz#id=142" "testfile2test" "testfile2test.gz" "testfile2.tar.gz" "testfile2.no"

$ ls -1 *([^.])file2*([^.])?(*.gz)
testfile2
testfile2.gz
testfile2.tar.gz
testfile2test
testfile2test.gz

# The same, using a variable:
$ file_mask=*([^.])file2*([^.]) # NO globbing here (no globbing in *assignments*).
$ file_mask+=?(*.gz) # Extend the pattern; still no globbing.
$ ls -1 $file_mask   # Globbing happens here, due to unquoted use of the variable.
# Same output as before.

# Using a loop should work equally:
for f in *([^.])file2*([^.])?(*.gz); do echo "$f"; done
# Same output as before.

# Loop with a variable:
$ file_mask=*([^.])file2*([^.])
$ file_mask+=?(*.gz)
$ for f in $file_mask; do echo "$f"; done    
# Same output as before.

bash 3.x:

中不明显的扩展通配错误

请注意,该错误与是否使用变量无关。

我不知道这个错误在哪个版本中被修复了,但是它不存在于 4.3.30,例如。

简而言之,*?(...) 错误地表现得好像已指定 *+(...)

换句话说:独立的简单模式*后跟扩展模式?(...)(匹配零或1...实例)有效地表现像 * 后跟 +(...)(匹配 1 个或更多 ... 个实例)。

演示,在 bash 3.2.57 中观察到(OSX 10.10.2 上的当前版本;OP 使用 3.2.25):

$ touch f f.gz # create test files

$ ls -1 f?(.gz)    # OK: finds files with basename root 'f', optionally suffixed with '.gz'
f
f.gz

# Now extend the glob with `*` after the basename root.
# This, in fact, is logically equivalent to `f*` and should
# match *all files starting with 'f'*.
$ ls -1 f*?(.gz)    
f.gz
# ^ BUG: only matches the suffixed file.