O culpado direto é $phrase
entre aspas simples. Este não é o único problema.
O que acontece
Este é o código relevante (note que eu uso reticências …
para a parte menos interessante; essa linha deve ser entendida por humanos, e não diretamente executada em uma concha):
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \;
O shell que interpreta o script contém o valor da variável phrase
; digamos que o valor seja volym
. No comando acima, tudo o que está entre aspas simples é deixado intacto porque é assim que funciona a cotação única; então $phrase
ainda não está expandido. O shell só analisa \
, o que informa o seguinte: ;
não serve para separar comandos, ele deve ser tratado como um argumento de linha de comando para find
.
Quando o utilitário find
é executado, isso é o que ele vê como argumentos (a partir de 0º, ou seja, o find
; um argumento por linha, exceto …
que denota vários argumentos menos interessantes):
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"
;
Observe que a última, mas uma linha, é um argumento longo.
Suponhamos que foo.pdf
seja encontrado e -exec
faça seu trabalho. Todos os argumentos entre -exec
e ;
se tornam um novo comando depois que {}
é substituído por foo.pdf
. O novo comando será (novamente, a partir do 0º argumento; um argumento por linha):
sh
-c
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase"
Portanto, sh
é executado, obtém -c
e, portanto, sabe que o próximo argumento deve ser executado como se fosse digitado na linha de comando:
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase"
Este é o momento em que $phrase
é expandido. Ele se expande para nada (a última palavra se torna ""
) porque não foi definida neste shell. Expandiria para volym
se você exportasse a variável em seu script; mas você não fez. Eu não exportaria embora; na minha opinião, neste caso, a exportação poluiria desnecessariamente o meio ambiente.
Solução? Ainda não
Colocar $phrase
fora das aspas simples parece uma boa ideia. Isso funcionará em alguns casos. A abordagem mais ingênua:
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'$phrase'"' \;
É falho. Com a frase sendo " ; -exec rm "{}
, esses são argumentos que nosso find
verá:
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color ""
;
-exec
rm
"{}"
;
Seus PDFs sumiram. Exemplo artificial? Talvez. Mesmo se você for o único usando o script, essa vulnerabilidade de injeção de código não é nada boa.
Isso foi porque $phrase
não foi citado. Você provavelmente sabe que quase sempre deve colocar variáveis entre aspas duplas. Vamos fazer isso. Uma abordagem melhorada:
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'"$phrase"'"' \;
Com a frase sendo " ; -exec rm "{}
this find
verá:
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color "" ; -exec rm "{}"
;
Parece um pouco melhor; ainda com falhas, porque para foo.pdf
sh
tentará executar:
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "" ; -exec rm "foo.pdf"
A última parte provavelmente lançará um erro porque não há nenhum comando -exec
. E se a frase fosse " ; rm "{}
? E se fosse " ; rm -rf ~/"
.
Existe mais. Deixe a frase ser volym
(bastante seguro) mas nomeie um dos seus PDFs "; rm -rf ~ #.pdf
(isso é possível em alguns sistemas de arquivos, incluindo ext família). Depois que {}
-s for substituído, sh
executará algo assim:
pdftotext "/home/ad0x/…/"; rm -rf ~ #.pdf" - | grep …
Eu acho que pdftotext
irá falhar (irrelevante); então seus arquivos sumiram; então #
começa um comentário, qualquer que seja.
Solução
Este é o caminho certo para passar seu {}
e $phrase
para sh
com segurança :
find … -exec sh -c 'pdftotext "$1" - | grep --with-filename --label="$1" --color "$2"' dummy {} "$phrase" \;
Quando esse sh
executar a sequência de comandos especificada, $1
será expandido para qualquer find
substituído por {}
, $2
será expandido para qualquer que seja o shell original substituído por $phrase
. No contexto de sh
, esses parâmetros são citados corretamente, portanto, você não pode mais injetar código. ( Esta outra resposta minha explica dummy
).
Mesmo agora, há espaço para melhorias. E se a frase fosse -f
? A parte grep
acabaria sendo:
grep --with-filename --label="…" --color "-f"
reclamaria do argumento perdido. Use --
para indicar o final das opções; -f
após --
não será tratado como uma opção. O mesmo se aplica a pdftotext
(embora no seu caso particular todo caminho para PDF deva começar com /home
, então ele não pode ser interpretado como uma opção; mas em geral, $1
pode se expandir para uma string que parece uma opção). Nossa invocação sh
já está imune porque sh
toma opções antes de uma cadeia de comandos e nossa cadeia de comando não pode ser confundida com uma opção (ainda sh -c -- 'pdftotext …' …
não causará nenhum dano). Comando mais robusto:
find … -exec sh -c 'pdftotext -- "$1" - | grep --with-filename --label="$1" --color -- "$2"' dummy {} "$phrase" \;