ssh echo command and result of its execution - bash

I would like to assign ssh output to variable like:
VAR="$(ssh $HOST "uname -a")"
VAR receives only result of command execution.
Is it possible to get two lines command+result without calling additional echo?
Expected VAR value:
hostname>uname -a
E570 4.15.0-70-generic #79-Ubuntu

You can try this :
HOST=hostname
CMD="uname -a"
VAR="$HOST>$CMD #the new line is needed
$(ssh $HOST "$CMD")"
Now if you do:
echo "$VAR"
You obtain:
hostname>uname -a
E570 4.15.0-70-generic #79-Ubuntu
I think this is the only way to avoid using echo command, but you need to assign the command to execute in ssh into a variable.

Related

Iterating array in declared function of bash shell script

I've been working through creating a script to move some files from a local machine to a remote server. As part of that process I have a function that can either be called directly or wrapped with 'declare -fp' and sent along to an ssh command. The code I have so far looks like this:
export REMOTE_HOST=myserver
export TMP=eyerep-files
doTest()
{
echo "Test moving files from $TMP with arg $1"
declare -A files=(["abc"]="123" ["xyz"]="789")
echo "Files: ${!files[#]}"
for key in "${!files[#]}"
do
echo "$key => ${files[$key]}"
done
}
moveTest()
{
echo "attempting move with wrapped function"
ssh -t "$REMOTE_HOST" "$(declare -fp doTest|envsubst); doTest ${1#Q}"
}
moveTest $2
If I run the script with something like
./myscript.sh test dev
I get the output
attempting move with wrapped function
Test moving files from eyerep-files with arg dev
Files: abc xyz
bash: line 7: => ${files[]}: bad substitution
It seems like the string expansion for the for loop is not working correctly. Is this expected behaviour? If so, is there an alternative way to loop through an array that would avoid this issue?
If you're confident that your remote account's default shell is bash, this might look like:
moveTest() {
ssh -t "$REMOTE_HOST" "$(declare -f doTest; declare -p $(compgen -e)); doTest ${1#Q}"
}
If you aren't, it might instead be:
moveTest() {
ssh -t "$REMOTE_HOST" 'exec bash -s' <<EOF
set -- ${##Q}
$(declare -f doTest; declare -p $(compgen -e))
doTest \"\$#\"
EOF
}
I managed to find an answer here: https://unix.stackexchange.com/questions/294378/replacing-only-specific-variables-with-envsubst/294400
Since I'm exporting the global variables, I can get a list of them using compgen and use that list with envsubst to specify which variables I want to replace. My finished function ended up looking like:
moveTest()
{
echo "attempting move with wrapped function"
ssh -t "$REMOTE_HOST" "$(declare -fp doTest|envsubst "$(compgen -e | awk '$0="${"$0"}"') '${1}'"); doTest ${1#Q}"
}

How to set variable value inside the remote server

Inside the remote server i have a condition statement.If that condition passes
status value should be set as success.
But here i am always getting Failure response while i print status variable
status='Success';
status='Success';
# !/bin/bash
declare -a server_PP=('XXXXX' 'YYYYYYY' );
declare -A results_map;
function process(){
serverList=$1[#];
servers=("${!serverList}");
status='Failure';
for serverName in "${servers[#]}"
do
ssh $serverName << EOF
if [ -f /app/Release/abc.war ]; then
echo "available - success"
status='Success';
fi
echo "***********status-inside******$status"
exit
EOF
echo "***********status-outside******$status"
results_map+=([$serverName]=$status);
done
}
process 'server_PP'
for i in "${!results_map[#]}"
do
echo "key :" $i
echo "value:" ${results_map[$i]}
done
Status variable should set as success when that condition get satisfied.
As written in pcarter's comment, the variables on both systems are independent from each other and don't get passed via ssh. Instead of setting a variable (or printing and reading the value as proposed in the comment, which is a working solution) you can use the exit code which gets passed automatically by ssh.
The following script is close to the original. For further improvements see below.
# !/bin/bash
declare -a server_PP=('XXXXX' 'YYYYYYY' );
declare -A results_map;
function process(){
serverList=$1[#];
servers=("${!serverList}");
status='Failure';
for serverName in "${servers[#]}"
do
if ssh $serverName << EOF
if [ -f /app/Release/abc.war ]; then
echo "available - success"
exit 0;
fi
echo "error"
exit 1
EOF
then
status='Success'
else
status='Failure'
fi
echo "***********status-outside******$status"
results_map+=([$serverName]=$status);
done
}
process 'server_PP'
for i in "${!results_map[#]}"
do
echo "key :" $i
echo "value:" ${results_map[$i]}
done
As you no longer need the variable assignments you can even omit the if ... and exit in the remote commands.
if ssh $serverName << EOF
[ -f /app/Release/abc.war ]
EOF
then
...
Your approach of using a heredoc as
ssh hostname <<EOF
# commands ...
EOF
has the disadvantage that you run an interactive shell on the remote system, which may print some system information or welcome message before executing your commands. You can further simplify the script (and removing the welcome message) by specifying the command or a script as command line arguments for ssh.
if ssh $serverName [ -f /app/Release/abc.war ]
then
...
If your command sequence is longer you can create a script on the remote system and run this script in the same way as ssh hostname scriptname. You could also create the script on the remote system using ssh or scp.

how to properly create a case statement to map an alias to a value

I'm trying to figure out create this case statement. What I am trying to do is map my input parameter $1 which has to be one of the 3 options and then map to the hostname. My command is failing. How do I assign my variable to value? What is the best way to do this?
Execute:
./test.sh cluster1
Example:
#!/bin/bash
ENDPOINT="$1"
SCH="$2"
case $ENDPOINT in
"cluster1") $HOST="myhost1.abcde.us-west-1.amazonaws.com";;
"cluster2") $HOST="myhost2.abcde.us-west-1.amazonaws.com";;
"cluster3") $HOST="myhost3.abcde.us-west-1.amazonaws.com";;
esac
psql $HOST -U myuser -d $SCH -p 5439 << EOF
Getting the error:
/test.sh: line 18: =myhost1.abcde.us-west-1.amazonaws.com: command not found
It would work if you remove the $ before HOST variable while making an assignment.
#!/bin/bash
ENDPOINT="$1"
SCH="$2"
case $ENDPOINT in
"cluster1") HOST="myhost1.abcde.us-west-1.amazonaws.com";;
"cluster2") HOST="myhost2.abcde.us-west-1.amazonaws.com";;
"cluster3") HOST="myhost3.abcde.us-west-1.amazonaws.com";;
esac
psql "$HOST" -U myuser -d "$SCH" -p 5439 << EOF
Also try using getopts for better scalability.
You should change your case statement like this:
HOST=$(case $ENDPOINT in
"cluster1") echo "myhost1.abcde.us-west-1.amazonaws.com";;
"cluster2") echo "myhost2.abcde.us-west-1.amazonaws.com";;
"cluster3") echo "myhost3.abcde.us-west-1.amazonaws.com";;
esac)
Can't test right now, let me know if it works.

