Como usar curingas em um comando xargs?

5

Eu tenho um diretório com arquivos numerados, por exemplo 1_foo.txt 2_bar.asc 13_test.png e deseja movê-los para diretórios individuais (por exemplo, 1, 2 e 13) usando o mais simples possível de um comando bash.

Criar os diretórios numerados foi fácil:

mkdir $(seq 1 15)

Eu também criei um comando para copiar os arquivos em seus respectivos diretórios:

seq 15 -1 1 | xargs -I@ mv @_* @

Isso não funciona, no entanto, como * é interpretado como um caractere normal quando usado com xargs, me dando erros como "mv: File '15_ *' not found.".

Existe alguma maneira fácil de usar * como curinga em um comando chamado por xargs?

    
por n.st 13.12.2012 / 20:28

2 respostas

17

Você estava muito perto.

seq 15 -1 1 | xargs -I@ sh -c 'mv @_* @'

Você precisa atrasar a interpretação (expansão) do * até depois da substituição de @ . (Mas você já entendeu que esse era o problema, né?)

Eu tenho informado de nunca incorporar um nome de arquivo desconhecido (ou outra string de substituição) diretamente em uma linha de comando do shell. O exemplo acima é provavelmente bastante seguro, porque você sabe o que as cordas vão ser 15 , 14 ,…, 3 , 2 e 1 . Mas usando o exemplo acima como um modelo para comandos mais complexos pode ser perigoso. Um arranjo mais seguro seria

seq 15 -1 1 | xargs -I@ sh -c 'mv "$1"_* "$1"' x-sh @

onde x-sh é uma string arbitrária que será usado para rotular quaisquer mensagens de erro emitidas pelo shell chamado. Isso é equivalente ao meu primeiro exemplo, exceto, em vez de incorporar as strings (representadas por @ ) diretamente no comando shell, injeta-os fornecendo o @ como um argumento para o shell, e, em seguida, referenciá-los como "$1" .

P.S. Você sugeriu executar o comando seq no sentido inverso ( seq 15 -1 1 , que gera 15 , 14 ,…, 3 , 2 , 1 em vez de 1 , 2 , 3 ,…, 14 , 15 ) e ninguém mencionou. Esta seria uma parte importante da resposta se os nomes dos arquivos forem iguais a 1foo.txt , 2bar.asc e 13test.png , etc. (com vários caracteres aparecendo após o número, em vez de sempre _ ). Nesse caso, o comando seria mv "$1"* "$1" (sem o _ ), e, se você fez 1 primeiro, então o comando mv 1* 1 varreria todos os arquivos 10quick* , 11brown* , 12fox* , etc. junto com os arquivos 1foo* . Mas

seq 1 15 | xargs -I@ sh -c 'mv "$1"_* "$1"' x-sh @

deve ser seguro.

P.P.S. O comando seq não é especificado pelo POSIX. Nem a expansão de contraventamento no shell. Uma solução compatível com POSIX pode ser construída combinando a resposta de Grawity a essa pergunta com esta outra resposta por Adam Katz :

i=1
while [ "$i" -le 15 ]
do
    mv "${i}"_* "$i"
    i=$((i+1))
done
    
por 13.12.2012 / 20:39
6

Apenas não use xargs para isso. Use um loop for :

for i in $(seq 1 15); do
    mv ${i}_* $i
done

Melhor ainda é usar a expansão de chaves em vez de seq :

mkdir {1..15}

for i in {1..15}; do
    mv ${i}_* $i
done
    
por 13.12.2012 / 20:39