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.
Related
I have a bash script that runs a series of python scripts. It always runs all of them, but exits with a failure code if any script exited with a failure code. At least that's what I hope it does. Here it is ...
#!/bin/bash
res=0
for f in scripts/*.py
do
python "$f";
res=$(( $res | $? ))
done
exit $res
I'd like to run this as a bash command in the terminal, but i can't work out how to replace exit so that the command fails like a failed script, rather than exits the terminal. How do I do that?
Replace your last line exit $res with
$(exit ${res})
This exits the spawned subshell with the exit value of ${res} and because it is the last statement, this is also the exit value of your script.
Bash doesn't have a concept of anonymous functions (e.g. Go) which you can defined inline and get the return value, you need to do it explicitly. Wrap the whole code in a function say f()
f() {
local res=0
for f in scripts/*.py
do
python "$f";
res=$(( $res | $? ))
done
return $res
}
and use the exit code in command line.
if ! f; then
printf '%s\n' "one more python scripts failed"
fi
Is it true, that the value of the error code doesn't matter. Then I have another solution:
#!/bin/bash
total=0
errcount=0
for f in scripts/*.py
do
let total++
python "$f" || let errcount++
done
if ((errcount))
then
printf '%u of %u scripts failed\n' $errcount $total >&2
exit 1
fi
exit 0
#!/bin/bash
for f in scripts/*.py
do
python "$f" && echo "1" >> stack || echo "0" >> stack
done
[ $(grep -o stack) -eq 0 ] && rm -v ./stack && exit 1
I am rather stoned at the moment, so I apologise if I am misinterpreting, but I believe that this will do what you need. Every time the python script returns an error code of 0, (which means it works) a 1 is echoed into a stack file. At the end of the loop, the stack is checked for the presence of a single 0, and if it finds one, exits with an error code of 1, which is for general errors.
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
I'm new to bash so assume that I don't understand everything in this simple script as I've been putting this together as of today with no prior experience with bash.
I get this error when I run test.sh:
command substitution: line 29: syntax error near unexpected token `$1,'
./f.sh: command substitution: line 29: `index_of($1, $urls))'
FILE: f.sh
#!/bin/bash
urls=( "example.com" "example2.com")
error_exit()
{
echo "$1" 1>&2
exit 1
}
index_of(){
needle=$1
haystack=$2
for i in "${!haystack[#]}"; do
if [[ "${haystack[$i]}" = "${needle}" ]]; then
echo "${i}"
fi
done
echo -1
}
validate_url_param(){
index=-2 #-2 as flag
if [ $# -eq 0 ]; then
error_exit "No url provided. Exiting"
else
index=$(index_of($1, $urls)) #ERROR POINTS TO THIS LINE
if [ $index -eq -1 ]; then
error_exit "Provided url not found in list. Exiting"
fi
fi
echo $index
}
FILE: test.sh
#!/bin/bash
. ./f.sh
index=$(validate_url_param "example.com")
echo $index
echo "${urls[0]}"
I've lost track of all of the tweaks I tried but google is failing me and I'm sure this is some basic stuff so... thanks in advance.
The immediate error, just like the error message tells you, is that shell functions (just like shell scripts) do not require or accept commas between their arguments or parentheses around the argument list. But there are several changes you could make to improve this code.
Here's a refactored version, with inlined comments.
#!/bin/bash
urls=("example.com" "example2.com")
error_exit()
{
# Include script name in error message; echo all parameters
echo "$0: $#" 1>&2
exit 1
}
# A function can't really accept an array. But it's easy to fix:
# make the first argument the needle, and the rest, the haystack.
# Also, mark variables as local
index_of(){
local needle=$1
shift
local i
for ((i=1; i<=$#; ++i)); do
if [[ "${!i}" = "${needle}" ]]; then
echo "${i}"
# Return when you found it
return 0
fi
done
# Don't echo anything on failure; just return false
return 1
}
validate_url_param(){
# global ${urls[#]} is still a bit of a wart
if [ $# -eq 0 ]; then
error_exit "No url provided. Exiting"
else
if ! index_of "$1" "${urls[#]}"; then
error_exit "Provided url not found in list. Exiting"
fi
fi
}
# Just run the function from within the script itself
validate_url_param "example.com"
echo "${urls[0]}"
Notice how the validate_url_param function doesn't capture the output from the function it is calling. index_of simply prints the result to standard output and that's fine, just let that happen and don't intervene. The exit code tells us whether it succeeded or not.
However, reading the URLs into memory is often not useful or necessary. Perhaps you are simply looking for
grep -Fx example.com urls.txt
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)
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"