use of ssh variable in the shell script

I want to use the variables of ssh in shell script.
suppose I have some variable a whose value I got inside the ssh and now I want to use that variable outside the ssh in the shell itself, how can I do this ?
ssh my_pc2 <<EOF
<.. do some operations ..>
a=$(ls -lrt | wc -l)
echo \$a
EOF
echo $a
In the above example first echo print 10 inside ssh prints 10 but second echo $a prints nothing.
I would refine the last answer by defining some special syntax for passing the required settings back, e.g. "#SET var=value"
We could put the commands (that we want to run within the ssh session) in a cmdFile file like this:
a=`id`
b=`pwd`
echo "#SET a='$a'"
echo "#SET b='$b'"
And the main script would look like this:
#!/bin/bash
# SSH, run the remote commands, and filter anything they passed back to us
ssh user#host <cmdFile | grep "^#SET " | sed 's/#SET //' >vars.$$
# Source the variable settings that were passed back
. vars.$$
rm -f vars.$$
# Now we have the variables set
echo "a = $a"
echo "b = $b"
If you're doing this for lots of variables, you can add a function to cmdFile, to simplify/encapsulate your special syntax for passing data back:
passvar()
{
var=$1
val=$2
val=${val:-${!var}}
echo "#SET ${var}='${val}'"
}
a=`id`
passvar a
b=`pwd`
passvar b
You might need to play with quotes when the values include whitespace.
A script like this could be used to store all the output from SSH into a variable:
#!/bin/bash
VAR=$(ssh user#host << _EOF
id
_EOF)
echo "VAR=$VAR"
it produces the output:
VAR=uid=1000(user) gid=1000(user) groups=1000(user),4(adm),10(wheel)

