Como posso usar o inverso ou wildcards negativos quando a correspondência de padrão em um shell unix / linux?
-
03-07-2019 - |
Pergunta
Digamos que eu queira copiar o conteúdo de um diretório excluindo arquivos e pastas cujos nomes contenham a palavra 'Music'.
cp [exclude-matches] *Music* /target_directory
O que deve ir no lugar de [excluem-jogos] para alcançar este objetivo?
Solução
Em Bash você pode fazê-lo, permitindo que a opção extglob
, como este (substituir ls
com cp
e adicionar o diretório de destino, é claro)
~/foobar> shopt extglob
extglob off
~/foobar> ls
abar afoo bbar bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob # Enables extglob
~/foobar> ls !(b*)
abar afoo
~/foobar> ls !(a*)
bbar bfoo
~/foobar> ls !(*foo)
abar bbar
Você pode mais tarde desativar extglob com
shopt -u extglob
Outras dicas
A opção shell extglob
lhe dá mais poderosa correspondência de padrão na linha de comando.
Você ligá-lo com shopt -s extglob
, e desligá-lo com shopt -u extglob
.
No seu exemplo, você iria inicialmente fazer:
$ shopt -s extglob
$ cp !(*Music*) /target_directory
O completo disponível ext terminou glob operadores bing são (trecho de man bash
):
Se a opção shell extglob está habilitado usando o builtin shopt, vários estendida operadores padrão de correspondência são reconhecidos. Na descrição seguinte, um tapinha andorinha-lista é uma lista de um ou mais padrões separados por uma |. padrões composite pode ser formado usando um ou mais dos seguintes sub-padrões:
- ? (Padrão-list)
Corresponde a zero ou uma ocorrência dos padrões dados- * (padrão-list)
Corresponde a zero ou mais ocorrências dos padrões dados- + (padrão-list)
Corresponde a um ou mais ocorrências dos padrões dados- @ (padrão-list)
Corresponde a um dos padrões dados- ! (Padrão-list)
Corresponde a qualquer coisa exceto um dos padrões dados
Assim, por exemplo, se você quisesse listar todos os arquivos no diretório atual que não são .c
ou .h
arquivos, você faria:
$ ls -d !(*@(.c|.h))
Claro, shell globing trabalhos normais, de modo que o último exemplo também pode ser escrita como:
$ ls -d !(*.[ch])
Não em bash (que eu saiba), mas:
cp `ls | grep -v Music` /target_directory
Eu sei que isso não é exatamente o que você estava procurando, mas vai resolver o seu exemplo.
Se você quiser evitar o custo mem de usar o comando exec, eu acredito que você pode fazer melhor com xargs. Eu acho que o seguinte é uma alternativa mais eficiente para
find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
festa, uma alternativa para shopt -s extglob
é a variável GLOBIGNORE
. Não é realmente melhor, mas acho que é mais fácil de lembrar.
Um exemplo que pode ser o que o poster original queria:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
Quando terminar, unset GLOBIGNORE
para ser capaz de rm *techno*
no diretório de origem.
Você também pode usar um loop for
muito simples:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
A minha preferência pessoal é usar grep e o tempo de comando. Isto permite escrever scripts poderosos ainda legíveis, garantindo que você acaba fazendo exatamente o que você quer. Além disso, usando um comando echo você pode executar uma corrida seca antes de realizar a operação real. Por exemplo:
ls | grep -v "Music" | while read filename
do
echo $filename
done
irá imprimir os arquivos que você vai acabar copiando. Se a lista estiver correta o próximo passo é simplesmente substituir o comando echo com o comando de cópia da seguinte forma:
ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
Uma solução para isso pode ser encontrado com find.
$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt
Find tem muito poucas opções, você pode ficar muito específico sobre o que você incluir e excluir.
Edit: Adam nos comentários observou que este é recursiva. encontrar opções mindepth e maxdepth pode ser útil no controle desta.
Um truque que eu não vi aqui ainda que não usa extglob
, find
, ou grep
é tratar duas listas de arquivos como conjuntos e "diff" -los usando comm
:
comm -23 <(ls) <(ls *Music*)
comm
é preferível sobre diff
porque ele não tem cruft extra.
Isso retorna todos os elementos do conjunto 1, ls
, que são não também em conjunto 2, ls *Music*
. Isso requer ambos os conjuntos para a ordem de classificação para funcionar corretamente. Nenhum problema para ls
e expansão glob, mas se você está usando algo como find
, certifique-se de invocar sort
.
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
potencialmente úteis.
A seguinte lista todos os arquivos *.txt
trabalha no dir atual, exceto aqueles que começam com um número.
Isso funciona em bash
, dash
, zsh
e todos os outros shells compatíveis com POSIX.
for FILE in /some/dir/*.txt; do # for each *.txt file
case "${FILE##*/}" in # if file basename...
[0-9]*) continue ;; # starts with digit: skip
esac
## otherwise, do stuff with $FILE here
done
-
Em uma linha do
/some/dir/*.txt
padrão fará com que o loopfor
para repetir todos os arquivos em/some/dir
cujo fim nome com.txt
. -
Na linha dois uma declaração caso é usado para eliminar arquivos indesejados. - As tiras de expressão
${FILE##*/}
fora qualquer componente que leva o nome dir do nome do arquivo (aqui/some/dir/
) de modo que padrões pode igualar contra apenas o nome base do arquivo. (Se você só está eliminando nomes de arquivos com base em sufixos, você pode encurtar isso para$FILE
vez.) -
Na linha de três, todos os arquivos correspondentes) linha do
case
padrão[0-9]*
será ignorado (a declaraçãocontinue
pula para a próxima iteração do loopfor
). - Se você quiser, você pode fazer algo mais interessante aqui, por exemplo, como pular todos os arquivos que não comecem com uma letra (a-z), utilizando[!a-z]*
, ou você pode usar vários padrões para pular vários tipos de nomes de arquivos, por exemplo,[0-9]*|*.bak
para ignorar arquivos ambos os arquivos.bak
e arquivos que não começar com um número.
isso iria fazê-lo excluindo exatamente 'Music'
cp -a ^'Music' /target
isto e aquilo para excluir coisas gostam da música? * Ou * Música
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target