Яка різниця між `curl | sh` і `sh -c“ $ (згортка) ”`?


23

Один простий спосіб установки Docker (наприклад):

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

Однак я також бачив деякі, які виглядають так (на прикладі Докера):

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, воно скоротить результат, що може призвести до непередбачуваних результатів.

Варіант труби не має цього обмеження. Кожен рядок виводу з curlкоманди буде виконуватися по bashмірі надходження з труби.

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


Цікаво, тому є різниця. Спасибі
Сарке

1
Якщо ви сумніваєтесь, використовуйте трубовий метод. Я не вказав перевагу у своїй відповіді, але я віддав би перевагу методу труби для такого типу використання, оскільки ви ніколи не знаєте, скільки даних надходить через трубу.
Грег Тарса

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 з curlбуде передано як STDIN sh(це те, що труба |, робить)

В той час як sh -c "$(curl -sSL https://get.docker.com/)":

  • Заміна команди,, $()буде виконана першою, тобто curlбуде запущена першою в підпакеті

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

  • sh -c (неінтерактивна оболонка без входу) виконає STDOUT з curl


1
Отже, чи є реальна різниця?
Сарк

@Sarke Так, теоретично, як я вже згадував, але практично важко помітний. (Буде помітний ефект, якщо ви залишите заміну команди без котирування.)
heemayl

1
@Sarke, якщо сценарії не завантажуються повністю, ви не обов'язково помічаєте це за допомогою трубопроводів. Процес, на який потрапляє, може ігнорувати цей сигнал.
Janus Troelsen

@JanusTroelsen, що ти маєш на увазі під завантаженням не повністю? Чому це станеться, за винятком помилки сервера, і в цьому випадку нічого не буде передано sh.
hasufell

Або переривання зв’язку ... Є так багато способів, коли передача може не вдатися
Янус Троельсен

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.