Existem alguns problemas.
>>
no seu primeiro comando será interpretado pelo seu shell atual como um redirecionamento para um arquivo literalmente chamado {}
, a menos que seja citado.
*.ovpn
pode ser expandido pela globalização do shell antes que find
seja executado. Isso acontecerá se você tiver pelo menos um objeto no diretório atual que corresponda ao padrão. Você quer citar isso. Compare esta questão .
Você recebe Can't open echo
porque, de fato, está dizendo sh
para abrir echo
. Para executar um comando, você precisa de sh -c
.
find
sem especificar o caminho não é portátil (compare esta questão ). Embora você possa se safar disso, mencionei o problema para tornar a resposta mais útil para outros usuários.
Esta é a versão melhorada do seu primeiro comando que kinda funciona (não o rode, continue lendo):
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "{}"' \;
Observe que eu tive que citar duas vezes {}
entre aspas simples. Essas aspas duplas são "vistas" pelo sh
e criam nomes de arquivos com espaços, etc. funcionam como alvos de redirecionamento. Sem aspas, você pode acabar com echo "line to append" >> foo bar.ovpn
, o que equivale a echo "line to append" bar.ovpn >> foo
. A cotação torna echo "line to append" >> "foo bar.ovpn"
.
Infelizmente, nomes de arquivos contendo "
quebrarão essa sintaxe.
O caminho certo para passar {}
para sh
não é incluí-lo na cadeia de comando, mas passar seu conteúdo como um argumento separado:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$0"' {} \;
$0
dentro da cadeia de comando se expande para o primeiro argumento nosso sh
fica depois de -c '…'
. Agora, mesmo "
no nome do arquivo não quebrará a sintaxe.
Geralmente (como em um script) para se referir ao primeiro argumento que você usa $1
. Esta é a razão pela qual alguns usuários preferem usar um argumento fictício para ser $0
, assim:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$1"' dummy {} \;
Se fosse um script, $0
expandiria para o nome dele. É por isso que não é incomum ver isso dummy
sendo sh
(ou bash
, se alguém chamar bash -c …
etc.):
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$1"' sh {} \;
Mas espere! find
chama um sh
separado para cada arquivo. Eu não espero que você tenha milhares de arquivos .ovpn
, mas em geral você pode querer processar muitos arquivos sem gerar processos desnecessários. Podemos otimizar a abordagem com tee -a
que pode gravar em vários arquivos como um único processo:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" | tee -a "$@" >/dev/null' sh {} +
Observe {} +
, isso passa por vários caminhos de uma só vez. Dentro do comando executado por sh -c
, nós os recuperamos com "$@"
, que se expande para "$1" "$2" "$3" …
. Neste caso, um argumento fictício que preencha (não utilizado) $0
é uma obrigação.
Em geral, há também este problema: Por que printf
é melhor que echo
? No entanto, neste caso, você está usando echo
sem opções e a string que obtém é estática, então deve ficar bem.