Passing external shell script variable via ssh
Your problem: Your entire command is put into single quotes – obviously so that bash expressions are expanded on the server and not locally.
But this also applies to your $1
.
Simple solution: “Interupt” the quotation by wrapping your local variable into single quotes.
ssh root@192.168.0.1 'echo "#date added $(date +%m/%d/%Y)" >> /var/named/chroot/etc/named.conf; echo "zone \"'$1'\" { type master; file \"/etc/zone/dummy-block\"; };" >> /var/named/chroot/etc/named.conf'
NB: \"$1\"
→ \"'$1'\"
.
NOTE: This solution is a simple fix for the one-liner as posted in the question above. If there's the slightest chance that this script is executed by other people, or it could process external output of any kind, please have a look at Charles Duffy's solution.
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
Passing variables in remote ssh command
If you use
ssh pvt@192.168.1.133 "~/tools/run_pvt.pl $BUILD_NUMBER"
instead of
ssh pvt@192.168.1.133 '~/tools/run_pvt.pl $BUILD_NUMBER'
your shell will interpolate the $BUILD_NUMBER
before sending the command string to the remote host.
Passing variables to SSH
As an approach that doesn't require you to do any manual escaping of your code (which frequently becomes a maintenance nightmare, since it means that code needs to be changed whenever you modify where it's expected to run) -- consider defining a function, and using declare -f
to ask the shell to generate code that will output that function for you.
The same can be done with variables, using declare -p
. Thus, passing both a function with the remote code, and the variables that remote code needs to operate that way:
#!/usr/bin/env bash
# This is run on the remote server _as root_ (behind sudo su)
remotePostEscalationFunc() {
cd /home/jenkins/report || return
if psql -d db -U user -c "$(sed -e "s/state_name/${state}/" county.sql)"; then
echo "Success processing $state" >&2
else
rc=$?
echo "Failure processing $state" >&2
return "$rc"
fi
}
# This is run on the remote server as the jenkins user (before sudo).
remoteFunc() {
sudo su -c "$(declare -p state); $(declare -f remotePostEscalationFunc); remotePostEscalationFunc"
}
# Everything below here is run locally.
arr_states=( NY CO )
for state in "${arr_states[@]}"; do
ssh jenkins@server 'bash -s' <<EOF
$(declare -f remoteFunc remotePostEscalationFunc); $(declare -p state); remoteFunc
EOF
done
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
Using external variable in awk being passed to ssh in bash
I have the following test.sh:
#! /bin/bash
host=$1
mytime=$2
pastusers=`ssh -n $host "last -n 10" | awk -v TIME=$mytime '{if($7>TIME) print $1}'`
echo $pastusers
When I run bash test.sh <some_valid_host> 20:00
it prints a list of users as expected, all in the same line. Those being in the same line is also expected.
If you have a file test.txt like:
abc
def ghi
Running
words=`cat test.txt`; echo $words
will print abc def ghi
all in same line. This is the expected behavior.
Passing variable through ssh doesnt work
Try using double quotes, that should evaluate the variable locally:
ssh $USER@some_host "qsub $working_dir/some_file.txt"
Command line Parameters in bash shell script in nested ssh
Use printf %q
to generate an eval
-safe string version of your argument list:
# generate a string which evals to list of command-line parameters
printf -v cmd_str '%q ' "$@"
# pass that list of parameters on the remote shell's command line
ssh "$host" "bash -s $cmd_str" <<'EOF'
echo "This is running on the remote host."
echo "Got arguments:"
printf '- %q\n' "$@"
EOF
For what you're really doing, the best practice is probably to use a ProxyCommand -- see the relevant documentation -- and to have your private key exposed via agent forwarding, rather than having it sitting on your bounce host on-disk. That said, it's straightforward enough to adopt the answer given above to fit the code in the question:
#!/bin/bash
printf -v args '%q ' "$@"
echo "Arguments on original host are:"
printf '- %q\n' "$@"
ssh -t "StrictHostKeyChecking=no" -i "$1" user@ip "bash -s $args" <<'EOF1'
printf -v args '%q ' "$@"
echo "Arguments on ip1 are:"
printf '- %q\n' "$@"
ssh -t -i "$1" user2@ip2 "bash -s $args" <<'EOF2'
echo "Arguments on ip2 are:"
printf '- %q\n' "$@"
EOF2
EOF1
Related Topics
How to Find Directory of Some Command
How to Run a Windows Executable from Wsl (Ubuntu) Bash
Can't Detach Child Process When Main Process Is Started from Systemd
How to Use Xargs to Copy Files That Have Spaces and Quotes in Their Names
Colour Highlighting Output Based on Regex in Shell
Best Way to Get MAChine Id on Linux
How to Create a Dynamic Variable and Assign Value to It
Why Doesn't the Cd Command Work in My Shell Program
Unix: How to Delete Files Listed in a File
Bash: Inserting a Line in a File at a Specific Location
The Concept of 'Hold Space' and 'Pattern Space' in Sed
How to Find the Maximum Stack Size
How Does Bash Deal with Nested Quotes
Generating a Sha-256 Hash from the Linux Command Line
Run Script with Rc.Local: Script Works, But Not at Boot
What Is Path on a MAC (Unix) System
I Need a List of Async-Signal-Safe Functions from Glibc
Bash: Set Array Env Variable and De-Referencing It from Any Shell Script Fails