brace expansão e * expansão

4

Suponha que no diretório de trabalho, há apenas um arquivo djvu. Eu gostaria de fazer o backup do arquivo em um arquivo com o nome do arquivo tendo .bk extra no final. cp *.djvu{,.bk} copia o arquivo djvu em um arquivo chamado *.djvu.bk . Por que * no nome do arquivo de backup não é expandido? Como pode ser?

    
por Tim 01.04.2015 / 00:26

2 respostas

8

O que acontece é que o bash primeiro expande *.djvu{,.bk} para *.djvu *.djvu.bk e, em seguida, faz a expansão glob sobre eles. Isso explicaria o que você observa: no seu caso, *.djvu , corresponde a um arquivo existente, digamos foo.djvu e expande para isso, mas *.djvu.bk não corresponde a nenhum arquivo, e assim se expande como, *.djvu.bk .

A ordem de expansão é especificada na documentação do bash:

The order of expansions is: brace expansion, tilde  expansion,  parame‐
ter,  variable  and arithmetic expansion and command substitution (done
in a left-to-right fashion), word splitting, and pathname expansion.

Sugiro que você reescreva seu comando de cópia como:

for f in *.djvu; do cp -- "$f" "$f".bk; done

Ou talvez, para evitar a sobrecarga sintática de um loop for explícito:

parallel -j1 cp -- {} {}.bk ::: *.djvu

(pensando duas vezes ... isso não é muito mais curto.)

Para responder à sua sub-questão "como poderia ser expandido", pode-se usar um subcomando (exemplo em um diretório contendo apenas foo.djvu e bar.djvu ):

$ echo $(echo *.djvu){,.bk}
bar.djvu foo.djvu bar.djvu foo.djvu.bk

Mas essa não é uma solução tão segura quanto o loop for ou a chamada paralela acima; ele vai quebrar em nomes de arquivos contendo espaço em branco.

    
por 01.04.2015 / 00:29
4

A expansão de contraventamento acontece antes da expansão de caractere curinga, porque a expansão de contraventamento cria palavras separadas, enquanto a expansão de caractere curinga acontece no final quando os padrões de caracteres coringa já estão em palavras separadas. (De qualquer forma, se acontecer o contrário, então cp *.djvu{,.bk} não corresponderia a nada, porque não há arquivo cujo nome termine com .djvu{,.bk} e, em seguida, a expansão de contraventamento produziria as duas palavras *.dvju e *.djvu.bk .) Então, primeiro cp *.djvu{,.bk} é expandido para cp *.djvu *.djvu.bk , então cada palavra contendo curingas é expandida se os curingas corresponderem a pelo menos um arquivo. Como você tem arquivos correspondentes a *.djvu , mas nenhum que corresponda a *.djvu.bk , você acaba com algo como cp a.djvu b.djvu *.djvu.bk .

Se houver arquivos correspondentes a *.djvu.bk , esse padrão curinga também será expandido. Assim, você terá algo como cp a.djvu b.djvu a.djvu.bk c.djvu.bk . Se o objetivo era executar cp nos arquivos *.djvu e *.djvu.bk , mas permitir que nenhum padrão correspondesse a nada, você poderia usar padrões estendidos do ksh:

shopt -s extglob
cp *.djvu?(.bk) somedir/

Mas isso não é o que você está tentando fazer. Você não pode obter o que deseja por qualquer forma de expansão nos argumentos de cp : você precisa emitir um comando cp separado para cada arquivo .djvu , cada um com o destino correto. O único caso em que um comando cp é suficiente é se houver um único arquivo .djvu , de forma que *.djvu seja expandido para um único arquivo. Nesse caso, a maneira mais fácil de digitar o comando é digitar o padrão (comece a digitar cp *.djvu ), depois diga ao bash para expandi-lo pressionando Ctrl + X < kbd> * , pressione Backspace para deletar o espaço final e digite {,.bk} .

Se houver vários arquivos para copiar, você precisará invocar cp em um loop, de uma forma ou de outra, ou então usar uma ferramenta diferente que possa copiar arquivos de acordo com padrões de nomes.

Escrever o loop no bash é muito fácil:

for x in *.djvu; do cp "$x" "$x.bk"; done

Outra ferramenta que você pode usar é pax . Não é muito menos digitar aqui.

pax -rw -s'/$/.bk/' *.djvu .

O Zsh tem zcp para esse tipo de tarefa. Carregue no seu .zshrc :

autoload -U zmv
alias zcp='zmv -C' zln='zmv -L'

execute

zcp '*.djvu' '$f.bk'
    
por 01.04.2015 / 01:51