exit code from ssh command - bash

I'm trying to retrieve the code return from this script:
#!/bin/bash
echo "CM 1"
ssh -i key/keyId.ppk user#X.X.X.X "
grep blabla ddd
if [ ! $? -eq 0 ]; then exit 1; fi
"
echo $?
But the last command echo $? returns 0 instead of 1.
And if try to run separately (not as a script) :
the ssh command: ssh -i key/keyId.ppk user#X.X.X.X
grep blabla ddd => I get the msg "grep: ddd: No such file or directory"
then: if [ ! $? -eq 0 ]; then exit 1; fi
then: echo $? => it returns 1 as expected
Do you have an idea why it doesn't work in my script ?
Thank you

This code
ssh -i key/keyId.ppk user#X.X.X.X "
grep blabla ddd
if [ ! $? -eq 0 ]; then exit 1; fi
"
evaluates $? in your shell and not in the remote one, because the $ is not escaped in single quotes. You should escape that to reach desired behaviour. Once to avoid evaluation in your local shell, for the second time to avoid evaluation when it is passed to the bash on remote side. Or rather put the command into single quotes:
ssh -i key/keyId.ppk user#X.X.X.X '
grep blabla ddd
if [ ! $? -eq 0 ]; then exit 1; fi
'

Related

shell script handle an error from a command being excuted from echo

