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
delimiterNo 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
How to Call a Function (Defined in Shell Script) in a Perl Script
In Linux, How to Test Whether the Output of a Program Is Going to a Live Terminal or to a File
What Is the Use of "Echo || True"
Force Cmake to Use the Full Library Path
How to Run a Script Every Time I Save a File in Linux
Can't Read Variable That Was Stored from Within a While Loop, When Out of the While Loop
How to Write Shell Command Within Pharo Smalltalk
Replace First Few Lines with First Few Lines from Other File
How to Install Ssreflect and Mathcomp in Linux
Git Gui Like Bzr Explorer But for Git
Execve Shellcode Linux Segmentation Fault
Use Gnuplot to Plot SQLite Database
How Does Copy_From_User from the Linux Kernel Work Internally
Fork() Failing with Out of Memory Error
Need to Get the Dates of All Mondays in a Year
Pycharm Startup Error: Unable to Detect Graphics Environment