I have a project to execute a script and need to format the output, including unexpected errors. I know that I can use trap to intercept errors, so I tried to use it to format the output, the example is as follows
#!/bin/bash
set -o errtrace
status=false
trap "print " ERR
print() {
echo "{\"status\":$status,\"result\":\"$1\"}"
exit 0
}
main(){
arg=$1
if [ "$arg" == "0" ];then
status=true
print "successfully finish"
else
cat /a/b/c/d >>/dev/null
echo "abnormal termination"
fi
}
main "$#"
The logic of success meets my needs as follows
# bash format-print.sh 0
{"status":true,"result":"successfully finish"}
But when an exception error is caught it doesn't meet my needs
# bash format-print.sh 1
cat: /a/b/c/d: No such file or directory
{"status":false,"result":""}
I would like to enter the following result
# bash format-print.sh 1
cat: /a/b/c/d: No such file or directory
{"status":false,"result":"cat: /a/b/c/d: No such file or directory"}
How can I modify the code to meet my needs, and if trap does not work or is not the standard way, can you please tell me how to implement it?
It sounds like what you need is to capture the error output from your command so that you can include it in your formatted output. You don't need a trap for this, although if you wanted to use a trap to invoke print you could do that (maybe with EXIT instead of ERR though).
With direct calls to the print function, this will do what you're looking for:
#!/bin/bash
set -o errtrace
status=false
#trap "print " ERR # removed trap for testing
print() {
echo "{\"status\":$status,\"result\":\"$1\"}"
exit 0
}
main(){
arg=$1
if [ "$arg" == "0" ];then
status=true
print "successfully finish"
else
# captures stderr into a variable while discarding stdout
result=$( cat /a/b/c/d 2>&1 >/dev/null )
print "$result"
fi
}
main "$#"
This will provide the output:
$ ./test.sh 1
{"status":false,"result":"cat: /a/b/c/d: No such file or directory"}
Related
I am trying to use grep as a variable and a function in a bash shell script. I get the desired output when grep is used as an variable. I do not get the desired output when grep is used as an function.
The script used for grep as a variable is as below:
#! /bin/bash
grep=$(grep -ico "mmshutdown: Finished" mmshutdown-output.txt)
#grep()
#{
# grep -ico "mmshutdown: Finished" mmshutdown-output.txt
#}
status()
{
echo $?
}
if [[ "grep" -gt "0" ]];
then
echo
echo "exit code of search mmshutdown: Finished is $(status)"
echo
echo "FILE SYSTEM UNMOUNTED SUCESSFULLY"
sleep 3
else
echo "exit code of search mmshutdown: Finished is $(status)"
echo "check output log file mmshutdown.txt"
sleep 3
fi
the out put when we run the script is
[root#ad ~]# ./grep-variable.sh
exit code of search mmshutdown: Finished is 0
FILE SYSTEM UNMOUNTED SUCESSFULLY
The script used for grep in function is as below:
#! /bin/bash
#grep=$(grep -ico "mmshutdown: Finished" mmshutdown-output.txt)
grep()
{
grep -ico "mmshutdown: Finished" mmshutdown-output.txt
}
status()
{
echo $?
}
if [[ "grep" -gt "0" ]];
then
echo
echo "exit code of search mmshutdown: Finished is $(status)"
echo
echo "FILE SYSTEM UNMOUNTED SUCESSFULLY"
sleep 3
else
echo "exit code of search mmshutdown: Finished is $(status)"
echo "check output log file mmshutdown.txt"
sleep 3
fi
the out put when we run the script is
[root#ad ~]# ./grep-function.sh
exit code of search mmshutdown: Finished is 1
check output log file mmshutdown.txt
Could someone point out what has gone wrong here? Why is grep when used as function not providing desired output?
Your both programs are comparing "grep" with "0" not the output of the grep command.
Also -c option returns the number of lines matched.
Check the following code it works with grep as function. But as grep is as a function you will need to call original grep i.e. command with complete path else it will go in an infinite loop.
Send output to /dev/null and process on command execution status.
#! /bin/bash
set -x
#grep=$(grep -ico "mmshutdown: Finished" mmshutdown-output.txt)
grep()
{
return $(/bin/grep -ico "mmshutdown: Finished" mmshutdown-output.txt > /dev/null)
}
status()
{
echo $?
}
if grep;
then
echo
echo "exit code of search mmshutdown: Finished is $(status)"
echo
echo "FILE SYSTEM UNMOUNTED SUCESSFULLY"
sleep 3
else
echo "exit code of search mmshutdown: Finished is $(status)"
echo "check output log file mmshutdown.txt"
sleep 3
fi
i stumbled in a confusing way of conditionally returning value based on variable. I would like to check if process is successful then echo "process success", but if it's failed, i want to check specific error message then return the error message,
ERRMSG="$(cd /nonexist 2>&1)"
if [ $? -ne 0 ]
then
if [ -z "$ERRMSG|grep -o 'No such file or directory'|head -1" ]
then
echo "empty" >> $FQLOGNAME
else
echo $ERRMSG|grep -o 'No such file or directory'|head -1 >> $FQLOGNAME
fi
else
echo "success" >> $FQLOGNAME
fi
Please advice,
Thanks
You don't need to use grep to check if a string contains a substring. The built-in pattern matching in Bash is sufficient. This code should do something close to what you want:
if ERRMSG=$(cd /nonexist 2>&1) ; then
echo 'process success'
elif [[ $ERRMSG == *'No such file or directory'* ]] ; then
echo 'No such file or directory'
else
echo 'empty'
fi >> "$FQLOGNAME"
See the Conditional Constructs section of the Bash Reference Manual for details of the pattern matching capabilities of [[...]].
I've retained the ERRMSG and FQLOGNAME variables, but note that it's best to avoid ALL_UPPERCASE variable names. There is a danger that they will clash with environment variables or Bash builtin variables. See Correct Bash and shell script variable capitalization.
To find error messages defined by a pattern in multi-line error messages, and only print the first one, you can use regular expression matching (=~) in [[...]]. To provide a concrete example, this code assumes that error messages consist of 'ERROR' followed by one or more spaces followed by a decimal number:
# Example function for testing
function dostuff
{
printf 'Output line A\n'
printf 'Encountered ERROR 29\n' >&2
printf 'Output line B\n'
printf 'Encountered ERROR 105\n' >&2
printf 'Output line C\n'
return 1
}
# Regular expression matching an error string
readonly error_rx='ERROR +[0-9]+'
if ERRMSG=$(dostuff 2>&1) ; then
echo 'process success'
elif [[ $ERRMSG =~ $error_rx ]] ; then
printf '%s\n' "${BASH_REMATCH[0]}"
else
echo 'empty'
fi >> "$FQLOGNAME"
It appends 'ERROR 29' to the log file.
For more information about Bash's built-in regular expression matching see mklement0's answer to "How do I use a regex in a shell script?".
Make it simpler and easier:
if ! ERRMSG=$(cd /nonexist 2>&1); then
if <<<"$ERRMSG" grep -q 'No such file or directory'; then
# if the error string contains the message 'No such file or directory'
echo "empty" >> "$FQLOGNAME"
else
printf "Unhandled cd error: %s" "$ERRMSG" >> "$FQLOGNAME"
fi
else
echo "process success" >> "$FQLOGNAME"
fi
if statements checks for the return status of a COMMAND. [ or test is just a command, which return a status. The return status of assignment is the same as command status. What I mean, is that out=$(cmd); if [ "$?" -eq 0 ]; then is the same as if out=$(cmd); then.
Using HERE-strings is a bit better than echo "$string". Echo is not that much portable, better get used to printf "%s" "$string" which is a portable way. However HERE-strings puts additional EOF at the end of the stream, which sometimes breaks while read loops, but for most cases works ok.
Don't if [ -z "$(echo smth | grep ..)" ]; then. You can just check grep return status, just if echo smth | grep ...; then or with HERE-strings if <<<"smth" grep -q ...; then or if grep -q ... file; then. The -q option which has --quiet or --silent alternatives makes grep produce no output.
The quoting is not needed when assigning a variable from a single command substitution. tmp="$(...)" is just the same as tmp=$(...).
A bash script executes several commands.
After each command, how to:
if success: show a custom message in the console and append it to a log file
if error: show the error message in the console, append error message to the log file and resume the entire script
Here is where I am:
log_file="log.txt"
output() {
echo $#
echo $# 2>&1 >> $log_file;
if [ $# -eq 0 ]
then
exit 1
fi
}
and then after each command:
if [ $? -eq 0 ]
then
output "Custom message"
else
output $?
fi
Which makes lots of repetitions…
You could create a "run" function to limit repetition:
run()
{
message="$1"
shift
eval "$*"
if [ $? -eq 0 ]; then
output "$message"
else
output $?
fi
}
And simply prefix every command with:
run "message" "command"
The command only needs to be quoted if it contains shell meta-expressions.
Here's a way to accomplish that; after each command to be tracked, add this line:
rc="$?"; if [ "$rc" -eq 0 ]; then echo "Custom message" 2>&1 | tee -a /folder/log; else echo "$rc" 2>&1 | tee -a /folder/log; fi
The following command will give you the exit status of your last run command.
$?
e.g lets say you executed "ls" command , after that if you execute the following
echo $?
you will see the exit status as 0. which means successful . An Non-zero exit status means failure.
You can use this in some sort of if else in your shell script and based on the exit value do whatever you need to do.
Lets say a program that outputs a zero in case of success, or 1 in case of failure, like this:
main () {
if (task_success())
return 0;
else
return 1;
}
Similar with Python, if you execute exit(0) or exit(1) to indicate the result of running a script. How do you know what the program outputs when you run it in shell. I tried this:
./myprog 2> out
but I do not get the result in the file.
There's a difference between an output of a command, and the exit code of a command.
What you ran ./myprog 2> out captures the stderr of the command and not the exit code as you showed above.
If you want to check the exit code of the a program in bash/shell you need to use the $? operator which captures the last command exit code.
For example:
./myprog 2> out
echo $?
Will give you the exit code of the command.
BTW,
For capturing the output of a command, you may need to use 1 as your redirect where 1 captures stdout and 2 captures stderr.
The returnvalue of a command is stored in $?. When you want to do something with the returncode, it is best to store it in a variable before you call another command. The other command will set a new returncode in $?.
In the next code the echo will reset the value of $?.
rm this_file_doesnt_exist
echo "First time $? displays the rm result"
echo "Second time $? displays the echo result"
rm this_file_doesnt_exist
returnvalue_rm=$?
echo "rm returned with ${returnvalue}"
echo "rm returned with ${returnvalue}"
When you are interested in stdout/stderr as well, you can redirect them to a file. You can also capture them in a shell variable and do something with it:
my_output=$(./myprog 2>&1)
returnvalue_myprog=$?
echo "Use double quotes when you want to show the ${my_output} in an echo."
case ${returnvalue_myprog} in
0) echo "Finally my_prog is working"
;;
1) echo "Retval 1, something you give in your program like input not found"
;;
*) echo "Unexpected returnvalue ${returnvalue_myprog}, errors in output are:"
echo "${my_output}" | grep -i "Error"
;;
esac
I want to completely terminate/exit a bash shell script upon error, but using a function error that lets me display a debug output before termination. Now I have the problem that the exit 1 statement inside the error function will not terminate the shell script if the function's output is captured via backticks or $().
Here is my sample script:
#!/bin/bash
function error ()
{
echo "An error has occured: $1"
exit 1
}
function do_sth ()
{
if [ $1 -eq 0 ]; then
error "First param must be greater than 0!"
else
echo "OK!"
fi
}
RESULT=`do_sth 0`
echo "This line should never be printed"
How can I immediately terminate the script in the error() function?
The problem with command substitution is, that a subshell is started to execute do_sth. exit 1 then terminates this subshell and not the main bash.
You can work around this by appending || exit $?, which exits with the exit code from the command substitution
RESULT=`do_sth 0` || exit $?
If you want to show the error message, redirect it to stderr
echo "An error has occured: $1" >&2
RESULT=`do_sth 0` || exit $?
then
echo "An error has occured: $1" >&2
There's no way to avoid the fact that the subshell cannot make the parent terminate directly: the parent will have to evaluate the value returned from the subshell. A common technique is to trap exit and print an error message in the trap function. Since you are generating the error message in a subshell, you cannot simply assign the message to a variable as otherwise might be done, but you can use the filesystem. Please note that this idea is really silly, and it is much cleaner to simply write error messages to stderr. That's what it is for, and that's why it is inherited by children. Something like:
#!/bin/sh
trap final 0 2 15
# Create a temporary file to store error messages. This is a terrible
# idea: it would be much better to simply write error messages to stderr,
# but this code is attempting to demonstrate the technique of having the
# parent print the message. Perhaps it would do better to serve as an example
# of why reporting your children's mistakes is a bad idea. The children
# should be responsible for reporting their own errors. Doing so is easy,
# since they inherit file descriptor 2 from their parent.
errmsg=$( mktemp xxxxx )
final() {
test "$?" = 0 || cat $errmsg
rm -f $errmsg
} >&2
# Must emphasize one more time that this is a silly idea. The error
# function ought to be writing to stderr: eg echo "error: $*" >&2
error() { echo "error: $*" > $errmsg; exit 1; }
do_sth() {
if test "$1" -eq 0; then
error "First param must be greater than 0!"
else
echo "OK!"
fi
}
result=$( do_sth 0 ) || exit 1
echo not printed
It seems the (clever) answer is on another question's top answer by #FatalError, here on so
This should work...
#!/bin/bash
trap "exit 1" 50 #exit process after receiving signal 50.
function myerror ()
{
echo "An error has occured: $1" >&2
}
function do_sth ()
{
if [ $1 -eq 0 ]; then
myerror "First param must be greater than 0!"
kill -50 $(ps --pid $$ -opid=) #uncommon signal 50 is used.
else
echo "OK!"
fi
}
RESULT=`do_sth 1`
echo $RESULT
RESULT=`do_sth 0`
echo $RESULT
echo "This line should never be printed"