I am trying to execute a script on a remote host through SSH. The script uses environment variables that are set on the local shell. The variables in the script are not expanded.

ssh user@host 'bash -s' < script.sh

Contents of script.sh:


docker pull "$IMAGE"

On the remote host this is executed as:

docker pull ""

As indicated by the error output:

Error parsing reference: "" is not a valid repository/tag: repository name must have at least one component

I've tried to use command substitution:

ssh user@host < $(cat script.sh)

Which leads to:

bash: line 95: $(cat script.sh): ambiguous redirect

How can I make the variables in script.sh expand before passing the commands over SSH?

  • 1
    Do you just need $IMAGE, or are there others that aren't apparent? – Jeff Schaller Jan 21 '19 at 20:38
  • There are others as well – Thomas Jan 21 '19 at 20:38
  • Are you able to enumerate them, or do you prefer to send every environmental variable over? – Jeff Schaller Jan 21 '19 at 20:39
  • The environment variables must all remain local, I'm not entirely sure what you mean by enumerating them in this context – Thomas Jan 21 '19 at 20:41
  • If there's just IMAGE, FOO, and BAR, that's one case; if there are dozens of others, that's the other case. – Jeff Schaller Jan 21 '19 at 20:45

You can in principle send environment variables to the remote over SSH, but at least OpenSSH disallows it on the server, and doesn't try to send any from the client. The relevant configuration options are AcceptEnv in sshd_config (server) and SendEnv in ssh_config (client).

Assuming you can't or don't want to change the configuration there, since you're sending a whole script to the stdin on the remote anyway, you could also stick the variable assignments in front of that script.

We can use export -p to print out all the exported variables in the current shell, in a format that the remote shell can use to assign them there. Then the remote can process the script as usual, with the variables set.

So with script.sh containing

echo "$FOO" "$HOSTNAME"

This would export FOO in the local shell, and then pass the definitions of all exported variables to remote, along with the script itself. The resulting output contains the value of FOO that was just set.

$ export FOO=abc123
$ (export -p; cat script.sh) | ssh user@remotehost  'bash -s' 
FOO: abc123 HOSTNAME: remotehost

(There's a slight problem with Bash here. In normal mode, its export -p prints out declare commands instead of the standard export, and those may not be supported by other shells. So if your local shell is Bash, and you'd start a standard sh on the remote, you'd get errors.)

| improve this answer | |
  • Thank you for the explanation, however, my intention is not to send the env vars to the remote host, they should remain local. What I am trying to send to the remote host are the commands from script.shwith the vars already expanded. Your second suggestion seems interesting, could we shape that in a way to achieve the aformentioned goal? (I already tried command substitution, but without invoking bash, would that make a difference?) – Thomas Jan 21 '19 at 22:08
  • @Thomas, mhh, does it matter which way you do it? If I didn't get this too wrong, that docker pull "$IMAGE" should work just as if with the local value of IMAGE if the export definitions are pushed to the remote. Pre-expanding the variables within the script text would be really problematic in the general case. Any quotes and other shell syntax characters within the values would take effect during parsing, which they don't if the variables are kept just as variables. – ilkkachu Jan 21 '19 at 22:14
  • Sorry, I misunderstood the concept. I've been experimenting with your solution and it seems to do exactly what I want, thank you (process subtitution is a new one to me). Guess the frustration of the past several hours got to me, haha. Marked as best answer -- thanks again! – Thomas Jan 21 '19 at 22:38
  • @Thomas, ah ok, good. I'm sorry I wasn't able to explain the idea better. Process substitution isn't really the point, mentioning it might just have been confusing here. It's mostly like a generalized replacement for pipes (see mywiki.wooledge.org/ProcessSubstitution or related questions here). Neither that or command substitution do anything about the contents of the files. I think the names refer to the fact that e.g. $(somecmd) is itself substituted on the command line with the output of `somecmd. – ilkkachu Jan 21 '19 at 22:52

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.