使用 Bash 的扩展通配符来否定以点开头的文件名

使用 Bash 的扩展通配符来否定以点开头的文件名

假设我有这个文件:

xb@dnxb:/tmp/aaa/webview$ tree -F -C -a .
.
├── .git/
├── ss/
├── y
└── !yes/

3 directories, 1 file
xb@dnxb:/tmp/aaa/webview$ 

通过使用以下转义来否定!yes工作正常:!\

xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(\!yes) /tmp/aaa/webview2                                         
xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2
total 16K
39331773 -rw-rw-r-- 1 xiaobai xiaobai ?    0 Jul  27 05:07 y
39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 .git/
39331772 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 ss/
39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul  27 05:44 ../
39331770 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul  27 05:44 ./
xb@dnxb:/tmp/aaa/webview$

!(.git)不工作:

xb@dnxb:/tmp/test/hello$ rm -r /tmp/test; shopt -s extglob; shopt -s dotglob; mkdir -p /tmp/test/hello; mkdir /tmp/test/hello2; cd /tmp/test/hello; mkdir '.git'; cp -r -a !(.git) /tmp/test/hello2/; ls -la /tmp/test/hello2/
cp: will not create hard link '/tmp/test/hello2/hello' to directory '/tmp/test/hello2/.'
cp: cannot copy a directory, '..', into itself, '/tmp/test/hello2/'
total 16
drwxrwxr-x 4 xiaobai xiaobai 4096 Jul  27 16:05 .
drwxrwxr-x 4 xiaobai xiaobai 4096 Jul  27 16:05 ..
drwxrwxr-x 2 xiaobai xiaobai 4096 Jul  27 16:05 .git
drwxrwxr-x 2 xiaobai xiaobai 4096 Jul  27 16:05 hello2
xb@dnxb:/tmp/test/hello$ 

.不工作时逃脱\

xb@dnxb:/tmp/aaa/webview$ rm -r /tmp/aaa/webview2
xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(\.git) /tmp/aaa/webview2        
cp: will not create hard link '/tmp/aaa/webview2/webview' to directory '/tmp/aaa/webview2/.'
cp: cannot copy a directory, '..', into itself, '/tmp/aaa/webview2'
xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2                                         
total 24K
39331773 -rw-rw-r-- 1 xiaobai xiaobai ?    0 Jul  27 05:07 y
39331774 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:39 !yes/
39331775 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:39 webview2/
39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 .git/
39331772 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 ss/
39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul  27 05:44 ../
39331770 drwxrwxr-x 6 xiaobai xiaobai ? 4.0K Jul  27 05:44 ./
xb@dnxb:/tmp/aaa/webview$

双斜杠\\也不起作用:

xb@dnxb:/tmp/aaa/webview$ rm -r /tmp/aaa/webview2
xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(\\.git) /tmp/aaa/webview2
xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2
total 20K
39331773 -rw-rw-r-- 1 xiaobai xiaobai ?    0 Jul  27 05:07 y
39331774 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:39 !yes/
39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 .git/
39331772 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 ss/
39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul  27 05:44 ../
39331770 drwxrwxr-x 5 xiaobai xiaobai ? 4.0K Jul  27 05:44 ./
xb@dnxb:/tmp/aaa/webview$ 

我发现这有效:

xb@dnxb:/tmp/aaa/webview$ rm -r /tmp/aaa/webview2
xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(.git|.|..) /tmp/aaa/webview2
xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2
total 16K
39331772 -rw-rw-r-- 1 xiaobai xiaobai ?    0 Jul  27 05:07 y
39331773 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:39 !yes/
39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul  27 05:43 ss/
39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul  27 05:45 ../
39331770 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul  27 05:45 ./
xb@dnxb:/tmp/aaa/webview$

为什么直接转义点不起作用?在这种情况下否定的正确方法是什么? (我怀疑!(.git|.|..)在某些情况下会得到意想不到的结果)。

请注意,我知道rsync可以用来排除,但这不是我的问题。

[更新]

我确实为我的测试用例启用了 extglob 和 dotglob,它们不是原因。

我认为失败的原因!(.git)是因为错误...中止,而不是因为.git扩展失败。并且!(.git|.|..)应该已经是正确的方法了。

答案1

在 中bash,如果没有该dotglob选项,隐藏文件将被忽略,除非 glob明确地(即字面前导.)要求它们。并且...不会被忽略(与更明智的 shell 喜欢pdksh,zshfish所做的相反)。

除非 glob 以 开头,否则dotglob.忽略和。...

使用extglob,bash添加了对一些ksh扩展全局运算符的支持。然而,这里有一个错误/错误功能!(...)

ksh(尽管不是pdksh)和中bash@(.*)是一种明确请求点文件的情况(尽管文档没有明确说明)。

!(.git)你并不要求点文件。kshzsh在仿真中ksh正确处理它,但bash假设您正在请求点文件。这包括.甚至..dotglob.因此.git将作为复制的一部分进行复制.

要解决这个问题,您可以使用!([.]git)(此处[.].明确)与 结合使用dotglob,或排除...显式与 结合使用!(.git|.|..)

$ bash -O extglob -c 'echo !(.git)'
. .. foo .foo
$ bash -O extglob -O dotglob -c 'echo !(.git)'
. .. foo .foo
$ bash -O extglob -O dotglob -c 'echo !([.]git)'
foo .foo
$ bash -O extglob -c 'echo !(.git|.|..)'
foo .foo

在后一种情况下,我仍然会添加该dotglob选项,因为bash在未来的版本中可能会修复以停止在此处包含点文件,就像在其他 shell 中一样。

当我使用zsh它有自己的扩展运算符时(extendedglob默认情况下未启用该选项以保持 Bourne 兼容性;它还支持带有该kshglob选项的 ksh glob,但这些更尴尬),我会这样做:

set -o extendedglob # (in my ~/.zshrc)
cp -a -- ^.git(D) target/

(请注意,这-a意味着-r您需要 ,--因为我们不能保证文件名不会以 开头-)。

^.git相当于zshksh 的!(.git)。仅(D)包含该 glob 的点文件(但决不包含.) 。..

答案2

默认情况下,shell 不包含“.”通配符的扩展。 Runshopt -s dotglob使 shell 扩展能够包含“.”

dotglob
    If set, Bash includes filenames beginning with a ‘.’ in the results of 
    filename expansion.

Shopt 内置手册页

相关内容