encontre exec '{}' indisponível após>

8

O Exec nos permite passar todos os argumentos de uma só vez {} +ou passá-los um por um com{} \;

Agora, digamos que eu queira renomear todos os jpeg , não há problema em 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 precisaria usar um loop for, armazenando a saída do find em uma variável antes de usá-la. 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?

Buzut
fonte
Não há uma >falta no terceiro exemplo de código?
#
1
Até a sua primeira linha é fora do padrão e, portanto, não é portátil. Tente evitar linhas de comando onde {}aparecem em cadeias mais longas, pois essas cadeias geralmente não são expandidas.
schily
Esse primeiro exemplo não faz o que você diz.
ctrl-alt-Delor
1
Você corrigiu sua pergunta: eu não a alteraria mais.
Ctrl-alt-delor
1
Pelo que vale, o principal motivo pelo qual o redirecionamento não funciona como você deseja é que ele é tratado pelo shell a partir do qual você inicia find, uma vez, e aplicado ao findpróprio comando. O {}não tem significado especial nesse contexto. O redirecionamento não é um argumento finde certamente não faz parte da -execcláusula.
John Bollinger 14/11

Respostas:

13

Você pode usar bash -cdentro do find -execcomando 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 maneira {}é fornecida $1.

O shantes do {}diz ao shell interno seu "nome", a string usada aqui é usada em, por exemplo, mensagens de erro. Isso é discutido mais nesta resposta no stackoverflow .

oliv
fonte
8

Você tem uma resposta ( https://unix.stackexchange.com/a/481687/4778 ), mas aqui está o porquê.

O redirecionamento >, e também pipes |, e $expansão, são todos feitos pelo shell antes que o comando seja executado. Portanto, o stdout é redirecionado para optimized_{}, antes de findser iniciado.

ctrl-alt-delor
fonte
3

O redirecionamento precisa ser citado para evitar que o shell atual o interprete.
Mas citá-lo 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 $0parâmetro (o nome) do shell filho ( sh).

Isso funciona bem se um sufixo for adicionado ao nome do arquivo, mas não se você usar um prefixo. Para que um prefixo funcione, é necessário remover o ./que encontrar prefix para nomes de arquivos ${1#./}e usar a -execdiropção

Você pode (ou não pode) deseja usar a -inameopção para que os arquivos com o nome *.JPGou *.JpGou outras variações também estão incluídos.

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

E você também pode (ou não) 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 '{}' \+
Isaac
fonte
3

cjpegpossui uma opção que permite gravar em um arquivo nomeado, em vez da saída padrão. Se a sua versão do findsuporta a -execdiropção, você pode tirar vantagem disso para tornar o redirecionamento desnecessário.

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

Nota: na verdade, isso assume a versão BSD de find, que parece retirar a liderança ./do nome do arquivo ao expandir para {}. (Ou, inversamente, o GNU find adiciona ./ ao nome. Não há um padrão para dizer qual comportamento é "correto".)

chepner
fonte
Se o seu findsuporte -execdir, você pode usá-lo em vez de -exec. Isso faz com que o comando seja executado no diretório em que o arquivo foi encontrado e {}se tornará em aa.jpgvez de ./t2/aa.jpg.
chepner
Ainda em erro: cjpeg: can't open optimized_./aa.jpg.
Isaac Isaac
Hum, isso parece ser uma diferença entre GNU finde BSD find. (Os perigos de utilização de extensões não-padrão.)
chepner
Um nome de arquivo sem um líder ./parece ser mais propenso a erros. Uma solução razoável é proposta nesta resposta .
Isaac
1

Crie um script cjq80:

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

Torne executável

chmod u+x cjq80

E use-o em -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
choroba
fonte
Eu pensei sobre isso, mas não é muito útil. Especialmente porque eu tive que incorporar isso em um processo de construção
Buzut
Basta adicionar o script a outros scripts de compilação, acho mais legível do que o duplamente aninhado bash -c.
choroba
Bem, é uma questão de gosto. Agora, cada opção é especificada por isso, se alguém encontra o mesmo problema, ele vai escolher por si mesmo :)
Buzut