How to Use Local and Remote Variables Within a Heredoc or Command Over Ssh

Unable to use local and remote variables within a heredoc or command over SSH

You need to escape the $ sign if you don't want the variable to be expanded:

$ x=abc
$ bash <<EOF
> x=def
> echo $x # This expands x before sending it to bash. Bash will see only "echo abc"
> echo \$x # This lets bash perform the expansion. Bash will see "echo $x"
> EOF
abc
def

So in your case:

ssh user@host bash << EOF
# run script with output...
REMOTE_PID=$(cat $FILE_NAME)
echo \$REMOTE_PID
EOF

Or alternatively you can just use a herestring with single quotes:

$ x=abc
$ bash <<< '
> x=def
> echo $x # This will not expand, because we are inside single quotes
> '
def

How to pass local variables to ssh scope in bash script?

The problem has nothing to do with ssh but is only related to here documents in bash or any other Posix shell.

Man page for bash says in the here document paragraph:

The format of here-documents is:

<<[-]word

here-document

delimiter

No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. In the latter case, the character sequence \<newline> is ignored, and \ must be used to quote the characters \, $, and ` .

As you quote EOF, you explicitely ask the shell not to replace the $1 and $2 variables.

The most robust way is to not quote EOF and consistently quote all others special characters in the here document.

For example, if your here document contained something like:

ssh -T user@servername << 'EOF'
for f in /var/log/messages*
do echo "Filename: " $f
done
EOF

you could rewrite it with no quotes around EOF but with one inside $:

ssh -T user@servername << EOF
for f in /var/log/messages*
do echo "Filename: " \$f
done
EOF

That way all unquoted varibles would be interpolated.


Alternatively, if the server allows it, you can try to pass the 2 parameters as environment variables.

Say you want to use the names PARAM1 and PARAM2. The sshd_config file on the server should contain the line AcceptEnv PARAM1 PARAM2 because by default and for security reasons no environment variable is accepted.

You can then use:

export PARAM1=$1
export PARAM2=$2
ssh -T -o SendEnv=PARAM1 -o SenEnv=PARAM2 user@servername << 'EOF'
...
Using variables $PARAM1 and $PARAM2
...
EOF

Execute several commands through ssh with local and remote values

Even if it's been some time, I'll answer my question with the solution I used for my problem.

After struggling a LOT with the combination of heredocs and SSH, I came up with a partial solution :

MY_ARRAY=('value1' 'value2' 'value3' 'wrongValue)
ssh admin@machine -T << EOF >output.file
for VALUE in "${MY_ARRAY[@]}"
do
if [[ \$VALUE =~ ^v ]] ; then
echo "\$VALUE : \$(matching_function \$VALUE)"
else
echo "\$VALUE : this is a wrong value"
fi
done
EOF

I used $ before local variables, and \$ before remote ones. This is due to heredoc behaviour, as bash interpretes every varaible preceeded by a $ before executing the content of the heredoc.

The >output.file allowed me to catch the output of the ssh command and do some postprocessing. However, I never found out how to allocate a variable out of the <<EOF ... EOF, but I guess this is more of a heredoc issue.

I'll accept my answer as of now, but I'll be glad if anyone could clarify why/if it is impossible to allocate a variable from the heredoc to the outside.

How to pass local variable to remote using ssh and bash script?

The variable assignment TEST="sdfsd" given in the here document is no real variable assignment, i. e. the variable assignment will actually not be performed directly in the declaration / definition of the here document (but later when the here document gets evaluated by a shell).

In addition, the $TEST variable contained in an unescaped or unquoted here document will be expanded by the local shell before the local shell executes the ssh command.
The result is that $TEST will get resolved to the empty string if it is not defined in the local shell before the ssh command or the here document respectively.

As a result, the variable assignment export TEST="sdfsd" in the here document will not take effect in the local shell, but first be sent to the shell of the remote host and only there be expanded, hence your prints nothing experience.

The solution is to use an escaped or single-quoted here document, <<\EOF or <<'EOF'; or only escape the \$TEST variable in the here document; or just define the $TEST variable before the ssh command (and here document).

# prints sdfsd
export TEST="sdfsd"
ssh localhost 'bash -s' << EOF
echo $TEST
EOF

# prints sdfsd
ssh localhost 'bash -s' << EOF
export TEST="sdfsd"
echo \$TEST
EOF

# prints sdfsd
ssh localhost 'bash -s' <<'EOF'
export TEST="sdfsd"
echo $TEST
EOF

SSH inside SSH heredoc

Replace

ssh server2

with

ssh -n server2

to prevent ssh reading from stdin.

Make a typeset function access local variable when executed remotely

Thanks to @Guy for the hint, it indeed is because ssh disables by default sending the environment variables. In my case, changing the server's setting was not wanted.

Hopefully we can hack around by using compgen, eval and declare.

First we identify added variables generically. Works if variables are created inside a called function too. Using compgen is neat because we don't need to export variables explicitely.

The array diff code comes from https://stackoverflow.com/a/2315459/1013628 and the compgen trick from https://stackoverflow.com/a/16337687/1013628.

# Store in env_before all variables created at this point
IFS=$'\n' read -rd '' -a env_before <<<"$(compgen -v)"
a=1
# Store in env_after all variables created at this point
IFS=$'\n' read -rd '' -a env_after <<<"$(compgen -v)"

# Store in env_added the diff betwen env_after and env_before
env_added=()
for i in "${env_after[@]}"; do
skip=
for j in "${env_before[@]}"; do
[[ $i == $j ]] && { skip=1; break; }
done
if [[ $i == "env_before" || $i == "PIPESTATUS" ]]; then
skip=1
fi
[[ -n $skip ]] || env_added+=("$i")
done

echo_a() {
echo a: $a
}

env_added holds now an array of all names of added variables between the two calls to compgen.

$ echo "${env_added[@]}"
a

I filter out also the variables env_before and PIPESTATUS as they are added automatically by bash.

Then, inside the heredocs, we add eval $(declare -p "${env_added[@]}").

declare -p VAR [VAR ...] prints, for each VAR, the variable name followed by = followed by its value:

$ a = 1
$ b = 2
$ declare -p a b
declare -- a=1
declare -- b=2

And the eval is to actually evaluate the declare lines. The rest of the code looks like:

bash <<EOF

# Eval the variables computed earlier
eval $(declare -p "${env_added[@]}")
$(typeset -f echo_a)

echo local heredoc:
echo_a
echo

echo local raw heredoc:
echo a: $a
echo

EOF

ssh rpi_301 bash <<EOF

# Eval the variables computed earlier
eval $(declare -p "${env_added[@]}")
$(typeset -f echo_a)

echo remote heredoc:
echo_a
echo

echo remote raw heredoc:
echo a: $a
echo

EOF

Finally, running the modified script gives me the wanted behavior:

local heredoc:
a: 1

local raw heredoc:
a: 1

remote heredoc:
a: 1

remote raw heredoc:
a: 1

Trying to cd in ssh heredoc

How to get rid of the error whilst keeping the variable intact/expandable ?

Disable expanding of this particular variable. Remember about quotes.

ssh -x ${INPUT_USER}@nano-machine /bin/bash <<- EOF
cd /tmp
mkdir --parents -- temp_img && cd -- "\$_"
DISPLAY=:0 scrot "Screenshot-${numbah}_%d-%m-%Y.png"
echo "first arg is $1 $1 $1"
EOF

But I really find that for complicated scripts it's easier to pass data as arguments and have the script not expand at all:

ssh -x ${INPUT_USER}@nano-machine /bin/bash -- "$numbah" <<-'EOF'
cd /tmp
mkdir --parents -- temp_img && cd -- "$_"
DISPLAY=:0 scrot "Screenshot-${1}_%d-%m-%Y.png"
echo "first arg is $1 $1 $1"
EOF

Passing a variable to a remote host in a bash script with ssh and EOF

Use printf %q to escape content in an eval-safe form; after doing so, you can pass them on the command line of the remote shell, and retrieve them via $1, $2, etc. within the remote script:

# put contents of $VAR into $var_str in a format that a shell can interpret
printf -v var_str %q "$VAR"

# v- pass the value on the shell command line
# | v- keep escaping the heredoc securely
# | |
ssh -T -p 1234 root@"$host" "bash -s $var_str" <<'EOF'

# retrieve it off the shell command line
var=$1

# ...and use it as you like thereafter.
echo "Remotely using $var"
EOF

Unable to use variables in remote ssh command

Variables expansions are not executed in a here document with quoted delimiter. Use unquoted delimiter to allow expansions to happen.

Because the remote shell re-parses the expression, remember about properly quoting the variables. The remote may not execute shell on the remote side - explicitly execute your shell and read from stdin. Remember to quote variable expansions:

ssh "user@${remote_ip}" 'bash -s' <<EOT
mkdir -p $(printf "%q" "$dir_to_create")
EOT

Properly passing properly quoted variables is a hard job. Do not do it yourself - use bash features. For me the best stable and simple to use feature for to transfer locally defined work to remote context is to use a function:

func() {
mkdir -p "$dir_to_create"
}
ssh "user@${remote_ip}" 'bash -s' <<EOT
$(declare -p dir_to_create) # transfer variables values
$(declare -f func) # transfer function definition
func # call the function
EOT

Please research: quoting in shell - what to use, what does it mean, what it's effect is. Research declare - what does it do. Research here document - how does a quoted terminating word <<'EOT' differ from unquoted one <<EOT. Check your scripts with http://shellcheck.net .



Related Topics



Leave a reply



Submit