find и grep выдают разные выходные данные, если выполняются из файла скрипта или командной строки

367
ad0x

Я использую эти команды для поиска в нескольких PDF-файлах, учитывая путь к файлу:

>>find /home/ad0x/Documents/Skola/Flervariabel/Tentor -name '*.pdf' -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "phrase"' \;

Где phraseтермин, который вы хотите найти в PDF-файлах. Это работает как ожидалось. Я получаю все вхождения слова "волым". Output in terminal

Когда я пытаюсь сделать то же самое в сценарии .sh (search.sh)

#!/bin/bash read -p "Enter term to search for: " phrase find /home/ad0x/Documents/Skola/Flervariabel/Tentor -name '*.pdf' -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \; echo "Search completed" 

 >>./search.sh >>Enter term to search for:volym 

Он выводит каждую строку в каждом файле PDF. Выход: outputs every line in every pdf

Я подозреваю, что это как-то связано с тем, как readинтерпретировать ввод, но я не нашел решения моей проблемы онлайн.

-1
Изменил его на одинарные кавычки, и теперь это работает! Спасибо! ad0x 5 лет назад 0

1 ответ на вопрос

2
Kamil Maciorowski

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

Похожие вопросы