Что такое второй sh в sh -c некоторого шелл-кода sh?
Вопрос
Я обнаружил следующий фрагмент:
sh -c 'some shell code' sh …
(где
… обозначает ноль или более дополнительных аргументов).
Я знаю первый
shэто команда. Я знаю
sh -c должен выполнять предоставленный код оболочки (т.е.
some shell code). Какова цель второго
sh?
Отказ от ответственности
Подобный или связанный вопрос иногда появляется в качестве дополнительного вопроса после
sh -cправильно используется в ответе, и спрашивающий (или другой пользователь) хочет подробно узнать, как работает ответ. Или это может быть часть более крупного вопроса типа "что делает этот код?". Цель текущего вопроса - дать канонический ответ ниже.
Основные вопросы, похожие или связанные вопросы, затронутые здесь:
- Какой второй
shвsh -c 'some shell code' sh …? - Какой второй
bashвbash -c 'some shell code' bash …? - Что такое
find-shвfind . -exec sh -c 'some shell code' find-sh {} \;? - Если
some shell codeбыл в сценарии оболочки, и мы позвонили./myscript foo …, тогдаfooбудет называться$1внутри скрипта. Ноsh -c 'some shell code' foo …(или жеbash -c …) относится кfooв виде$0. Почему несоответствие? Что не так с использованием
sh -c 'some shell code' foo …гдеfooтакое "случайный" аргумент? В частности:-
sh -c 'some shell code' "$variable" -
sh -c 'some shell code' "$@" -
find . -exec sh -c 'some shell code' {} \; -
find . -exec sh -c 'some shell code' {} +
Я имею в виду, что могу использовать
$0вместо$1внутриsome shell code, меня это не беспокоит. Что плохого может случиться?-
Некоторые из вышеперечисленных могут рассматриваться как дубликаты (возможно, межсайтовые дубликаты) существующих вопросов (например, этого). Тем не менее я не нашел вопроса / ответа, который был бы направлен на объяснение проблемы новичкам, которые хотят понять
sh -c …и его якобы бесполезный дополнительный аргумент, наблюдаемый в высококачественных ответах. Этот вопрос восполняет пробел.
1 ответ
Предварительное примечание
Это довольно необычно видеть
sh -c 'some shell code'вызывается непосредственно из оболочки. На практике, если вы находитесь в оболочке, вы, вероятно, предпочтете использовать ту же оболочку (или ее подоболочку) для выполнения
some shell code. Довольно часто можно увидеть
sh -c вызывается из другого инструмента, например
find -exec.
Тем не менее, большая часть этого ответа подробно описывает
sh -c представлена как отдельная команда (что и есть), потому что основная проблема зависит исключительно от
sh. Позже несколько примеров и подсказок используют
find где это кажется полезным и / или поучительным.
Основной ответ
Какой второй
shвsh -c 'some shell code' sh …?
Это произвольная строка. Его цель - предоставить содержательное имя для использования в предупреждениях и сообщениях об ошибках. Вот это
sh но это могло быть
foo,
shell1 или же
special purpose shell (правильные кавычки, чтобы включать пробелы).
Bash и другие совместимые с POSIX оболочки работают одинаково, когда дело доходит до
-c. Хотя я считаю документацию POSIX слишком формальной, чтобы ее цитировать здесь, отрывок из
man 1 bash довольно просто:
bash [options] [command_string | file]
-c
Если-cопция присутствует, то команды читаются из первого аргумента без опцииcommand_string. Если есть аргументы послеcommand_string, первый аргумент присваивается$0а любые оставшиеся аргументы присваиваются позиционным параметрам. Поручение$0устанавливает имя оболочки, которое используется в предупреждениях и сообщениях об ошибках.
В нашем случае
some shell code это
command_string и в этот второй
shэто "первый аргумент после". Он закреплен за
$0 в контексте
some shell code.
Таким образом, ошибка от
sh -c 'nonexistent-command' "special purpose shell" является:
special purpose shell: nonexistent-command: command not found
и вы сразу узнаете, из какой он оболочки. Это полезно, если у вас много
sh -cпризывы. "Первый аргумент после
command_string"может не поставляться вообще; в таком случае
sh (строка) будет присвоена
$0 если оболочка
sh,
bash если оболочка
bash. Следовательно, они эквивалентны:
sh -c 'some shell code' sh
sh -c 'some shell code'
Но если вам нужно передать хотя бы один аргумент после
some shell code (т.е., возможно, аргументы, которые следует назначить
$1,
$2,…), То нельзя пропустить тот, который будет назначен
$0.
Несоответствие?
Если
some shell codeбыл в сценарии оболочки, и мы позвонили./myscript foo …, тогдаfooбудет называться$1внутри скрипта. Ноsh -c 'some shell code' foo …(или жеbash -c …) относится кfooв виде$0. Почему несоответствие?
Оболочка, интерпретирующая сценарий, назначает имя сценария (например,
./myscript) к
$0. Затем имя будет использоваться в предупреждениях и сообщениях об ошибках. Обычно такое поведение совершенно нормально, и нет необходимости предоставлять
$0вручную. С другой стороны, с
sh -cнет сценария, из которого можно было бы получить имя. Тем не менее, какое-то значащее имя полезно, отсюда и способ его предоставления.
Расхождение исчезнет, если вы перестанете рассматривать первый аргумент после
some shell codeкак (своего рода) позиционный параметр для кода. Если
some shell code находится в сценарии с именем
myscript и ты звонишь
./myscript foo …, то эквивалентный код с
sh -c будет:
sh -c 'some shell code' ./myscript foo …
Вот
./myscriptэто просто строка, выглядит как путь, но этот путь может не существовать; Во-первых, строка может быть другой. Таким образом можно использовать тот же шелл-код. Оболочка назначит
foo к
$1в обоих случаях. Нет противоречий.
Подводные камни лечения
$0 нравиться
$1
Что не так в использовании
sh -c 'some shell code' foo …где foo - "случайный" аргумент? […] То есть я могу использовать$0вместо$1внутриsome shell code, меня это не беспокоит. Что плохого может случиться?
Во многих случаях это сработает. Однако есть аргументы против такого подхода.
Самая очевидная ошибка - вы можете получить вводящие в заблуждение предупреждения или ошибки от вызванной оболочки. Помните, они начнут с чего угодно
$0расширяется до в контексте оболочки. Рассмотрим этот фрагмент:sh -c 'eecho "$0"' foo # typo intendedОшибка:
foo: eecho: command not foundи вы можете спросить, если
fooрассматривался как команда. Это не так уж плохо, еслиfooжестко запрограммирован и уникален; по крайней мере, вы знаете, что ошибка как-то связана сfoo, поэтому он привлекает ваше внимание именно к этой строке кода. Бывает и хуже:# as regular user sh -c 'ls "$0" > "$1"/' "$HOME" "/root/foo"Выход:
/home/kamil: /root/foo: Permission deniedПервая реакция: что случилось с моим домашним каталогом? Другой пример:
find /etc/fs* -exec sh -c '<<EOF' {} \; # insane shell code intendedВозможный выход:
/etc/fstab: warning: here-document at line 0 delimited by end-of-file (wanted `EOF')Очень легко думать, что что-то не так с
/etc/fstab; или задаться вопросом, почему код хочет интерпретировать это как здесь-документ.Теперь запустите эти команды и посмотрите, насколько точны ошибки, когда мы даем значимые имена:
sh -c 'eecho "$1"' "shell with echo" foo # typo intended sh -c 'ls "$1" > "$2"/' my-special-shell "$HOME" "/root/foo" find /etc/fs* -exec sh -c '<<EOF' find-sh {} \; # insane shell code intendedsome shell codeне идентично тому, что было бы в сценарии. Это напрямую связано с указанным выше предполагаемым несоответствием. Это может не быть проблемой; все же на каком-то уровне шелл-фу вы можете оценить последовательность.Точно так же на каком-то уровне вы можете наслаждаться правильным написанием сценариев. Тогда, даже если вам удастся избежать неприятностей с помощью
$0, вы не сделаете этого, потому что это не должно работать.Если вы хотите передать более одного аргумента или если количество аргументов заранее не известно, и вам нужно обрабатывать их последовательно, используйте
$0для одного из них - плохая идея.$0по дизайну отличается от$1или же$2. Этот факт проявится, еслиsome shell codeиспользует одно или несколько из следующих:$#- Количество позиционных параметров не занимает$0во внимание, потому что$0не является позиционным параметром.$@или же$*-"$@"как"$1", "$2", …, здесь нет"$0"в этой последовательности.for f do(эквивалентноfor f in "$@"; do) -$0никогда не назначается$f.shift(shift [n]в общем) - Позиционные параметры сдвинуты,$0остается нетронутым.
В частности, рассмотрите этот сценарий:
Вы начинаете с такого кода:
find . -exec sh -c 'some shell code referring "$1"' find-sh {} \;Вы замечаете, что он работает
shна файл. Это неоптимально.Тебе известно
-exec … \;заменяет{}с одним именем файла, но-exec … {} +заменяет{}с возможно многими именами файлов. Вы пользуетесь преимуществом последнего и вводите цикл:find . -exec sh -c ' for f do some shell code referring "$f" done ' find-sh {} +
Такая оптимизация - дело хорошее. Но если начать с этого:
# not exactly right but you will get away with this find . -exec sh -c 'some shell code referring "$0"' {} \;и превратите его в это:
# flawed find . -exec sh -c ' for f do some shell code referring "$f" done ' {} +тогда вы введете ошибку: самый первый файл, полученный из расширения
{}не будет обработанsome shell code referring "$f". Запись-exec sh -c … {} +бежитshс максимально возможным количеством аргументов, но для этого есть ограничения, и если файлов много-много, то одинshне хватит, другойshпроцесс будет порожденfind(и, возможно, еще и еще, …). С каждымshвы пропустите (т.е. не обработаете) один файл.Чтобы проверить это на практике, замените строку
some shell code referringс участиемechoи запустите полученные фрагменты кода в каталоге с несколькими файлами. Последний фрагмент не печатается..Все это не означает, что вы не должны использовать
$0вsome shell codeвообще. Вы можете и должны использовать$0для вещей, для которых он был разработан. Например, если вы хотитеsome shell codeчтобы напечатать (настраиваемое) предупреждение или ошибку, затем начните сообщение с$0. Введите значимое имя послеsome shell codeи наслаждайтесь значимыми ошибками (если таковые имеются) вместо расплывчатых или вводящих в заблуждение.
Заключительные подсказки:
С участием
find … -exec sh -c …никогда не вставлять{}в коде оболочки.По тем же причинам
some shell codeне должны содержать фрагменты, расширенные текущей оболочкой, если вы действительно не знаете, что расширенные значения безопасны. Лучше всего заключать весь код в одинарные кавычки (как в приведенных выше примерах, всегда'some shell code') и передайте каждое нефиксированное значение как отдельный аргумент. Такой аргумент можно безопасно получить из позиционного параметра во внутренней оболочке. Экспорт переменных также безопасен. Запустите это и проанализируйте вывод каждогоsh -c …(желаемый результатfoo';date'):variable="foo';date'" # wrong sh -c "echo '$variable'" my-sh # right sh -c 'echo "$1"' my-sh "$variable" # also right export variable sh -c 'echo "$variable"' my-shЕсли ты бежишь
sh -c 'some shell code' …в оболочке оболочка удалит одинарные кавычки, заключающиеsome shell code; затем внутренняя оболочка (sh) будет разбиратьsome shell code. В этом контексте важно правильно цитировать. Вы можете найти это полезным: Расширение параметров и кавычки внутри кавычек.