Что такое второй 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 intended
some 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
. В этом контексте важно правильно цитировать. Вы можете найти это полезным: Расширение параметров и кавычки внутри кавычек.