Use o globbing estendido de Bash para negar um nome de arquivo que comece com um ponto

2

Digamos que eu tenha este arquivo:

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

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

Negue que o !yes esteja funcionando corretamente, escape do ! com \ :

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$

Mas !(.git) não funciona:

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$ 

Escape . com \ não funcionando:

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$

Double slash \ não está funcionando:

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$ 

E eu encontrei este trabalho:

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$

Por que o escape direto não funciona? E qual é a maneira correta de negar neste caso? (Duvido que !(.git|.|..) obtenha resultados inesperados em alguns casos).

Note que eu sei que rsync pode ser usado para excluir, mas não é minha pergunta.

[UPDATE]

Eu habilitei extglob e dotglob para o meu caso de teste, eles não são a causa.

Acho que o motivo de !(.git) ter falhado é por causa do erro . and .. e abortar, não porque a expansão .git falhou. E !(.git|.|..) deve ser o caminho correto já.

    
por 林果皞 26.07.2017 / 23:53

2 respostas

1

Em bash , sem a opção dotglob , os arquivos ocultos são ignorados, a menos que o glob explicitamente (que é um líder literal . ) os solicite. E . e .. não são ignorados (ao contrário do que os shells mais sensatos gostam de pdksh , zsh ou fish do).

Com dotglob , apenas . e .. são ignorados, a menos que a glob comece com . .

Com extglob , bash adiciona suporte a alguns dos operadores% glob extendido deksh. No entanto, aqui com um bug / misfeature quando se trata de !(...) one.

Em ksh (embora não pdksh ) e bash , @(.*) é um caso em que você solicita explicitamente os dotfiles (embora a documentação não deixe isso claro).

Mas em !(.git) você não está solicitando dotfiles. ksh e zsh in ksh emulation lidam com isso corretamente, mas bash presume que você esteja solicitando dotfiles. E isso inclui . e .. , mesmo com dotglob . Então .git será copiado como parte da cópia de . .

Para contornar esse problema, você pode usar !([.]git) (aqui [.] torna o . não explícito) em combinação com dotglob ou excluir . e .. explicitamente com !(.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

No último caso, eu ainda adicionaria a opção dotglob , porque bash em uma versão futura pode ser corrigido para parar de incluir os dotfiles aqui como em outras shells.

Como eu uso o zsh que tem seus próprios operadores estendidos (com a opção extendedglob não ativada por padrão para manter a compatibilidade Bourne; ele também suporta ksh globs com a opção kshglob , mas esses são mais complicados), faria:

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

(observe que -a implica -r e você precisa do -- , pois não podemos garantir que os nomes dos arquivos não começarão com - ).

^.git é zsh de !(.git) do ksh. (D) para incluir dotfiles (mas nunca . nem .. ) apenas para esse glob.

    
por 27.07.2017 / 11:57
2

Por padrão, o shell não inclui '.' na expansão de curingas. Execute shopt -s dotglob , o que permite que a expansão do shell inclua '.'

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

Página man shopin interna

    
por 27.07.2017 / 00:05