variables in remote nested server via ssh - bash

Trying to set and access some variables on a remote server
the script will execute on a local server, it will login to remote-server1 and then login again to another second remote server remote-server2
I can successfully set and access variables in both local and remote-server1 without any issues, but having problem doing the same on remote-server2
local1=$(echo "local-srv"); echo "${local1}"
output=$(sshpass -p "${PSSWD}" ssh -t -q -oStrictHostKeyChecking=no admin#$mgmtIP "bash -s" <<EOF
remote1=\$(echo "remote-srv1"); echo "\${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
remote2=\$(echo "remote-srv2"); echo "\${remote2}"
EOF2
EOF
)
Here is my output
local-srv
remote-srv1
as you can see remote-srv2 is missing
----- UPDATE ---
please note that $(echo "text") is just for simplicity but a complex command will be executed here and the output set to a variable

You have two nested ssh commands with nested here-documents, and to delay interpretation of the $ expressions in the inner one, you need more escapes. To see the problem, you can replace the ssh command with cat to see what would be sent to the remote computer. Here's an example, using your original code (and some modified variable definitions); note that the $ and > are prompts from my shell.
$ targetCompute=remote-server2
$ local1="local-srv"; echo "${local1}"
local-srv
$ cat <<EOF
> remote1=\$(echo "remote-srv1"); echo "\${remote1}"
> ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
> remote2=\$(echo "remote-srv2"); echo "\${remote2}"
> EOF2
> EOF
remote1=$(echo "remote-srv1"); echo "${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "remote-server2" "bash -s" <<EOF2
remote2=$(echo "remote-srv2"); echo "${remote2}"
EOF2
Notice that the lines relating to remote1 and remote2 have both had their escapes removed, so they're both going to have their $ expressions expanded on remote-srv1. That's what you want for the remote1 line, but to delay interpretation of the remote2 line you have to add another escape... and that escape itself needs to be escaped, so there'll actually be three escapes before each $:
$ cat <<EOF
> remote1=\$(echo "remote-srv1"); echo "\${remote1}"
> ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
> remote2=\\\$(echo "remote-srv2"); echo "\\\${remote2}"
> EOF2
> EOF
remote1=$(echo "remote-srv1"); echo "${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "remote-server2" "bash -s" <<EOF2
remote2=\$(echo "remote-srv2"); echo "\${remote2}"
EOF2
So \\\$(echo "remote-srv2") and "\\\${remote2}" in the local here-document become \$(echo "remote-srv2") and "\${remote2}" in the here-document on remote-srv1, and then the command actually gets executed and the variable expanded on remote-srv2.

I needed to escape by ///
Thanks to the answer given by #Goron Davisson
local1=$(echo "local-srv"); echo "${local1}"
output=$(sshpass -p "${PSSWD}" ssh -t -q -oStrictHostKeyChecking=no admin#$mgmtIP "bash -s" <<EOF
remote1=\$(echo "remote-srv1"); echo "\${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
remote2=\\\$(echo "remote-srv2"); echo "\\\${remote2}"
EOF2
EOF
)
Output
local-srv
remote-srv1
remote-srv2

Related

Calling local variable in remote host

below script is not working in remote host
PARAMETER="123 456"
ssh user#host PARAMETER="$PARAMETER" bash -s <<- __EOF
echo \$PARAMETER
__EOF
but when I try Below it is working fine
PARAMETER="123"
ssh user#host PARAMETER="$PARAMETER" bash -s <<- __EOF
echo \$PARAMETER
__EOF
Looks like it is not accepting space in the variable. Can anyone help?
As locally, you have to quote variable expansion to disable word splitting. echo "$var" not echo $var.
And you have to quote the value for unquoting by ssh:
ssh user#host PARAMETER="$(printf "%q" "$PARAMETER")" bash -s <<- __EOF
echo "\$PARAMETER"
__EOF
If you are using stdin, I suggest to use to transfer all context with stdin without caring of super-double-quoting:
# define function with the code you want to run, normally quoted
work() {
echo "$PARAMETER"
}
ssh user#host bash -s <<EOF
$(declare -p PARAMETER) # serialize variables
$(declare -f work) # serialize functions
work # run the function
EOF

