Назад | Перейти на главную страницу

/ bin / sh: разница между переменной и прямой командой

Представьте, что у меня есть следующий сценарий bash:

#/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

Вопрос - почему второй подход через переменную не работает? Как заставить работать?

Оболочка Борна (и снова Борна) имеет сложные правила от синтаксического анализа строки до выполнения команд.

Для упрощения сократим вашу прямую командную строку до

su -c 'whoami; ls'

и ваши "встроенные" в

MYVAR="su -c 'whoami; ls'"     
$MYVAR                   

В первом случае командная строка разделена на 3 лексемы (слова), потому что пробел является метасимволом, ограничивающим слова, а также потому, что кавычка экранирует метасимволы (поэтому пробел экранируется в кавычки). Перед выполнением команды применяется этап удаления кавычек, оставляя 3 слова без кавычек. Первое слово - это название команды su, а еще 2 -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 здесь переопределяется, чтобы исключить символ пробела из разбиения слов, задачу берет на себя недавно введенный символ двоеточия.

Как объяснил @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: Я пытаюсь поместить команду в переменную, но сложные случаи всегда терпят неудачу!