21

One easy install method for Docker (for example) is this:

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

However, I have also seen some that look like this (using the Docker example):

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

They appear to be functionally the same, but is there a reason to use one over the other? Or is it just a preference/aesthetic thing?

(Of note, be very careful when running script from unknown origins.)

migrated from serverfault.com Jan 22 '17 at 3:51

This question came from our site for system and network administrators.

36

There is a practical difference.

curl -sSL https://get.docker.com/ | sh starts curl and sh at the same time, connecting the output of curl with the input of sh. curl will carry out with the download (roughly) as fast as sh can run the script. The server can detect the irregularities in the timing and inject malicious code not visible when simply downloading the resource into a file or buffer or when viewing it in a browser.

In sh -c "$(curl -sSL https://get.docker.com/)", curl is run strictly before the sh is run. The whole contents of the resource are downloaded and passed to your shell before the sh is started. Your shell only starts sh when curl has exited, and passes the text of the resource to it. The server cannot detect the sh call; it is only started after the connection ends. It is similar to downloading the script into a file first.

(This may not relevant in the docker case, but it may be a problem in general and highlights a practical difference between the two commands.)

  • 2
    Thanks, this seems like the most important difference between the two. – Sarke Jan 22 '17 at 10:31
  • Could you cite a resource backing up this claim please? I'd be very interested in knowing how the server can detect the 'sh' call. – Alfred Armstrong Jan 23 '17 at 9:32
  • 1
    @AlfredArmstrong Uhm, I put a link on the vulnerable to server-side detection phrase. It leads to a blog post which explains in great detail how they achieve it. TL;DR: put a sleep in your script and observe the delay in reception on the server. – Jonas Schäfer Jan 23 '17 at 9:47
  • 1
    @JonasWielicki thanks - the link wasn't very clear - not your fault, down to SE's CSS I think. People are brilliantly sneaky, aren't they? :) – Alfred Armstrong Jan 23 '17 at 10:10
  • 1
    All of which leads me to wonder if anyone has ever tried to do that trick in reality without getting immediately caught. Not that it probably matters much since you could probably just have the script do something malicious even without such tricks, and anyone who doesn't read it in full would be vulnerable. – ilkkachu Sep 29 '18 at 11:59
10

I believe that they are practically identical. However, there are rare cases where they are different.

$(cmd) gets substituted with the results of cmd. Should the length of that result command exceeds the maximum argument length value returned by getconf ARG_MAX, it will truncate the result, which may result in unpredictable results.

The pipe option does not have this limitation. Each line of output from the curl command will be executed by bash as it arrives from the pipe.

But ARG_MAX is usually in the 256,000 character range. For a docker install, I'd be confident using either method. :-)

  • Interesting, so there is a difference. Thanks – Sarke Jan 22 '17 at 4:17
  • 1
    When in doubt, use the pipe method. I didn't specify a preference in my answer, but I would prefer the pipe method for this kind of use because you never know how much data is coming through the pipe. – Greg Tarsa Jan 22 '17 at 4:20
  • 2
    "it will truncate the result" -- The shell should issue an error message for that, not silently truncate. When testing, I even get an error from the shell far below ARG_MAX, bash limits an individual argument to 131072 bytes on my system, when getconf ARG_MAX prints 2097152. But either way, error or truncation, it wouldn't work. – hvd Jan 22 '17 at 11:50
  • But old implementations of Bourne shell had much lower limits. In 4.2BSD the limit was 10240 characters, and on earlier systems it was even lower.. Of course that was 30 years ago, so you're unlikely to encounter such low limits today. If I recall correctly, some of these early shells did just silently truncate. – AndyB Jan 23 '17 at 4:05
  • The 128 kB limit for a single argument is a Linux thing, it's not about Bash. – ilkkachu Sep 29 '18 at 11:51
8

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

  • Both commands, curl and sh, will start at the same time, in respective subshells

  • The STDOUT from curl will be passed as the STDIN to sh (this is what pipe, |, does)

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

  • The command substitution, $(), will be executed first i.e. curl will be run first in a subshell

  • The command substitution, $(), will be replaced by the STDOUT from curl

  • sh -c (non-interactive, non-login shell) will execute the STDOUT from curl

  • 1
    So, is there any real difference? – Sarke Jan 22 '17 at 4:04
  • @Sarke Yes, theoretically like i mentioned, but practically hardly noticeable. (There would be visible effect if you leave the command substitution unquoted.) – heemayl Jan 22 '17 at 4:39
  • 1
    @Sarke, if the scripts is not downloaded completely, you wouldn't necessarily notice it with piping. The process getting piped to can ignore that signal. – Janus Troelsen Jan 22 '17 at 21:56
  • @JanusTroelsen what do you mean with not downloaded completely? Why would that happen except for a server error in which case there won't be anything piped to sh. – hasufell Jan 4 at 8:45
  • Or connection interruption... there are so many ways a transfer can fail – Janus Troelsen Jan 8 at 22:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.