How to launch a script for a list of ip addresses (bash script)

i have a problem with my bash shell script. This is my code:
#!/bin/bash/
while read line
do
echo -e "$line
"
sleep 5;
done < Ip.txt
sshpass -p 'Password' ssh -o StrictHostKeyChecking=no root#$ip -t "cd /exemple/ && sh restart-launcher.sh;exec \$SHELL -l"
My script allows to launch for each ip (Ip.txt) in the folder "exemple" a script(restart-launcher.sh) but when I launch it , it only lists the ip without taking this part into account:
sshpass -p 'Password' ssh -o StrictHostKeyChecking=no root#$ip -t "cd /folders/ && sh restart-launcher.sh;exec \$SHELL -l"
How do I create a bash script that works in Linux?
#!/bin/bash/
while read -r line; do
echo -e "$line\n"
sshpass -p 'Password' ssh -o StrictHostKeyChecking=no root#$line -t \
"cd /exemple/ && sh restart-launcher.sh;exec \$SHELL -l"
sleep 5
done < Ip.txt
Now, we could have a discussion about using sshpass (or rather, why you shouldn't), but it feels out of scope.
So, all commands you wish to be looped over need to be inside the loop.
As you read sets the variable $line, that is what you need to use. In your example, you used the variable $ip which you haven't set anywhere.
If we assume that "Ip.txt" contains a list of only IPs perhaps what you mean to do is use sshpass inside the while-loop so it runs for each IP in the .txt file.
#!/bin/bash/
while read line
do
echo -e "$line
"
sshpass -p 'Password' ssh -o StrictHostKeyChecking=no root#$line -t "cd /exemple/ && sh restart-launcher.sh;exec \$SHELL -l"
sleep 5;
done < Ip.txt
sshpass has been moved into the while loop and $ip replaced with $line

BASH Script passing variable with space to Heredoc?

I'm using the following:
filename="Test File 17-07-2020.xls"
sshpass -p $password ssh root#$IP /bin/bash -s "$filename" << 'EOF'
echo $1
EOF
This works when filename equals Testfile.xls and the echo outputs the full filename.
But fails if the filename is called Test File 17-07-2020.xls
My understanding is the spaces are breaking the input so it becomes:
$1 = Test
$2 = File
$3 = 17-07-2020.xls
Is there anyway to pass this keeping the spaces and having it all in $1
If I add filename=$(echo "$filename" | sed 's/ /\\ /g') before the SSHPASS command it does work.
Is that a valid way to do it or is there a better way ?
Thanks
You still need to quote $1. Quoting the delimiter prevents $1 from being expanded early; it doesn't prevent word splitting once the shell actually executes echo $1.
sshpass -p $password ssh root#$IP "/bin/bash -s \"$filename\"" << 'EOF'
echo "$1"
EOF
If you are using bash locally, there are two extensions that could produce a value that is safe to pass to the remote shell.
sshpass -p $password ssh root#$IP "/bin/bash -s $(printf '%q' "$filename")"
or
sshpass -p $password ssh root#$IP "/bin/bash -s ${filename#Q}"
The former works at least in bash 3.2 (I highly doubt you are using an older version), while the latter requires bash 4.4.

Multiline ssh command with for

I'm having a bash script that is executing commands through ssh.
FILENAMES=(
"export_production_20200604.tgz"
"export_production_log_20200604.tgz"
"export_production_session_20200604.tgz"
"export_production_view_20200604.tgz"
)
sshpass -p $PASSWORD ssh -T $LOGIN#$IP '/bin/bash' <<EOF
for f in "${FILENAMES[#]}"; do
echo Untar "$f"
done
EOF
The thing is when I execute the script, $f is empty.
I've looked at multiple solutions online to perform multiple command executions, but none works :
link 1
link 2
...
Could you help me figure it out ?
Note :
The execution of :
for f in "${FILENAMES[#]}"; do
echo Untar "$f"
done
outside the <<EOF EOF, works
On local :
bash 4.4.20(1)-release
Remote :
bash 4.2.46(2)-release
EDIT : Tricks
Having a tight timeline, and having no choice, I implemented the solution provided by #hads0m, may it helps fellow developer having the same issue :
# $1 the command
function executeRemoteCommand() {
sshpass -p $DB_PASSWORD ssh $DB_LOGIN#$DB_SERVER_IP $1
}
for i in "${!FILENAMES[#]}"; do
f=$FILENAMES[$i]
DB_NAME=$DB_NAMES[$i]
# Untar the file
executeRemoteCommand '/usr/bin/tar xzvf '$MONGODB_DATA_PATH'/'$TMP_DIRECTORY'/'$f' --strip-components=1'
# Delete the tar
executeRemoteCommand 'rm -f '$MONGODB_DATA_PATH'/'$TMP_DIRECTORY'/'$f''
# Restore the database
executeRemoteCommand 'mongorestore --host 127.0.0.1:'$DB_PORT' --username "'$MONGODB_USER'" --password "'$MONGODB_PASSWORD'" --authenticationDatabase admin --gzip "'$DB_NAME'" --db "'$DB_NAME'"'
done
You need to escape $ sign to avoid it being expanded locally and pass the array to remote.
This may be what you wanted :
#!/usr/bin/env bash
FILENAMES=(
"export_production_20200604.tgz"
"export_production_log_20200604.tgz"
"export_production_session_20200604.tgz"
"export_production_view_20200604.tgz"
)
sshpass -p $PASSWORD ssh -T $LOGIN#$IP '/bin/bash' <<EOF
$(declare -p FILENAMES)
for f in "\${FILENAMES[#]}"; do
echo Untar "\$f"
done
EOF
Try running it like this:
for f in "${FILENAMES[#]}"; do
sshpass -p $PASSWORD ssh -T $LOGIN#$IP echo Untar "$f"
done
Also, don't forget to add #!/bin/bash into the first line of your script.

Proper way to show which SSH commands you are running

I am trying to pass an array of commands to a NID device via SSH, then storing the output into a variable. I can't figure out a efficient way to display which command is running in the output.
I can get it working by looping the the array and doing 7 separate SSH sessions. Which is very slow.
n_info=$(sshpass -p "-PW-" ssh -q -o StrictHostKeyChecking=false admin#$nid_ip << EOF
${c_array[0]}
${c_array[1]}
${c_array[2]}
${c_array[3]}
${c_array[4]}
${c_array[5]}
${c_array[6]}
exit
EOF
)
echo "$i"
echo "$n_info"| sed "s/ACCEDIAN:>//g"
Expected:
[show log]
log text
log text
log text
[show config]
config text
config text
config text
Actual:
log text
log text
log text
config text
config text
config text
Commands are not static**
Simplest/cleanest solution, if it's possible, is to run the echo commands on the nid.
Assuming headers contain no shell special characters:
sshpass -p "-PW-" ssh -q -o StrictHostKeyChecking=false admin#$nid_ip << EOF
echo '[${h_array[0]}]'; ${c_array[0]}
echo '[${h_array[1]}]'; ${c_array[1]}
echo '[${h_array[2]}]'; ${c_array[2]}
echo '[${h_array[3]}]'; ${c_array[3]}
echo '[${h_array[4]}]'; ${c_array[4]}
echo '[${h_array[5]}]'; ${c_array[5]}
echo '[${h_array[6]}]'; ${c_array[6]}
exit
EOF | sed "s/ACCEDIAN:>//g"
If you are using openssh as the ssh client, it has options to use a single control connection. Something like:
REMOTE="admin#$nid"
CP_DIR="/tmp/sshctl/$$"
CP="$CP_DIR/"%L-%r#%h:%p"
mkdir -p "$CP_DIR"
sshpass -p "-PW-" \
ssh -q -nNf -o ControlMaster=yes -o ControlPath="${CP}" \
-q -o StrictHostKeyChecking=false "$REMOTE"
# !!! check you have a connection !!!
# it may work to wrap "sshpass ..." with "if ! sshpass ...; then do_error; fi"
(
for i in {0..6}; do
echo "[$h_array[$i]}]"
ssh -o ControlPath="${CP}" \
-q -o StrictHostKeyChecking=false "$REMOTE" "${c_array[$i]}"
done
ssh -O exit -o ControlPath="$CP" "$REMOTE"
) | sed "s/ACCEDIAN:>//g"
rm -r "$CP_DIR"

Resources