Eu sei que quando você tem problemas ao canalizar a saída de eg find ..
para xargs
devido a nomes de arquivo 'estranhos', pode ajudar usar um delimitador específico (por exemplo, \0
) para realmente passar nomes de arquivo completos (por exemplo find foldername/ -type f -print0 | xargs -0 ...
, . E 'sempre' ajuda citar strings problemáticas para especificar seus limites.
Mas e se essas strings (nomes de arquivo) contiverem caracteres sensíveis ao bash como '
, "
, `
, (
,.. ? e você quiser usar uma string duas vezes (ou seja, tiver que usar sh -c ..
e precisar injetar essas strings em comandos aninhados (ou seja, $(CMD)
)?
Por exemplo:
# create a file which should not exist but does
mkdir remove_afterwards
touch remove_afterwards/"some [\"strange\"] ('file')"
# this will work
find remove_afterwards/ -type f -print0 | xargs -0 -I{} sh -c 'echo "{}"'
# but this won't due to the nested command
find remove_afterwards/ -type f -print0 | xargs -0 -I{} sh -c 'echo "{}" $(stat -c "%s" "{}")'
stat: cannot statx 'remove_afterwards/some [strange] ('\''file'\'')': No such file or directory
remove_afterwards/some [strange] ('file')
Ainda não descobri como escapar do segundo {}
interior $()
.
Existe uma maneira?
É verdade que um script real com funções tornaria tudo isso mais fácil de escrever e ler, mas estou curioso e ter uma frase tão curta na história vale o esforço de escrever esta pergunta.
Análise
O problema surge porque você passa o caminho (da expansão de
{}
) dentro do código shell que vocêsh -c
obtém.sh
não sabe{}
que estava lá, ele obtém o argumento{}
já expandido (porxargs
), ele interpreta a string inteira como código shell .Ao incorporar
{}
em código shell, não há uma maneira firme de citar para o que ele se expande. Quero dizer: o que quer que você pense para fazer nomes de caminho estranhos funcionarem ("{}"
,'{}'
etc.), eu sempre posso inventar um nome de caminho que quebrará seu código específico; não apenas quebrará seu código, mas também injetará meu código. Por exemplo, seu snippet que "funciona":pode ser explorado criando um arquivo literalmente chamado
$(beep)
(o comando para criar tal arquivo serátouch 'remove_afterwards/$(beep)'
). Provavelmentebeep
é inofensivo, eu poderia ter usadoreboot
orrm something
embora.Solução
A maneira segura é passar o expandido
{}
como um argumento separado que se tornará um parâmetro posicional para seush -c
:(
find-sh
é explicado aqui: Qual é o segundo sh emsh -c 'some shell code' sh
? )Explicação
Na solução, o código que nossas
sh -c
interpretações não contêm o que{}
acabou de ser expandido. O código é totalmente estático, ou seja, conhecido de antemão, é exatamente o que está dentro das aspas simples. O expandido{}
é passadosh
como um parâmetro posicional,sh
sabe que não é código. Onde quer que você precise do valor dentro do código do shell, use$1
. É importante fazer$
aspas simples ou escape para o shell externo, mas depois$1
deve ser corretamente aspas para o shell interno (observe que o shell interno verá aspas e barras invertidas que permanecem depois que o shell externo remove aspas e barras invertidas que considera especiais). O ponto ésh
que se expandirá$1
por conta própria, ainda sabendo que o valor expandido não é código do shell.Imagem mais ampla
O mesmo problema (com a mesma solução) existe no caso de
find -exec
. Compare É possível usarfind -exec sh -c
com segurança?Em geral, você deve sempre preferir código shell estático (se possível*), a menos que a parte variável seja higienizada ou deliberadamente projetada para ser interpretada como código shell. Isso se aplica sempre que você for tentado a incorporar no código shell qualquer coisa expandida "fora" do shell que irá interpretar o código. Não precisa ser
{}
; por exemplo, pode ser uma variável externa que você deseja usar de dentrosh -c
:* Construir código shell estático nem sempre é possível. Por exemplo, um comando executado via
ssh
é sempre uma única string a ser interpretada pelo shell remoto como código shell; então, se você precisa passar o valor de uma variável local, não há outra maneira senão incorporá-la no código shell. Ainda assim, há maneiras de fazer isso direito (para Bash: veja${parameter@operator}
quando o operador éQ
).Dica
Se você escolher construir código shell estático para
sh -c
, aconselho colocar o código inteiro entre aspas simples. O único caractere "problemático" será a própria aspa simples (caso você precise que ela pertença ao argumento do código shell). Você pode achar isto útil: Como posso colocar aspas simples ou escapar a linha de comando inteira no Bash convenientemente?