PIPESTATUS[0] in BASH script - bash

I am implementing a scenario in Unix Bash scripts. I have two scripts ABC.bash and XYZ.bash. There is one condition in ABC.bash when requester does not enter Y or y scripts exit with message and do not work further. ABC.bash working fine when runs alone.Problem arises when I run it from another bash script i.e. XYZ.bash. It does not check for exit condition. Syntax of logic in XYZ.bash.
echo "Calling ABC.bash from XYZ.bash"
ABC.bash $a $b | tee -a $LOGFILE; sleep 2
if [ ${PIPESTATUS[0]} = 0 ]
then
echo "Do some work"
else
echo "Check ABC.bash input"
exit 1
fi
But when ABC.bash $a $b exit with status 2 flow still goes to IF block rather than ELSE.In log I can see message as DEBUGMODE set to 0. I need this DEBUGMODE setting as it is required but want to exit if ABC.bash exit. Ideally it should go to ELSE part as ABC.bash exit with wrong user input.
Additionally I have set up DEBUGMODE option in XYZ.bash script. Like-
if [[ -z "$1" ]]
then
echo " ">> No input so default to 0"
DEBUGMODE=0
else
echo "DEBUGMODE set to $1"
DEBUGMODE=$1
fi
enter code here

The problem is that PIPESTATUS is a volatile variable. That is it will be reset as soon as any other command is executed. You need to remove the call to sleep 2 if you want to inspect the PIPESTATUS.

