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