Почему оболочка игнорирует кавычки в аргументах, передаваемых ей через переменные?
Это работает как рекламируется:
# example 1
#!/bin/bash
grep -ir 'hello world' .
Это не:
# example 2
#!/bin/bash
argumentString="-ir 'hello world'"
grep $argumentString .
Несмотря на 'hello world'
заключенный в кавычки во втором примере, grep интерпретирует 'hello
в качестве одного аргумента и world'
как другой, что означает, что в этом случае 'hello
будет шаблон поиска и world'
будет путь поиска.
Опять же, это происходит только тогда, когда аргументы раскрываются из argumentString
переменная. grep правильно интерпретирует 'hello world'
в качестве одного аргумента в первом примере.
Кто-нибудь может объяснить, почему это так? Есть ли правильный способ расширения строковой переменной, которая сохранит синтаксис каждого символа так, чтобы он правильно интерпретировался командами оболочки?
4 ответа
Зачем
Когда строка раскрывается, она разбивается на слова, но она не переоценивается, чтобы найти специальные символы, такие как кавычки или знаки доллара или... Это способ, которым оболочка "всегда" вела себя, так как оболочка Bourne обратно в 1978 году или около того.
исправлять
В bash
используйте массив для хранения аргументов:
argumentArray=(-ir 'hello world')
grep "${argumentArray[@]}" .
Или, если смелый / безрассудный, используйте eval
:
argumentString="-ir 'hello world'"
eval "grep $argumentString ."
С другой стороны, усмотрение часто является лучшей частью доблести, и работа с eval
это место, где усмотрение лучше, чем храбрость. Если вы не полностью контролируете строку, которая eval
d (если в командной строке есть пользовательский ввод, который не был строго проверен), то вы открываете себя для потенциально серьезных проблем.
Обратите внимание, что последовательность расширений для Bash описана в разделе Расширения оболочки в руководстве по GNU Bash. Обратите внимание на отдельные разделы 3.5.3 Расширение параметров оболочки, 3.5.7 Разделение слов и 3.5.9 Удаление кавычек.
Когда вы помещаете символы кавычек в переменные, они просто становятся обычными литералами (см. Http://mywiki.wooledge.org/BashFAQ/050; спасибо @tripleee за указание на эту ссылку)
Вместо этого попробуйте использовать массив для передачи аргументов:
argumentString=(-ir 'hello world')
grep "${argumentString[@]}" .
Рассматривая этот и другие вопросы, я удивляюсь, что никто не поднял вопрос об использовании явной подоболочки. Для bash и других современных оболочек вы можете явно выполнить командную строку. В bash требуется опция -c.
argumentString="-ir 'hello world'"
bash -c "grep $argumentString ."
Работает точно так же, как желаемый оригинальный вопросник. Есть два ограничения на эту технику:
- Вы можете использовать только одинарные кавычки в строке команды или аргумента.
- Команде будут доступны только экспортированные переменные среды
Кроме того, этот метод обрабатывает перенаправление и конвейерную обработку, а также работают другие оболочки. Вы также можете использовать внутренние команды bash, а также любые другие команды, работающие в командной строке, потому что вы по сути просите bash subshell интерпретировать его непосредственно как командную строку. Вот более сложный пример, несколько более сложный вариант ls -l.
cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'"
bash -c "$cmd"
Я построил командные процессоры как таким образом, так и с массивами параметров. Как правило, этот способ гораздо проще написать и отладить, и просто отобразить команду, которую вы выполняете. OTOH, массивы param работают хорошо, когда у вас действительно есть абстрактные массивы параметров, в отличие от простого простого варианта команды.
Внутри двойных кавычек одиночные кавычки теряют свое особое значение, поэтому они являются обычными символами. Чтобы это работало, используйте что-то вроде eval
:
eval grep $argumentString .
который должен правильно собрать командную строку из объединенных строк.