0

I currently compute some environment variable on my local machine $FOO. Now, I have an SSH script which consists of a few commands, some of which use the environment variable $FOO, which is only set on my local machine.

I want to run those commands on the remote machine but I want to substitute the local version of $FOO so that when the SSH script runs on my remote machine it will use the local, computed value of $FOO. I need to do this because how the commands run on the remote machine depends on the local value of $FOO that I have computed.

I know that you can do ssh user@remote < script.sh to run the local shell script on the remote machine but I am unsure how to do that process but with my local $FOO environment variable as I mentioned above. I do not want to have to configure SSH on either the local or remote machines so SendEnv is not an option for me.

What is the best way to run those commands that I have in my shell script with the local environment variable $FOO?

0

I assume you know what shell should interpret the script. This example uses sh:

# somewhat flawed
ssh user@remote "FOO='$FOO' exec sh" < script.sh

The local shell will substitute $FOO because it's double-quoted (it is). Single-quotes are for the remote shell (i.e. the original remote shell that execs to the final sh).

Note ssh passes the whole command as a string. It will be interpreted on the remote side. If $FOO expands to a string containing one or more single-quotes, the whole command may misbehave. Code injection is possible.

To "disarm" possible single-quotes you can try this approach:

# still somewhat flawed
FOOx="$(printf '%s' "$FOO" | sed "s|'|'\"'\"'|g")"
ssh user@remote "FOO='$FOOx' exec sh" < script.sh

In my Debian 9 it changes every ' in $FOO into '"'"' in $FOOx. Analyze quoting in the remote shell to see the center single-quote will survive literally.

I can do this without the extra variable:

# still somewhat flawed
ssh user@remote "FOO='$(printf '%s' "$FOO" | sed "s|'|'\"'\"'|g")' exec sh" < script.sh

It's not as firm as I wish. Problems:

  • I think some implementations of sed may print a complete line (with trailing newline character) if their input is an incomplete line (without trailing newline character). Or sed can ignore an incomplete line. This is very important if the content of the variable doesn't end with a newline.
  • $() removes all trailing newlines.

To deal with these I append x and a newline character before sed. The tool (regardless of implementation) should then accept everything, it should add nothing. Then $() will remove only the extra newline. I remove the extra x on the remote side. Like this:

# foolproof (AFAIK)
ssh user@remote "FOO='$(printf '%sx\n' "$FOO" | sed "s|'|'\"'\"'|g")'; FOO=\"\${FOO%x}\" exec sh" < script.sh

Notes:

  • If your (local) $FOO expands to a string that contains no single-quotes for sure, then you can use the first, simplest command.
  • If your local shell is Bash then you can replace single-quotes with '"'"' upon expansion like this:

    # foolproof (AFAIK), needs local Bash
    pattern="'"
    replacement="'\"'\"'"
    ssh user@remote "FOO='${FOO//$pattern/$replacement}' exec sh" < script.sh
    

    This solves the problem of single quotes and uses neither $() nor sed, so other mentioned problems doesn't appear. I used additional variables to avoid this issue.

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.