What's the difference between `curl | sh` and `sh -c "$(curl)"`?

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.)


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. :-)


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