Manipular o nome do arquivo canalizado do comando find

9

Sou relativamente novo no Bash e estou tentando fazer algo que na superfície parece bastante simples - execute o comando find em uma hierarquia de diretórios para obter todos os arquivos * .wma, canalizando para um comando onde os converto para mp3 e salve o arquivo convertido como .mp3. Meu pensamento era que o comando deve ser semelhante ao seguinte (deixei o comando de conversão de áudio e estou usando echo para ilustração):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

Pelo que entendi, o -print0 arg me permite lidar com nomes de arquivos que possuem espaços (que muitos deles fazem como arquivos de música). Espero então (como resultado de xargs) que cada caminho de arquivo de find seja capturado em f, e que usando a correspondência de substring / delete do final da string, que eu deveria estar ecoando o caminho do arquivo original com um mp3 extensão em vez de wma. No entanto, em vez desse resultado, vejo o seguinte:

*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...

Portanto, minha pergunta (além do específico 'o que estou fazendo errado aqui') é que os valores que são o resultado de uma operação de pipe precisam ser tratados de forma diferente nas operações de manipulação de string do que aqueles resultantes de uma atribuição variável?

    
por Howard Dierking 06.01.2013 / 00:49

2 respostas

8

Como outras respostas já foram identificadas, ${f%.*} é expandido pelo shell antes de executar o comando xargs . Você precisa que essa expansão aconteça uma vez para cada nome de arquivo, com a variável de shell f definida para o nome do arquivo (passando -I f não faz isso: xargs não tem noção de variável shell, ele procura pela string f no comando, por isso, se você usou, por exemplo, xargs -I e echo … , teria executado comandos como ./somedir/somefile.wmacho .mp3 ).

Mantendo essa abordagem, informe xargs para invocar um shell que possa executar a expansão. Melhor, diga que find - xargs é uma ferramenta amplamente obsoleta e é difícil de usar corretamente, já que as versões modernas do find possuem uma construção que faz o mesmo trabalho (e mais) com menos dificuldades de encanamento. Em vez de find … -print0 | xargs -0 command … , execute find … -exec command … {} + .

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

O argumento _ é $0 no shell; os nomes dos arquivos são passados como argumentos posicionais, em que for f; do … faz um loop. Uma versão mais simples desse comando executa um shell separado para cada arquivo, o que é equivalente, mas um pouco mais lento:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

Você não precisa usar find aqui, presumindo que esteja executando um shell razoavelmente recente (ksh93, bash ≥4.0 ou zsh). No bash, coloque shopt -s globstar no seu .bashrc para ativar o padrão **/ glob para recursar em subdiretórios (em ksh, que é set -o globstar ). Então você pode correr

for f in **/*.wma; do
  echo "${f%.*}.mp3"
done

(Se você tiver diretórios chamados *.wma , adicione [ -f "$f" ] || continue no início do loop).

    
por 06.01.2013 / 22:27
6

No caso de sua avaliação de solução de $ {f%. *}. mp3 é feito no shell que você está invocando o comando inteiro, não no shell bifurcado pelo xargs . E no seu shell não existe uma variável f , ela é substituída por uma string vazia.

Solução usando xargs com -I f :

% cat 1.sh 
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3

Mas eu faria assim:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
    
por 06.01.2013 / 02:16