Прямой виновник - $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" \;