shell script ssh command exit status

In a loop in shell script, I am connecting to various servers and running some commands. For example
#!/bin/bash
FILENAME=$1
cat $FILENAME | while read HOST
do
0</dev/null ssh $HOST 'echo password| sudo -S
echo $HOST
echo $?
pwd
echo $?'
done
Here I am running "echo $HOST" and "pwd" commands and I am getting exit status via "echo $?".
My question is that I want to be able to store the exit status of the commands I run remotely in some variable and then ( based on if the command was success or not) , write a log to a local file.
Any help and code is appreciated.
ssh will exit with the exit code of the remote command. For example:
$ ssh localhost exit 10
$ echo $?
10
So after your ssh command exits, you can simply check $?. You need to make sure that you don't mask your return value. For example, your ssh command finishes up with:
echo $?
This will always return 0. What you probably want is something more like this:
while read HOST; do
echo $HOST
if ssh $HOST 'somecommand' < /dev/null; then
echo SUCCESS
else
echo FAIL
done
You could also write it like this:
while read HOST; do
echo $HOST
if ssh $HOST 'somecommand' < /dev/null
if [ $? -eq 0 ]; then
echo SUCCESS
else
echo FAIL
done
You can assign the exit status to a variable as simple as doing:
variable=$?
Right after the command you are trying to inspect. Do not echo $? before or the new value of $? will be the exit code of echo (usually 0).
An interesting approach would be to retrieve the whole output of each ssh command set in a local variable using backticks, or even seperate with a special charachter (for simplicity say ":") something like:
export MYVAR=`ssh $HOST 'echo -n ${HOSTNAME}\:;pwd'`
after this you can use awk to split MYVAR into your results and continue bash testing.
Perhaps prepare the log file on the other side and pipe it to stdout, like this:
ssh -n user#example.com 'x() { local ret; "$#" >&2; ret=$?; echo "[`date +%Y%m%d-%H%M%S` $ret] $*"; return $ret; };
x true
x false
x sh -c "exit 77";' > local-logfile
Basically just prefix everything on the remote you want to invoke with this x wrapper. It works for conditionals, too, as it does not alter the exit code of a command.
You can easily loop this command.
This example writes into the log something like:
[20141218-174611 0] true
[20141218-174611 1] false
[20141218-174611 77] sh -c exit 77
Of course you can make it better parsable or adapt it to your whishes how the logfile shall look like. Note that the uncatched normal stdout of the remote programs is written to stderr (see the redirection in x()).
If you need a recipe to catch and prepare output of a command for the logfile, here is a copy of such a catcher from https://gist.github.com/hilbix/c53d525f113df77e323d - but yes, this is a bit bigger boilerplate to "Run something in current context of shell, postprocessing stdout+stderr without disturbing return code":
# Redirect lines of stdin/stdout to some other function
# outfn and errfn get following arguments
# "cmd args.." "one line full of output"
: catch outfn errfn cmd args..
catch()
{
local ret o1 o2 tmp
tmp=$(mktemp "catch_XXXXXXX.tmp")
mkfifo "$tmp.out"
mkfifo "$tmp.err"
pipestdinto "$1" "${*:3}" <"$tmp.out" &
o1=$!
pipestdinto "$2" "${*:3}" <"$tmp.err" &
o2=$!
"${#:3}" >"$tmp.out" 2>"$tmp.err"
ret=$?
rm -f "$tmp.out" "$tmp.err" "$tmp"
wait $o1
wait $o2
return $ret
}
: pipestdinto cmd args..
pipestdinto()
{
local x
while read -r x; do "$#" "$x" </dev/null; done
}
STAMP()
{
date +%Y%m%d-%H%M%S
}
# example output function
NOTE()
{
echo "NOTE `STAMP`: $*"
}
ERR()
{
echo "ERR `STAMP`: $*" >&2
}
catch_example()
{
# Example use
catch NOTE ERR find /proc -ls
}
See the second last line for an example (scroll down)

Resources