Bourne shell: how should I terminate a script from within a while loop reading from a pipe? - sh

Here is /bin/sh behaviour that surprised me, "exit" failing to exit:
$ cat sh_exit_from_while_pipe
#!/bin/sh
echo foobar | while read blabla
do
echo >&2 "Calling 'exit 2'"
exit 2
echo >&2 "It seems 'exit' did not terminate the while loop?"
done
echo >&2 "It seems 'exit' did not terminate the script?"
$ ./sh_exit_from_while_pipe
Calling 'exit 2'
It seems 'exit' did not terminate the script?
Empirically I notice exit is leaving the while loop, but not the whole script. So my best guess at the moment is that, maybe, the pipe forks a new shell as subprocess, and exit only terminates the subprocess?
What would be a good way to terminate properly? (I might rewrite this entirely to avoid the "echo foobar |" - which might avoid this problem, but I'm still interested in how this might be more directly addressed.)

At the point where you execute exit 2, you can't tell how many "levels" of shell need to exit. The best you can do is use the exit status of the loop to see if that level should exit as well. For example,
echo foobar | while read blabla; do
x=$(foo)
if [ "$x" = bar ]; then
exit 0 # We're done with the loop, success
elif [ "$x" = gulp ]; then
exit 1 # We're done with the loop, failure
else
exit 2 # We're done with the script, abort!
fi
done
case $? in
2) printf 'Fatal error in loop, exiting\n' >&2; exit 1 ;;
*) printf 'Loop result %d, continuing\n' ;;
esac
printf 'Continuing script...'

You can check the value returned by the while loop. eg:
#!/bin/sh
if ! echo foobar | { while read blabla
do
echo >&2 "Calling 'exit 2'"
exit 2
echo >&2 "It seems 'exit' did not terminate the while loop?"
done
exit 0
}; then
echo >&2 "The loop failed"
exit 1
fi
echo >&2 "It seems 'exit' did not terminate the script?"
(Note that the brackets and the exit 0 are not actually necessary, just added to be explicit.)

Related

Bash script to monitor Mongo db doesn't work

I am writing a bash script to monitor my MongoDB status. once it is crash then restart it. the script is as below:
while true
do
ret = $("mongod --config /etc/mongod.conf")
if $ret == 0
then
echo "I am out with code 0."
break
fi
echo "running again"
done
echo "I am out with code $?"
But it seems doesn't work. Return from the system:
running again
./mongo-text: line 3: mongod --config /etc/mongod.conf: No such file or directory
./mongo-text: line 3: ret: command not found
./mongo-text: line 4: ==: command not found
not sure what the problem is. Any help is appreciated.
Your loop can be made much simpler:
while ! mongod --config /etc/mongod.conf; do
echo "running again" >&2
sleep 1
done
if test -n "$VERBOSE"; then echo 'modgod successful'; fi
Note that the if keyword executes a command. So if $ret == 0 attempts to run the command $ret (assuming that variable is non-empty and contains no whitespace) with the arguments == and 0. That is almost certainly not what you intend. It is more typical to write if test "$ret" = 0 or if [ "$ret" = 0 ]. If $ret is empty, then it is attempting to execute the command == with the single argument 0.
There are several issues in your code:
$("mongod --config /etc/mongod.conf") will try to run mongod --config /etc/mongod.conf as a command, with spaces included
the if syntax is wrong
You can rewrite it this way:
while :; do
if mongod --config /etc/mongod.conf; then
echo "I am out with code 0."
break
fi
echo "running again"
# probably sleep for a few seconds here
done
echo "I am out with code $?"
For info about if statements, see:
How to check the exit status using an if statement
How to compare strings in Bash
Compound if statements with multiple expressions in Bash

using eval and background a process in the same line in Fish

How can I use eval to execute the command in a variable and also background it in the same line? I'm trying the following but it's not working. For example s xeyes I would expect the shell to return.
function s --description "Start a command in the background and remove from jobs list"
echo (count $argv)
if test (count $argv) -ne 1
echo "illegal number of parameters"
return 1
end
eval $argv[1] 2>&1 > /dev/null &
disown
end
Fish does not support backgrounding functions. eval is a function, so backgrounding it is not supported.
You need to put the & into the eval'd code, so
eval $argv[1] 2>&1 > /dev/null &
might work.
Alternatively, since fish 3.0 eval isn't needed here anymore, so you can just do
$argv[1] 2>&1 >/dev/null &

How to detect an error at the beginning of a pipeline?

In my script I need to work with the exit status of the non-last command of a pipeline:
do_real_work 2>&1 | tee real_work.log
To my surprise, $? contains the exit code of the tee. Indeed, the following command:
false 2>&1 | tee /dev/null ; echo $?
outputs 0. Surprise, because the csh's (almost) equivalent
false |& tee /dev/null ; echo $status
prints 1.
How do I get the exit code of the non-last command of the most recent pipeline?
Bash has set -o pipefail which uses the first non-zero exit code (if any) as the exit code of a pipeline.
POSIX shell doesn't have such a feature AFAIK. You could work around that with a different approach:
tail -F -n0 real_work.log &
do_real_work > real_work.log 2>&1
kill $!
That is, start following the as yet non-existing file before running the command, and kill the process after running the command.

Error capture of perl script in shell

I am trying to captuer the actual error of the perl script if it fails. And its not stopping and the next script is getting triggered.
UPDATED
Python scripts are running fine only having problem with the perl script
if ! perl test.pl arg2 arg1 ;
then
echo "Error running test.pl in Preparing data for algorithm; rest of process stopped." >&2 >>$LOG_PATH/Perl_RUN_$TRACK_TIME
elif ! python themes.py;
then
echo "Error running themes.py rest of process stopped." >&2 >>$LOG_PATH/Python_RUN_$feed_name'_'$TRACK_TIME
elif ! python $SCRIPT_PATH/pre_survey_dt.py;
then
echo "Error running pre_survey_dt.py rest of process stopped." >&2 >>$LOG_PATH/Python_RUN_$TRACK_TIME
fi
Please advice what I am missing here.
if ! perl test.pl arg2 arg1; then
echo "test.pl failed with error code $?" >&2
exit 1
fi
Use return instead of exit:
to return from a shell function.
to exit from a shell script that has been sourced.

How to capture error message from prompt - shell or perl

I am trying to capture the output of a command. It works fine if the command executes. However when there is an error, i am unable to capture what gets displayed in commandline
Eg.
$ out=`/opt/torque/bin/qsub submitscript`
qsub: Unauthorized Request MSG=group ACL is not satisfied: user abc#xyz.org, queue home
$ echo $out
$
I want $out to have the message
Thanks!
Errors are on stderr, so you need to redirect them into stdout so the backticks will capture it:
out=`/opt/torque/bin/qsub submitscript 2>&1`
if [ $? -gt 0 ] ; then
# By convention, this is sent to stderr, but if you need it on
# stdout, just remove the >&2 redirection
echo "Error: $out" >&2
else
echo "Success: $out"
fi
You should test the exit status of the command to figure out what the output represents (one way shown). It is similar for perl, slightly different syntax of course.
Have you tried doing it like this
$ out=`/opt/torque/bin/qsub submitscript 2>&1 > /dev/null`
$ echo $out