В чем разница между `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


1
Итак, есть ли реальная разница?
— Сарке

@Sarke Да, теоретически, как я уже говорил, но практически не заметно. (Будет видимый эффект, если вы оставите команду подстановки без кавычек.)
— Heemayl

1
@ Сарке, если сценарии не загружены полностью, вы не обязательно заметите это с помощью труб. Процесс, к которому можно подключиться, может игнорировать этот сигнал.
— Янус Троелсен

@JanusTroelsen что ты имеешь в виду под не загружен полностью? Почему это произойдет, за исключением ошибки сервера, и в этом случае ничего не будет передано sh.
— Hasufell

Или прерывание соединения ... есть так много способов, которыми передача может потерпеть неудачу
— Янус Троелсен

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.