Предположите, что у меня есть следующий сценарий удара:
#/bin/sh
#next line will work correctly, outputting user name and current directory
start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls'
MYVAR="start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls'"
#next line will fail with following error:
#ls': -c: line 0: unexpected EOF while looking for matching `''
$MYVAR
Вопрос - почему второй подход через переменную не работает? Как заставить его работать?
Как объяснял @lled, проблема в том, что оболочка обрабатывает котировки перед расширением переменных, поэтому помещение котировок в переменные не делает ничего полезного. Но есть пара альтернатив, в зависимости от того, почему вы хотите сохранить команду (а не просто выполнять ее напрямую).
Если вы хотите определить команду только один раз, то используйте ее повторно, используйте функцию:
myfunc() {
start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls'
}
# ...
myfunc
Если вам нужно собрать/выбрать команду в одном месте, то используйте ее в другом месте, вы можете использовать массив bash для ее хранения. Храните каждое "слово" команды как элемент массива, и если вы правильно на него ссылаетесь, то эти слова будут сохранены:
myarray=(start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls')
# ...
"${myarray[@]}"
Здесь происходит то, что массив определяется как имеющий элементы "start-stop-daemon", "--start", "--exec", "/bin/su", "--", "root", "-c" и "whoami; ls". Одиночные кавычки не хранятся как часть массива, но имеют эффект, делающий "whoami; ls" единым элементом массива. Затем, при расширении массива, [@]
сообщает оболочке, что каждый элемент массива должен быть развернут в отдельное слово, а двойные кавычки вокруг него предотвращают какое-либо дополнительное разбиение слова на результирующие значения.
Дополнительную информацию (и еще пару опций) смотрите в BashFAQ #50:Я пытаюсь поместить команду в переменную, но в сложных случаях всегда происходит сбой!
Bourne (и снова Борн) shell имеет сложные правила от разбора строки до выполнения команды.
Для упрощения, давайте сократим вашу прямую командную строку до
su -c 'whoami; ls'
, а ваши "встроенные" - до
MYVAR="su -c 'whoami; ls'"
$MYVAR
В первом случае командная строка разбита на 3 лексемы (слова), потому что символ пробела является метахарактерным разделительным словом, а также потому что кавычки экранируют метахарактерные символы (таким образом, экранируется пробел в кавычки). Перед выполнением команды применяется этап удаления кавычек, оставляя 3 слова и без кавычек. Первым словом является имя команды su
, а двумя другими - -c
и whoami; ls
, параметры команды. Правильно.
Во втором случае вызов состоит из одного расширения параметра. Для некоторых расширений (в том числе и для расширения параметра) применяется разделение слов. Для этой цели используется специальная переменная с именем IFS, которая вычисляет слова из значения расширенного параметра. По умолчанию IFS состоит из пробела, символов табуляции и новой строки. Это означает, что каждый из этих символов используется для разбиения букв на слова. Здесь расширенное значение равно:
su -c 'whoami; ls'
и в итоге мы получаем 4 слова, а именно: su
, -c
, 'whoami;
и ls'
. Кроме того, этап удаления кавычек не происходит для расширенных значений параметров. Выдается команда (с именем команды su
и ее 3 аргументами), и вы получаете странное сообщение об ошибке.
Сложность этой ситуации заключается в том, что мы должны держать слово, содержащее пробел в качестве аргумента к su
, сам пробел подвергается этапу разбиения слова, и кавычки бесполезны в расширении параметра.
Что можно сделать, так это поиграть с переменной IFS. Одним из решений будет:
IFS=: MYVAR="su:-c:whoami; ls"
$MYVAR
IFS здесь переопределена, чтобы исключить символ пробела из разделения слов, задача принимается за вновь введенный символ двоеточия.
.