Прямой виновник - $phrase в одинарных кавычках. Это не единственная проблема.
Что просходит
Это соответствующий код (обратите внимание, я использую многоточие … для наименее интересной части; такая строка предназначена для понимания людьми, а не выполняется непосредственно в оболочке):
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \;
Оболочка, которая интерпретирует скрипт, содержит значение переменной phrase ; скажем, значение является volym . В приведенной выше команде все, что находится в одинарных кавычках, остается нетронутым, потому что именно так работает одинарное цитирование; так что $phrase еще не расширена. Оболочка только разбирает \ что сообщает ей следующее ; не предназначен для разделения команд, он должен рассматриваться как аргумент командной строки для find .
Когда запускается утилита find , это то, что она видит в качестве аргументов (начиная с 0-го, т. Е. Сама find ; один аргумент в строке, кроме … который обозначает несколько менее интересных аргументов):
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"
;
Обратите внимание, что последняя, но одна строка - это один длинный аргумент.
Давайте предположим, что foo.pdf найден и -exec выполнит свою работу. Все аргументы между -exec и ; стать новой командой после замены {} на foo.pdf . Новая команда будет (опять же, начиная с 0-го аргумента; один аргумент в строке):
sh
-c
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase"
Таким образом, sh запускает, он получает -c и поэтому знает, что следующий аргумент должен быть выполнен так, как если бы он был напечатан в командной строке:
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase"
В этот момент $phrase расширена. Он расширяется до нуля (последнее слово становится ""), потому что он не был установлен в этой оболочке. Это расширится до volym если вы экспортируете переменную в своем скрипте; но ты не сделал. Я не экспортировал бы все же; по моему мнению, в этом случае экспорт будет излишне загрязнять окружающую среду.
Решение? Еще нет
Ввод $phrase за пределы одинарных кавычек кажется хорошей идеей. Это будет работать в некоторых случаях. Самый наивный подход:
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'$phrase'"' \;
Это некорректно. Фраза " ; -exec rm "{} - это аргументы, которые увидит наша find :
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color ""
;
-exec
rm
"{}"
;
Ваши PDF-файлы исчезли. Искусственный пример? Может быть. Даже если вы единственный, кто использует скрипт, такая уязвимость внедрения кода ничего хорошего не дает.
Это было потому, что $phrase вообще не цитировали. Вы, вероятно, знаете, что почти всегда следует ставить переменные в двойных кавычках. Давай сделаем это. Улучшенный подход:
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'"$phrase"'"' \;
С фразой " ; -exec rm "{} эта find будет выглядеть так:
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color "" ; -exec rm "{}"
;
Выглядит несколько лучше; все еще несовершенен, потому что для foo.pdf sh попытается запустить:
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "" ; -exec rm "foo.pdf"
Последняя часть, скорее всего, выдаст ошибку, потому что нет команды -exec . Что если фраза была " ; rm "{}? Что если это было " ; rm -rf ~/" .
Есть больше. Пусть фраза будет volym (вполне безопасна), но назовите один из ваших PDF-файлов "; rm -rf ~ #.pdf (это возможно в нескольких файловых системах, включая семейство ext). После замены {} -s sh запустит что-то вроде этого:
pdftotext "/home/ad0x/…/"; rm -rf ~ #.pdf" - | grep …
Я предполагаю, что pdftotext потерпит неудачу (не имеет значения); тогда ваши файлы исчезли; затем # начинает комментарий, что угодно.
Решение
Это правильный путь , чтобы передать ваши {} и $phrase sh безопасно:
find … -exec sh -c 'pdftotext "$1" - | grep --with-filename --label="$1" --color "$2"' dummy {} "$phrase" \;
Когда этот sh выполняет заданную командную строку, $1 расширяется до любой find заменяющей {} , $2 расширяется до любой исходной оболочки, заменяющей $phrase . В контексте sh эти параметры правильно указаны, поэтому вы больше не можете вводить код. (Этот другой мой ответ объясняет dummy).
Даже сейчас есть возможности для улучшения. Что если фраза была -f? Часть grep конечном итоге будет:
grep --with-filename --label="…" --color "-f"
было бы жаловаться на отсутствующий аргумент. Используйте -- чтобы указать конец опций; -f после -- не будет рассматриваться как вариант. То же самое относится и к pdftotext (хотя в вашем конкретном случае каждый путь к PDF должен начинаться с /home поэтому его нельзя интерпретировать как параметр; но в целом $1 может расшириться до строки, которая выглядит как параметр). Наш вызов sh уже защищен, потому что sh принимает параметры перед командной строкой, и наша командная строка не может быть ошибочно принята за параметр (все же sh -c -- 'pdftotext …' … принесет вреда). Более надежная команда:
find … -exec sh -c 'pdftotext -- "$1" - | grep --with-filename --label="$1" --color -- "$2"' dummy {} "$phrase" \;