Como posso usar o inverso ou wildcards negativos quando a correspondência de padrão em um shell unix / linux?

StackOverflow https://stackoverflow.com/questions/216995

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?

Foi útil?

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
  1. Em uma linha do /some/dir/*.txt padrão fará com que o loop for para repetir todos os arquivos em /some/dir cujo fim nome com .txt.

  2. 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.)

  3. Na linha de três, todos os arquivos correspondentes) linha do case padrão [0-9]* será ignorado (a declaração continue pula para a próxima iteração do loop for). - 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top