In your example, PIPESTATUS reflects the status of sleep 2. So replace
ABC.bash $a $b | tee -a $LOGFILE; sleep 2
if [ ${PIPESTATUS[0]} = 0 ]
by
ABC.bash $a $b | tee -a $LOGFILE; pstat=(${PIPESTATUS[#]}); sleep 2
if [ ${pstat[0]} = 0 ]
to save the status.

As best practice, unless the your code has full control over the any variable content, better to quote the variable. This will not prevent logical error (e.g, the extra sleep that modified the PIPESTATUS), but it will avoid accidental injection of code into the script (or unexpected syntax errors)
if [ "${PIPESTATUS[0]}" = 0 ] ; then

Related

shell: failed to save error stream code to file

I am trying to detect whenever the following script (random_fail.sh) fails --which happens rarely-- by running it inside a while loop in the second script (catch_error.sh):
#!/usr/bin/env bash
# random_fail.sh
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "Everything went according to plan"
#!/usr/bin/env bash
# catch_error.sh
count=0 # The number of times before failing
error=0 # assuming everything initially ran fine
while [ "$error" != 1 ]; do
# running till non-zero exit
# writing the error code from the radom_fail script into /tmp/error
bash ./random_fail.sh 1>/tmp/msg 2>/tmp/error
# reading from the file, assuming 0 written inside most of the times
error="$(cat /tmp/error)"
echo "$error"
# updating the count
count=$((count + 1))
done
echo "random_fail.sh failed!: $(cat /tmp/msg)"
echo "Error code: $(cat /tmp/error)"
echo "Ran ${count} times, before failing"
I was expecting that the catch_error.sh will read from /tmp/error and come out of the loop once a particular run of random_fail.sh exits with 1.
Instead, the catch script seems to be running forever. I think this is because the error code is not being redirected to the /tmp/error file at all.
Please help.
You aren't catching the error code in the proper/usual manner. Also, no need to prefix the execution with the "bash" command, when it already contains the shebang. Lastly, curious why you don't simply use #!/bin/bash instead of #!/usr/bin/env bash .
Your second script should be modified to look like this:
#!/usr/bin/env bash
# catch_error.sh
count=0 # The number of times before failing
error=0 # assuming everything initially ran fine
while [ "$error" != 1 ]; do
# running till non-zero exit
# writing the error code from the radom_fail script into /tmp/error
./random_fail.sh 1>/tmp/msg 2>/tmp/error
error=$?
echo "$error"
# updating the count
count=$((count + 1))
done
echo "random_fail.sh failed!: $(cat /tmp/msg)"
echo "Error code: ${error}"
echo "Ran ${count} times, before failing"
[ "$error" != 1 ] is true if random_fail.sh prints a lone digit 1 to stderr. As long as this doesn't happen, your script will loop. You could instead test whether there has been written anything to stderr. There are several possibilities to achieve this:
printf '' >/tmp/error
while [[ ! -s /tmp/error ]]
or
error=
while (( $#error == 0 ))
or
error=
while [[ -z $error ]]
/tmp/error will always be either empty or will contain the line "The error was using magic numbers". It will never contain 0 or 1. If you want to know the exit value of the script, just check it directly:
if ./random_fail.sh 1>/tmp/msg 2>/tmp/error; then error=1; else error=0; fi
Or, you can do:
./random_fail.sh 1>/tmp/msg 2>/tmp/error
error=$?
But don't do either of those. Just do:
while ./random_fail.sh; do ...; done
As long as random_fail.sh (please read https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/ and stop naming your scripts with a .sh suffix) returns 0, the loop body will be entered. When it returns non-zero, the loop terminates.

Why is $? creating a file?

In the following script I am testing how to count how many times a command runs.
#!/bin/bash
echo "Hello"
scount=$?
if [ $scount -eq 0 ]; then
count=$(cat ${scount})
else
count=0
fi
((count++))
echo ${count} > ${scount}
echo "Scount: $count"
This is the output I receive. I am confused as to why I am getting the cat: 0: No such file or directory message.
Hello
cat: 0: No such file or directory
Scount: 1
As said on Unix manual for cat
cat - concatenate files and print on the standard output
And as said on $? here
$? is the exit status of the last executed command.
So:
cat command wants a file to read and print in standard output
$? is a number and its value is 0, cause the last command executed in that point of the script was echo "Hello" (well executed = status 0)
You're trying to do something like cat 0 so with this:
count=$(cat ${scount})
echo "Hello"
scount=$?
if [ $scount -eq 0 ]; then
count=$(cat ${scount})
else
count=0
fi
I think you were wanting to assign 0 to count and used cat instead of echo
count=$(echo "$scount")
But that's a useless use of echo
count=$scount
But now you're assigning 0 to count in both branches of the if statement, so you can remove the whole if:
echo "Hello"
scount=$?
count=0

Bash script for searching a specific word in terminal output

I'm trying to implement a bash script who supposed to search for a word in a Python script terminal output.
The Python script doesn't stop so "&" in the end of the command is needed but the "if [ $? == 0 ] ; then" condition doesn't work.
How it can be solved?
Thanks, Gal.
#!/bin/bash
#Check if Pixhawk is connected
PORT=/dev/ttyPixhawk
end=$((SECONDS+3))
not_exists=f
/usr/local/bin/mavproxy.py --daemon --non-interactive --master=$PORT | grep 'Failed' &> /dev/null &
while [ $SECONDS -lt $end ] ; do
if [ $? == 0 ] ; then
not_exists=t
fi
sleep 1
done
if [ $not_exists=t ] ; then
echo "Not Exists"
else
echo "Exists"
fi
kill $(pgrep -f '/usr/local/bin/mavproxy.py')
Bash doesn't know anything about the output of background commands. Check for yourself with [ 5444 -lt 3 ] & echo $?.
your if statement wouldn't work in any case because $? checks for the return value of the most recent previous command, which in this case is your while loop.
You have a few different options. If you're waiting for some output, and you know how long it is in the output until whatever target you're looking for occurs, you can have the python write to a file and keep checking on the file size with a timeout for failure.
You can also continue with a simple timed approach as you have where you just check the output after a few seconds and decide success or failure based on that.
You can make your python script actually end, or provide more error messages, or write only the relevant parts to file that way.
Furthermore, you really should run your script through shellcheck.net to notice more problems.
You'll need to define your goal and use case more clearly to get real help; all we can really say is "your approach will not work, but there are definitely approaches which will work"
You are checking the status of grep command output inside while loop using $?. This can be done if $? is the next command to be fired after grep and if grep is not a back-group process . But in your script, $? will return the status of while [$SECONDS -lt $end ]. You can try to re-direct the output to a temp file and check it's status
/usr/local/bin/mavproxy.py --daemon --non-interactive --master=$PORT | grep 'Failed' &> tmp.txt &
sleep 3
# If file exists and it's size is greater than 0, [ -s File] will return true
if [ -s tmp.txt ]; then
echo 'pattern exists'
else
echo 'pattern not exists'
fi

Unix shell script: exit with returning value

I have the following unix shell script, in which i have two integer
variables namely a and b.
If a is greater then or equal to b then shell script should exit with returning 0.
Else it should exit with returning 1.
My try:
Script: ConditionTest.sh
#!/bin/sh
a=10
b=20
if [ $a -ge $b ]
then
exit 0
else
exit 1
fi
....
....
....
Running Script:
$ ./ConditionTest.sh
$
Note: I am not getting any return value after executing the file.
The shell puts the exit status of the last command in the variable ?.
You could simply inspect it:
mycommand
echo $?
... or you could use it to do something else depending on its value:
mycommand && echo "ok" || echo "failed"
or alternatively, and slightly more readable:
if mycommand; then
# exit with 0
echo "ok"
else
# exit with non-zero
echo "failed"
if
Your script looks fine; you did everything right.
#!/bin/sh
a=10
b=20
if [ $a -ge $b ]
then
exit 0
else
exit 1
fi
So here's where we run it and check the return value:
$ sh test.sh
$ echo $?
1
$
10 is not greater than or equal to 20.
Another way to test it would be like this:
$ sh test.sh && echo "succeeded" || echo "failed"
failed
As noted in the comments, you should also quote your variables, always:
if [ $a -ge $b ]
Should be:
if [ "$a" -ge "$b" ]
To add to the previous answers, the key idea you should understand is that every program provides a number when exiting. That number is used as a way to report if the command has completed its operation successfully, and if not, what type of error has occurred.
Like mentioned, the exit code of the last command executed can be accessed with $?.
The reason nothing was printed by your script, is that your script returned 1, but the exit code of a command is not printed. (This is analogous to calling a function, you get a return value from the function but it's not printed)

How to process basic commandline arguments in Bash?

So I started today taking a look at scripting using vim and I'm just so very lost and was looking for some help in a few areas.
For my first project,I want to process a file as a command line argument, and if a file isn't included when the user executes this script, then a usage message should be displayed, followed by exiting the program.
I have no clue where to even start with that, will I need and if ... then statement, or what?
Save vim for later and try to learn one thing at a time. A simpler text editor is called nano.
Now, as far as checking for a file as an argument, and showing a usage message otherwise, this is a typical pattern:
PROGNAME="$0"
function show_usage()
{
echo "Usage: ${PROGNAME} <filename>" >&2
echo "..." >&2
exit 1
}
if [[ $# -lt 1 ]]; then
show_usage
fi
echo "Contents of ${1}:"
cat "$1"
Let's break this down.
PROGNAME="$0"
$0 is the name of the script, as it was called on the command line.
function show_usage()
{
echo "Usage: ${PROGNAME} <filename>" >&2
echo "..." >&2
exit 1
}
This is the function that prints the "usage" message and exits with a failure status code. 0 is success, anything other than 0 is a failure. Note that we redirect our echo to &2--this prints the usage message on Standard Error rather than Standard Output.
if [[ $# -lt 1 ]]; then
show_usage
fi
$# is the number of arguments passed to the script. If that number is less than 1, print the usage message and exit.
echo "Contents of ${1}:"
cat "$1"
$1 is out filename--the first argument of the script. We can do whatever processing we want to here, with $1 being the filename. Hope this helps!
i think you're asking how to write a bash script that requires a file as a command-line argument, and exits with a usage message if there's a problem with that:
#!/bin/bash
# check if user provided exactly one command-line argument:
if [ $# -ne 1 ]; then
echo "Usage: `basename "$0"` file"
exit 1
# now check if the provided argument corresponds to a real file
elif [ ! -f "$1" ]; then
echo "Error: couldn't find $1."
exit 1
fi
# do things with the file...
stat "$1"
head "$1"
tail "$1"
grep 'xyz' "$1"

Resources