why in shell script, $? can't echo twice? - linux

I have two simple scripts:
#!/bin/sh
echo "this script return sth"
exit 105
and:
#!/bin/sh
echo "this script will print last script return"
echo "first run a script"
./this_script_return_sth.sh
echo "previous script return value: $?"
echo $?
the run result is:
this script will print last script return
first run a script
this script return sth
previous script return value: 105
0
anything I did wrong? does it means that if I want to use it, it better to first store it to some variable?

$? expands to the last statement's return code. So, the zero says the last echo statement (i.e. echo "previous script return value: $?") was sucessful.
From bash manual:
?
($?) Expands to the exit status of the most recently executed foreground pipeline.
If you need the value in multiple places, then you can always store in a variable:
./this_script_return_sth.sh
rc=$?
echo "previous script return value: $rc"
echo $rc

$? always returns the status code for the last executed shell command.
In your case, the last line of your script:
echo $?
prints the return code for the last executed command which is:
echo "previous script return value: $?"
And not the script.

$? return the exit status of the last command executed.
Every command returns an exit status (sometimes referred to as a return status or exit code). A successful command returns a 0, while an unsuccessful one returns a non-zero value that usually can be interpreted as an error code.
In your first script, you are sending 105 as exit code, so it printed :
previous script return value: 105
And next line, echo $? returned 0 for successful execution of last command.
Your scripts are working as expected.
For more details, refer Exit status

Related

Pipe value to exit code

I'm looking to create a one-line command which does some stuff and gets a value (which would generally be output) and instead turn that into an exit code (so I can trigger a subsequent step which responds to non-zero exit codes).
For example, running something like this:
echo 5 | exit
And then having a subsequent echo $? output 5, the value I gave it earlier.
The above syntax doesn't work. Is there a way to achieve this?
Assuming:
cmd() { echo 5; }
For a literal answer:
cmd | (read -r rc && exit "$rc")
However, you can also run:
( exit "$(cmd)" ) # parens create a subshell so we aren't exiting the main shell
You can just use command substitution:
( exit $(cmd) )
Assuming cmd is returning an integer between 0-255
An example:
$> ( exit $(echo 5) )
$> echo $?
5

bash execute whole script but return exit code > 0 if any intermediate job has failed

