В чем разница между `curl | sh` и `sh -c“ $ (curl) »`?

23

Например, один из простых способов установки Docker:

curl -sSL https://get.docker.com/ | sh

Тем не менее, я также видел некоторые из них, которые выглядят так (на примере Docker):

sh -c "$(curl -sSL https://get.docker.com/)"

Похоже, они функционально одинаковы, но есть ли причина использовать один над другим? Или это просто предпочтение / эстетическая вещь?

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

Сарке
источник

Ответы:

40

Есть практическая разница.

curl -sSL https://get.docker.com/ | shзапускается curlи shодновременно, соединяя выход curlс входом sh. curlбудет выполнять загрузку (примерно) так быстро, как shможет запустить скрипт. Сервер может обнаружить нарушения во времени и внедрить вредоносный код, невидимый при простой загрузке ресурса в файл или буфер или при просмотре его в браузере.

В sh -c "$(curl -sSL https://get.docker.com/)", curlзапускается строго до shзапуска. Все содержимое ресурса загружается и передается в вашу оболочку перед shзапуском. Ваша оболочка запускается только shпосле curlвыхода и передает ей текст ресурса. Сервер не может обнаружить shвызов; он запускается только после завершения соединения. Это похоже на загрузку сценария в файл первым.

(Это может не относиться к случаю докера, но это может быть проблемой в целом и подчеркивает практическую разницу между двумя командами.)

Йонас Шефер
источник
2
Спасибо, это кажется самым важным различием между ними.
Сарке
Не могли бы вы привести ресурс, подтверждающий это утверждение, пожалуйста? Мне было бы очень интересно узнать, как сервер может обнаружить вызов 'sh'.
Альфред Армстронг
1
@AlfredArmstrong Хм, я поставил ссылку на vulnerable to server-side detectionфразу. Это приводит к сообщению в блоге, которое объясняет очень подробно, как они достигают этого. TL; DR: поместите сон в ваш скрипт и наблюдайте за задержкой приема на сервере.
Йонас Шефер
1
@JonasWielicki спасибо - ссылка была не очень понятной - не ваша вина, я думаю, до CSS в SE. Люди блестяще подлые, не так ли? :)
Альфред Армстронг
1
Все это заставляет меня задуматься, пытался ли кто-нибудь когда-нибудь сделать этот трюк в реальности, не будучи сразу пойманным. Не то чтобы это, вероятно, имеет большое значение, так как вы могли бы просто заставить скрипт сделать что-то вредоносное даже без таких уловок, и любой, кто не прочитает его полностью, будет уязвим.
ilkkachu
11

Я считаю, что они практически идентичны. Однако есть редкие случаи, когда они разные.

$(cmd)заменяется результатами cmd. Если длина этой команды результата превысит значение максимальной длины аргумента, возвращаемое getconf ARG_MAX, она урезает результат, что может привести к непредсказуемым результатам.

Опция pipe не имеет этого ограничения. Каждая строка вывода curlкоманды будет выполнена по bashмере ее поступления из канала.

Но ARG_MAX обычно находится в диапазоне 256 000 символов. Для установки докера я был бы уверен, используя любой метод. :-)

Грег Тарса
источник
Интересно, так что разница есть. Спасибо
Сарке
1
Если есть сомнения, используйте метод трубы. Я не указал предпочтения в своем ответе, но я бы предпочел метод pipe для этого вида использования, потому что вы никогда не знаете, сколько данных поступает через канал.
Грег Тарса
2
«это будет усекать результат» - оболочка должна выдавать сообщение об ошибке, а не молча усекаться. При тестировании я даже получаю сообщение об ошибке из оболочки ARG_MAX, показанной ниже , bash ограничивает отдельный аргумент до 131072 байта в моей системе при getconf ARG_MAXпечати 2097152. Но в любом случае, ошибка или усечение, это не сработает.
HVd
Но старые реализации оболочки Bourne имели гораздо более низкие пределы. В 4.2BSD ограничение составляло 10240 символов, а в более ранних системах оно было даже ниже. Конечно, это было 30 лет назад, поэтому вы вряд ли встретите такие низкие пределы сегодня. Если я правильно помню, некоторые из этих ранних оболочек просто молча усеклись.
AndyB
Ограничение в 128 кБ для одного аргумента - вещь Linux, а не Bash.
ilkkachu
8

В curl -sSL https://get.docker.com/ | sh:

  • Обе команды, curlи sh, начнутся одновременно в соответствующих подоболочках

  • STDOUT from curlбудет передан как STDIN sh(это то |, что делает pipe, )

Тогда как в sh -c "$(curl -sSL https://get.docker.com/)":

  • Команда подстановки, $()будет выполнена первой, т.е. curlбудет выполнена первой в подоболочке

  • Подстановка команды $(), будет заменена на STDOUT изcurl

  • sh -c (неинтерактивная оболочка без входа в систему) выполнит STDOUT из curl

heemayl
источник
1
Итак, есть ли реальная разница?
Сарке
@Sarke Да, теоретически, как я уже говорил, но практически не заметно. (Будет видимый эффект, если вы оставите команду подстановки без кавычек.)
Heemayl
1
@ Сарке, если сценарии не загружены полностью, вы не обязательно заметите это с помощью труб. Процесс, к которому можно подключиться, может игнорировать этот сигнал.
Янус Троелсен
@JanusTroelsen что ты имеешь в виду под не загружен полностью? Почему это произойдет, за исключением ошибки сервера, и в этом случае ничего не будет передано sh.
Hasufell
Или прерывание соединения ... есть так много способов, которыми передача может потерпеть неудачу
Янус Троелсен
0

Одно из различий между ними (взятое из других ответов в Интернете) состоит в том, что если вы не загрузите весь сценарий сразу, он может обрезать сценарий на полпути в неизвестной точке и изменить значение команды на казнены. Так что, кажется, сначала лучше загрузить весь файл, а затем оценить его.

Лэнс Поллард
источник