Manipular o nome do arquivo canalizado do comando find

10

Sou relativamente novo no Bash e estou tentando fazer algo que na superfície parecia bastante simples - execute a busca por uma hierarquia de diretórios para obter todos os arquivos * .wma, canalize essa saída para um comando em que os converto para mp3 e salve o arquivo convertido como .mp3. Meu pensamento era que o comando deveria ter a seguinte aparência (deixei de fora o comando de conversão de áudio e, em vez disso, estou usando echo para ilustração):

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

Pelo que entendi, o argumento -print0 permitirá que eu lide com nomes de arquivos com espaços (o que muitos deles fazem como arquivos de música). Estou esperando (como resultado do xargs) que cada caminho de arquivo encontrado seja capturado em f, e que usando a substring corresponda / exclua do final da string, que eu deva ecoar o caminho original do arquivo com um mp3 extensão em vez de wma. No entanto, em vez deste resultado, estou vendo o seguinte:

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

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

Howard Dierking
fonte
1
Não há nenhuma razão para usar xargscom find. Ele vem com uma -execopção. Você pode simplesmente adicionar o comando que você usará à sua pergunta e alguém pode mostrar o findcomando correto ?
Ixtmixilix
sim (como observei abaixo), desde que eu ainda posso fazer a manipulação de strings em cada resultado do comando find (por exemplo, o {}membro)
Howard Dierking
na verdade, existem casos extremos onde xargsé mais adequado do que exec. Consulte este stackpost stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs para um caso em questão.
D34dh0r53
@ d34dh0r53 Que caso extremo? O tópico ao qual você vincula não aponta nenhum.
Gilles 'SO- stop be evil'

Respostas:

8

Como outras respostas já identificaram, ${f%.*}é expandida pelo shell antes de executar o xargscomando. Você precisa que essa expansão ocorra uma vez para cada nome de arquivo, com a variável shell fdefinida como o nome do arquivo (a passagem -I fnão faz isso: xargsnão tem noção de variável do shell, ela procura a string fno comando, portanto, se você usado, por exemplo xargs -I e echo …, teria executado comandos como ./somedir/somefile.wmacho .mp3).

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

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

O argumento _está $0no shell; os nomes dos arquivos são passados ​​como argumentos posicionais que se for f; do …repetem. Uma versão mais simples deste comando executa um shell separado para cada arquivo, que é equivalente, mas um pouco mais lento:

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

Você realmente não precisa usar findaqui, supondo que você esteja executando um shell razoavelmente recente (ksh93, bash ≥4.0 ou zsh). No bash, coloque shopt -s globstarno seu .bashrcpara ativar o **/padrão glob para se repetir em subdiretórios (em ksh, isso é 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" ] || continueno início do loop.)

Gilles 'SO- parar de ser mau'
fonte
ótimas explicações, além de me apontar uma direção que eu nem tinha percebido (atualizando meu shell bash para obter a funcionalidade globstar) - no final, o globbing recursivo foi a solução que manteve o comando simples e realizou tudo o que eu estava tentando fazer . Obrigado!
Howard Dierking
6

No caso de sua avaliação da solução de $ {f%. *}. Mp3, é feita no shell em que você está invocando todo o comando, não no shell bifurcado pelo xargs . E no seu shell não há 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,'
0xFF
fonte