I have a bash script script.sh made like:
./step1.sh
./step2.sh
./step3.sh
Each of the step*.h scripts returns proper error codes whether they failed or not.
Now if step3.sh fails I get an appropriate exit code, but if either step1.sh or step2.sh fails and step3.sh succeeds than I get status = 0, which is not ideal.
I know I can use
set -e
at the top of my script to make the script fail if any of the intermediate steps fail, but that is not what I want.
I would like to know if there is an easy option to use to execute each of the intermediate scripts (even if one of them fails) but return an exit code > 0 if any of them fails without having to keep track of each individual exit code manually.
You can trap errors:
#!/bin/bash
echo "test start."
trap 'rc=$?' ERR
./error.sh
./script2.sh
echo "test done."
return ${rc}
For more information on how traps work, see trap.
Boiling down to the absolute basics,
rc=0
./step1 || rc=$?
./step2 || rc=$?
./step3 || rc=$?
exit $rc
You can do like this:
#!/bin/bash
# array with all the scripts to be executed
arr=(./step1.sh ./step2.sh ./step3.sh)
# initialize return status to 0
ret=0
# run a loop to execute each script
for i in "${arr[#]}"; do
echo "running $i"
bash "$i"
ret=$((ret | $?)) # return status from previous step bitwise OR with current status
done
echo "exiting with status: $ret"
exit $ret
I know you stated you didn't want to have to maintain every exit code but that's actually pretty easy to do with arrays:
declare -a rcs
./step1.sh ; rcs+=($?)
./step2.sh ; rcs+=($?)
./step3.sh ; rcs+=($?)
Then you can simply step through the array in a loop, looking for the first (or largest, last, average, sum, etc) error code to do with as you wish. The code below shows how to do this to return the first error encountered:
for ((idx = 0; idx < ${#rcs[#]}; idx++)); do
[[ ${rcs[$idx]} -ne 0 ]] && return ${rcs[$idx]}
done
return 0
Similarly, to get the largest error:
rc=0
for ((idx = 0; idx < ${#rcs[#]}; idx++)); do
[[ ${rcs[$idx]} -gt $rc ]] && rc=${rcs[$idx]}
done
return $rc

What happens if a variable is assigned with command expression in backticks

Below is the code of bash:
a=`echo hello`
echo $a
output is :
hello
But I think it should be:
hello
0
You think wrong ;-)
Putting the command in backticks assigns the output (stdout) from the expression on the right to the variable on the left.
$? gives you the "output status" (or return code) of the command - aka the "0" you were expecting.
So:
a=`echo hello`
Runs the command "echo hello" but instead of echoing to stdout, it "echoes" to varaiable a. So a=whatever_the_command_would_have_written_to_stdout (in this case "hello") - nothing is actually written to stdout because it is "captured" by the ``s
You mistakenly think that a=`echo hello`:
executes echo hello and prints its stdout output directly to the caller's stdout,
and then assigns the exit code (return value) of the echo command to variable $a.
Neither is true; instead:
echo hello's stdout output is captured in memory (without printing to the caller's stdout; that's how command substitutions work),
and that captured output is assigned to $a.
A command's exit code (a return value indicating success vs. failure) is never directly returned in POSIX-like shells such as Bash.
The only way to use an exit code is either:
explicitly, by accessing special variable $? immediately after the command ($? contains the most recent command's exit code)
implicitly, in conditionals (a command whose exit code is 0 evaluates to true in a conditional, any other exit code implies false).
Thus, to achieve what you're really trying to do, use:
echo 'hello' # Execute a command directly (its stdout output goes to the caller's stdout)
a=$? # Save the previous command's exit code in var. $a
echo "$a" # Echo the saved exit code.
As this [ this ] answer already mentioned, the return value for the last executed command is stored in
$? # ${?} is sometimes needed
If you wish a to contain 'hello' and the return value of echo hello in separate lines, ie
hello
0
below is one way to do it
$ a=`echo -en "hello\n" && echo -n $?` # here $? is ret val for 1st echo
$ echo -e "$a"
hello
0
Note
-n with echo suppresses the trailing new line
-e with echo interprets escape sequences
Since && is the logical and operator, the second echo wouldn't have been executed had the first echo failed
Another important point to note is that even the assignment ie
a=b
has a return value.

View exit code of a program (After the program exited)

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

Check the error code for a shell method which returns value

I have a sample shell method which:
Either returns a value after some processing.
Else exit with an exit code if any error occurs.
The sample script is as follows:
a.sh
#!/bin/bash
test(){ # test method
if [ $1 == 2 ]; then # the condition for checking
exit 500 # exit with exit code
else
echo $1 # do some business logic here and return the value
fi
}
I have problem with checking the error code. To use this test method I have another sample script.
b.sh
#!/bin/bash
source a.sh
val=`test $1` # call the test method
if [ $? == 500 ]; then # check the value
echo "here is an error" # error occurs
else
echo $val # no error, do something with returned value
fi
Followings are the output:
Input: ./b.sh 10
Output: 10
Expected output: 10
Input: ./b.sh 2
Output:
Expected output: here is an error
I think there is the problem in b.sh because if [ $? == 500 ]; then is always false. Is there any way to make this condition true or something to get the error code?
The idiomatic way of doing this is:
if val=$(test "$1"); then
echo "$val"
else
echo "An error occurred."
fi
The if statement tests the status of the command (or pipeline) which follows it, and executes the then branch of the status indicated success. The only time you need to explicitly check the value of $? is the rare case of a utility which returns different failure status values (and documents what they mean).
Many people seem to think that [ ... ] and other such things are part of the syntax of the if command. They are not; they are just commands (or builtins) whose names are punctuation. You can use any command whatsoever, or even several in a row; in the latter case, the status checked will be that of the last command.

Resources