find exec '{}' não disponível depois

7

Exec permite passar todos os argumentos de uma vez com {} + ou passá-los um a um com {} \;

Agora digamos que eu queira renomear todos os jpeg , sem problemas ao fazer isso:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec mv '{}' '{}'.new \;

Mas se eu precisar redirecionar a saída, '{}' não estará acessível após o redirecionamento.

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjpeg -quality 80 '{}' > optimized_'{}' \;

Isso não funcionaria. Eu teria que usar um loop for, armazenando a saída do find em uma variável antes de usá-lo. Vamos admitir, é complicado.

for f in 'find . \( -name '*.jpg' -o -name '*.jpeg' \)'; do cjpeg -quality 80 $f > optimized_$f; done;

Então, existe uma maneira melhor?

    
por Buzut 14.11.2018 / 12:39

5 respostas

12

Você pode usar bash -c dentro do comando find -exec e usar o parâmetro posicional com o comando bash:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec bash -c 'cjpeg -quality 80 "$1" > "$(dirname "$1")/optimized_$(basename "$1")"' sh {} \;

Dessa forma, {} é fornecido com $1 .

O sh antes de o {} informar ao shell interno seu "nome", a string usada aqui é usada em, e. mensagens de erro. Isso é discutido mais em esta resposta em stackoverflow .

    
por 14.11.2018 / 12:49
8

Você tem uma resposta ( link ), mas eis o motivo.

O redirecionamento > e também os pipes | e $ expansion são feitos pelo shell antes do comando ser executado. Portanto, a stdout é redirecionada para optimized_{} , antes que find seja iniciado.

    
por 14.11.2018 / 14:09
3

O redirecionamento precisa ser citado para evitar que o presente shell o interprete.
Mas citando isso também evitará que a saída do comando seja redirecionada.
A solução conhecida para isso é chamar um shell:

find . -name '*.jpg' -exec sh -c 'echo "$1" >"$1".new' called_shell '{}' \;

Nesse caso, o redirecionamento ( > ) é citado no shell atual e funciona corretamente dentro do shell chamado. O called_shell é usado como o parâmetro $0 (o nome) do shell filho ( sh ).

Isso funciona bem se um sufixo é adicionado ao nome do arquivo, mas não se você usar um prefixo. Para que um prefixo funcione, você precisa remover os ./ encontrados antes dos nomes dos arquivos com ${1#./} e usar a opção -execdir .

Você pode (ou não) querer usar a opção -iname para que os arquivos chamados *.JPG ou *.JpG ou outras variações também sejam incluídos.

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     cjpeg -quality 80 "$1" > optimized_"${1#./}"
     ' called_shell '{}' \;

E você pode (ou não) também querer chamar o shell uma vez por diretório, em vez de uma vez por arquivo, adicionando um loop ( for f do … ; done ) e um + no final:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" > optimized_"${f#./}"; done
     ' called_shell '{}' \+

E, finalmente, como cjpeg é capaz de gravar diretamente em um arquivo, o redirecionamento pode ser evitado como:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" -outfile optimized_"${f#./}"; done
     ' called_shell '{}' \+
    
por 14.11.2018 / 16:23
3

cjpeg tem uma opção que permite gravar em um arquivo nomeado, em vez de uma saída padrão. Se a sua versão de find suportar a opção -execdir , você poderá tirar proveito disso para tornar o redirecionamento desnecessário.

find . \( -name '*.jpg' -o -name '*.jpeg' \) \
  -execdir cjpeg -quality 80 -outfile optimized_'{}' '{}' \;

Nota: isso na verdade assume a versão BSD de find , que parece remover o ./ do nome do arquivo ao exportar para {} . (Ou, inversamente, o GNU find adiciona ./ ao nome. Não há nenhum padrão para dizer qual comportamento está "certo".)

    
por 14.11.2018 / 15:55
1

Crie um script cjq80:

#!/bin/bash
cjpeg -quality 80 "$1" > "${1%/*}"/optimized_"${1##*/}"

Torne-o executável

chmod u+x cjq80

E use-o em -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
    
por 14.11.2018 / 12:51

Tags