Hello I am new to shell script and I need to handle the error coming from a command being excuted inside echo like the following
echo -e "some internal command that I can't share \nq" | nc localhost 10000
I want to say
if [[ there's no error ]]
try
echo "YOUR SUPERSECRET COMMAND" | nc localhost 10000 | grep "Your expected error"
if [[ $? -eq 0 ]]; then
echo "Do something useful with error"
else
echo "Success"
fi
grep return 0 on matching and returns 1 when it doesn't find matching string.
The shell variable $? will give you the exit code. So, you could do:
echo -e "some internal command that I can't share \nq" | nc localhost 10000
rc=$?
if [[ $rc == 0 ]]; then
echo "success"
fi
Or simply,
if echo -e "some internal command that I can't share \nq" | nc localhost 10000; then
echo "success"
fi
Here is a concise way of doing it:
internalcommand &>/dev/null 2>&1 && echo OK || echo FAILED
If internalcommand succeeds OK will be printed to stdout, if it fails FAILED is printed.
Note tested on Bash v4

shell bash - return code

Is it possible to run a command to redirect output and standard error in a file and know the return code of the command ?
./COMMANDE.sh 2>&1 | tee -a $LOG_DIRECTORY/$LOG_FILE
if [ $? = 0 ]; then
echo "OK"
else
echo "KO"
exit 1
fi
Currently, I get the return code of the tee command, I want to recover that of COMMANDE.sh.
You want PIPESTATUS.
./COMMANDE.sh 2>&1 | tee -a $LOG_DIRECTORY/$LOG_FILE
if [ ${PIPESTATUS[0]} = 0 ]; then
echo "OK"
else
echo "KO"
exit 1
fi
The index into the array ([0]) is the index of the command in the pipe chain just executed (./COMMANDE.sh). ${PIPESTATUS[1]} would be the tee.

exit statement will not break while loop in unix shell

The exit statements in each status check if statement do not break the while loop and truly exit the script. Is there something I can do to break the loop and exit with that $STATUS code?
EDIT: I've updated my code and it still isn't working. The status check if statements successfully break the loop but when I try to evaluate the $EXIT_STATUS it's always null, likely having something to do with scope. What am I missing here?
if [ $RESTART -le $STEP ]; then
. tell_step
while read XML_INPUT; do
XML_GDG=`get_full_name $GDG_NAME P`
cp $XML_INPUT $XML_GDG
STATUS=$?
EXIT_STATUS=$STATUS
if [ $STATUS -ne 0 ]; then
break
fi
add_one_gen $XML_GDG
STATUS=$?
EXIT_STATUS=$STATUS
if [ $STATUS -ne 0 ]; then
break
fi
done < $XML_STAGE_LIST
echo $EXIT_STATUS
if [ $EXIT_STATUS -ne 0 ]; then
exit $EXIT_STATUS
fi
fi
I had the same problem: when piping into a while loop, the script did not exit on exit. Instead it worked like "break" should do.
I have found 2 solutions:
a) After your while loop check the return code of the while loop and exit then:
somecommand | while something; do
...
done
# pass the exit code from the while loop
if [ $? != 0 ]
then
# this really exits
exit $?
fi
b) Set the bash script to exit on any error. Paste this at the beginning of your script:
set -e
Not really understand why your script dosn't exits on exit, because the next is works without problems:
while read name; do
echo "checking: $name"
grep $name /etc/passwd >/dev/null 2>&1
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo "grep failed for $name rc-$STATUS"
exit $STATUS
fi
done <<EOF
root
bullshit
daemon
EOF
running it, produces:
$ bash testscript.sh ; echo "exited with: $?"
grep failed for bullshit rc-1
exited with: 1
as you can see, the script exited immediatelly and doesn't check the "daemon".
Anyway, maybe it is more readable, when you will use bash functions like:
dostep1() {
grep "$1:" /etc/passwd >/dev/null 2>&1
return $?
}
dostep2() {
grep "$1:" /some/nonexistent/file >/dev/null 2>&1
return $?
}
err() {
retval=$1; shift;
echo "$#" >&2 ; return $retval
}
while read name
do
echo =checking $name=
dostep1 $name || err $? "Step 1 failed" || exit $?
dostep2 $name || err $? "Step 2 failed" || exit $?
done
when run like:
echo 'root
> bullshit' | bash testexit.sh; echo "status: $?"
=checking root=
Step 2 failed
status: 2
so, step1 was OK and exited on the step2 (nonexisten file) - grep exit status 2, and when
echo 'bullshit
bin' | bash testexit.sh; echo "status: $?"
=checking bullshit=
Step 1 failed
status: 1
exited immediatelly on step1 (bullshit isn't in /etc/passwd) - grep exit status 1
You'll need to break out of your loop and then exit from your script. You can use a variable which is set on error to test if you need to exit with an error condition.
I had a similar problem when pipelining. My guess is a separate shell is started when piplining. Hopefully it helps someone else who stumbles across the problem.
From jm666's post above, this will not print 'Here I am!':
while read name; do
echo "checking: $name"
grep $name /etc/passwd >/dev/null 2>&1
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo "grep failed for $name rc-$STATUS"
exit $STATUS
fi
done <<EOF
root
yayablah
daemon
EOF
echo "Here I am!"
However the following, which pipes the names to the while loop, does. It will also exit with a code of 0. Setting the variable and breaking doesn't seem to work either (which makes sense if it is another shell). Another method needs to be used to either communicate the error or avoid the situation in the first place.
cat <<EOF |
root
yayablah
daemon
EOF
while read name; do
echo "checking: $name"
grep $name /etc/passwd >/dev/null 2>&1
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo "grep failed for $name rc-$STATUS"
exit $STATUS
fi
done
echo "Here I am!"

bash: redirectly subshell into read

A little history behind this - I'm trying to write a nagios plugin to detect if an nfs mount is unmounted and if a mount is stale, which is where I'm running into a problem.
What I'm trying to achieve is detecting if a mount is stale. The problem I'm trying to work around is the fact that a stale nfs handle causes any action on that directory to hang and timeout after 3-4 minutes. By forcing a timeout onto a stat command inside an nfs mounted directory with read, I should be able to work around that problem.
So I picked up this snippet somewhere, which works perfectly when run manually from the cli on an nfs client (where /www/logs/foo is a stale nfs mount)
$ read -t 2 < <(stat -t /www/logs/foo/*); echo $?
1
The problem comes when I try to incorporate this snippet into a script like so (snippet attached, full script attached at the end):
list_of_mounts=$(grep nfs /etc/fstab | grep -v ^# | awk '{print $2'} | xargs)
exitstatus $LINENO
for X in $list_of_mounts; do
AM_I_EXCLUDED=`echo " $* " | grep " $X " -q; echo $?`
if [ "$AM_I_EXCLUDED" -eq "0" ]; then
echo "" >> /dev/null
#check to see if mount is mounted according to /proc/mounts
elif [ ! `grep --quiet "$X " /proc/mounts; echo $?` -eq 0 ]; then
#mount is not mounted at all, add to list to remount
remount_list=`echo $remount_list $X`;
#now make sure its not stale
elif [ ! "`read -t 2 < <(stat -t $X/*) ; echo $?`" -eq "0" ]; then
stalemount_list=`echo $stalemount_list $X`
fi
Gives me this error:
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: syntax error near unexpected token `<'
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: `read -t 2 < <( '
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: syntax error near unexpected token `)'
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: ` ) ; echo $?'
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: line 46: [: stat -t /www/logs/foo/*: integer expression expected
I was able to work around the syntax error by using " read -t 2<<< $(stat -t $X/)" instead of " read -t 2< <(stat -t $X/)", however stat no longer benefits from the timeout on read, which takes me back to the original problem.
While I'm open to new solutions, I'm also curious as to what behavior might be causing this shell vs script difference.
Full nagios check:
#!/bin/bash
usage() {
echo "
Usage:
check_nfs_mounts.sh
It just works.
Optional: include an argument to exclude that mount point
"
}
ok() {
echo "OK - $*"; exit 0
exit
}
warning() {
echo "WARNING - $*"; exit 1
exit
}
critical() {
echo "CRITICAL - $*"; exit 2
exit
}
unknown() {
echo "UNKNOWN - $*"; exit 3
exit
}
exitstatus() {
if [ ! "$?" -eq "0" ] ;
then unknown "Plugin failure - exit code not OK - error line $*"
fi
}
# Get Mounts
list_of_mounts=$(grep nfs /etc/fstab | grep -v ^# | awk '{print $2'} | xargs)
exitstatus $LINENO
for X in $list_of_mounts; do
AM_I_EXCLUDED=`echo " $* " | grep " $X " -q; echo $?`
if [ "$AM_I_EXCLUDED" -eq "0" ]; then
echo "" >> /dev/null
#check to see if mount is mounted according to /proc/mounts
elif [ ! `grep --quiet "$X " /proc/mounts; echo $?` -eq 0 ]; then
#mount is not mounted at all, add to list to remount
remount_list=`echo $remount_list $X`;
#now make sure its not stale
elif [ ! "`read -t 2 <<< $(stat -t $X/*) ; echo $?`" -eq "0" ]; then
stalemount_list=`echo $stalemount_list $X`
fi
done
#Make sure result is a number
if [ -n "$remount_list" ] && [ -n "$stalemount_list" ]; then
critical "Not mounted: $remount_list , Stale mounts: $stalemount_list"
elif [ -n "$remount_list" ] && [ -z "$stalemount_list"]; then
critical "Not mounted: $remount_list"
elif [ -n "$stalemount_list" ] && [ -n "$remount_list" ]; then
critical "Stale mount: $stalemount_list"
elif [ -z "$stalemount_list" ] && [ -z "$remount_list" ]; then
ok "All mounts mounted"
fi
You need to make sure your shebang specifies Bash:
#!/bin/bash
The reason for the error message is that on your system, Bash is symlinked to /bin/sh which is used when there's no shebang or when it's #!/bin/sh.
In this case, Bash is run as if you had started it with bash --posix which disables some non-POSIX features such as process substitution (<()), but confusingly not others such as here strings (<<<).
Change your shebang and you should be OK.
You can save the output of a subshell in this way:
$ read a < <(echo one)
$ echo $a
one
Or in this way (if you just want to process $a and forget it:
$ ( echo one; echo two) | (read a; echo $a)
one
The first variant will work only in bash. Bourne Shell (/bin/sh) does not support this syntax. May be that is the reason why you get the error message. May be you script is interpreted by /bin/sh not by /bin/bash

Bash remote files system directory test

the more I learn bash the more questions I have, and the more I understand why very few people do bash. Easy is something else, but I like it.
I have managed to figure out how to test directories and there writablity, but have a problem the minute I try to do this with a remote server over ssh. The first instance testing the /tmp directory works fine, but when the second part is called, I get line 0: [: missing]'`
Now if I replace the \" with a single quote, it works, but I thought that single quotes turn of variable referencing ?? Can someone explain this to me please ? Assuming that the tmp directory does exist and is writable, here the script so far
#!/bin/bash
SshHost="hostname"
SshRsa="~/.ssh/id_rsa"
SshUser="user"
SshPort="22"
Base="/tmp"
Sub="one space/another space"
BaseBashExist="bash -c \"[ -d \"$Base\" ] && echo 0 && exit 0 || echo 1 && exit 1\""
SSHBaseExist=$( ssh -l $SshUser -i $SshRsa -p $SshPort $SshHost ${BaseBashExist} )
echo -n $Base
if [ $? -eq 0 ]
then
echo -n "...OK..."
else
echo "...FAIL"
exit 1
fi
BaseBashPerm="bash -c \"[ -w \"$Base\" ] && echo 0 && exit 0 || echo 1 && exit 1\""
SSHBaseExist=$( ssh -l $SshUser -i $SshRsa -p $SshPort $SshHost ${BaseBashPerm} )
if [ $? -eq 0 ]
then
echo "...writeable"
else
echo "...not writeable"
fi
BaseAndSub="$Base/$Sub"
BaseAndSubBashExist="bash -c \"[ -d \"$BaseAndSub\" ] && echo 0 && exit 0 || echo 1 && exit 1\""
SSHBaseAndSubExist=$( ssh -l $SshUser -i $SshRsa -p $SshPort $SshHost ${BaseAndSubBashExist} )
echo -n $BaseAndSub
if [ $? -eq 0 ]
then
echo -n "...OK..."
else
echo "...FAIL"
exit 1
fi
BaseAndSubBashPerm="bash -c \"[ -w \"$BaseAndSub\" ] && echo 0 && exit 0 || echo 1 && exit 1\""
SSHBaseAndSubPerm=$( ssh -l $SshUser -i $SshRsa -p $SshPort $SshHost ${BaseAndSubBashPerm} )
if [ $? -eq 0 ]
then
echo -n "...writeable"
else
echo "...not writeable"
fi
exit 0
The first thing you should do is refactor your code with simplicity in mind, then the quoting error will go away as well. Try:
if ssh [flags] test -w "'$file'"; then
Encapsulate your SSH flags in a ssh config to facilitate re-use, and your script will shorten dramatically.
You are fine with single quotes in this context; by the time the script is seen by the remote bash, your local bash has already substituted in the variables you want to substitute.
However, your script is a total mess. You should put the repetitive code in functions if you cannot drastically simplify it.
#!/bin/bash
remote () {
# most of the parameters here are at their default values;
# why do you feel you need to specify them?
#ssh -l "user" -i ~/.ssh/id_rsa -p 22 hostname "$#"
ssh hostname "$#"
# —---------^
# if you really actually need to wrap the remote
# commands in bash -c "..." then add that here
}
exists_and_writable () {
echo -n "$1"
if remote test -d "$1"; then
echo -n "...OK..."
else
echo "...FAIL"
exit 1
fi
if remote test -w "$1"; then
echo "...writeable"
else
echo "...not writeable"
fi
}
Base="/tmp"
# Note the need for additional quoting here
Sub="one\\ space/another\\ space"
exists_and_writable "$Base"
BaseAndSub="$Base/$Sub"
exist_and_writable "$BaseAndSub"
exit 0
ssh -qnx "useraccount#hostname"
"test -f ${file absolute path} ||
echo ${file absolute path} no such file or directory"

Resources