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
Related
I read this question, but my problem is that I have "plenty" of commands to run; and I need a solution that works for a systems calls.
We have an exit task that basically triggers a lot of "cleanup" activity within our JVM. The part I am working on has to call a certain script, not once, but n times!
The current implementation on the Java side creates n ProcessBuilder objects; and each one runs a simple bash script.sh parm ... where parm is different on each run.
Now I want to change the Java side to only make one system call (instead of n) using ProcessBuilder.
I could just use the following command:
bash script.sh parm1 ; bash script.sh parm2 ; ... ; bash script.sh parmN
Now the thing is: if one of the runs fails ... I want all other runs to still take place; but I would like to get a "bad" return code in the end.
Is there a simple, elegant way to achieve that, one that works with command strings coming from system calls?
You can build up the return codes in a subshell as you go, then check them at the end using arithmetic evaluation. E.g., on my test system (cygwin), at a bash prompt:
$ ( r=; echo "foo" ; r=$r$?; echo "bar" ; r=$r$? ; echo "baz" ; r=$r$? ; (($r==0)) )
foo
bar
baz
$ echo $?
0 <--- all the commands in the subshell worked OK, so the status is OK
and
VVVV make this echo fail
$ ( r=; echo "foo" ; r=$r$?; echo "bar" 1>&- ; r=$r$? ; echo "baz" ; r=$r$? ; (($r==0)) )
foo
-bash: echo: write error: Bad file descriptor
baz
$ echo $?
1 <--- failure code, but all the commands in the subshell still ran.
So, in your case,
(r=; bash script.sh parm1 ; r=$r$?; bash script.sh parm2 ; r=$r$?; ... ; bash script.sh parmN r=$r$?; (($r==0)) )
You can also make that slightly shorter with a function s that stashes the return code:
$ (r=;s(){ r=$r$?;}; echo "foo" ; s; echo "bar" 1>&-; s; echo "baz" ; s; (($r==0)) )
foo
-bash: echo: write error: Bad file descriptor
baz
$ echo $?
1
s(){ r=$r$?;} defines a function s that will update r. Then s can be run after each command. The space and semicolon in the definition of s are required.
What's happening?
r= initializes r to an empty string. That will hold our return values as we go.
After each command, r=$r$? tacks that command's exit status onto r. There are no spaces in r, by construction, so I left off the quotes for brevity. See below for a note about negative return values.
At the end, (($r==0)) succeeds if r evaluates to 0. So, if all commands succeeded, r will be 000...0, which equals 0.
The exit status of a subshell is the exit status of its last command, here, the (($r==0)) test. So if r is all zeros, the subshell will report success. If not, the subshell will report failure ($?==1).
Negative exit values
If some of the programs in the subshell may have negative exit values, this will probably still work. For example, 0-255100255 is a valid expression that is not equal to zero. However, if you had two commands, the first exited with 127, and the second exited with -127, r would be 127-127, which is zero.
To avoid this problem, replace each r=$r$? with r=$r$((! ! $?)). The double logical negation $((! ! $?)) converts 0 to 0 and any other value, positive or negative, to 1. Then r will only contain 0 and 1 values, so the (($r==0)) test will be correct. (You do need spaces after each ! so bash doesn't think you're trying to refer to your command history.)
A binary OR-ing of all exit codes will be zero (0) "if" and "only if" all exit codes are zero (0).
You could get a running exit code with this simple arithmetic expression:
excode=((excode | $?))
To run all parameters, you could use an script ("callcmd") like:
#!/bin/bash
excode=0
for i
do cmd "$i"
excode=((excode | $?))
done
echo "The final exit code is $excode"
# if [[ $excode -ne 0 ]]; exit 1; fi # An alternative exit solution.
Where this script ("callcmd") is called from java as:
callcmd parm1 parm2 parm3 … parmi … parmN
The output of each command is available at the usual standard output and the error strings (if any) will also be available in the stderr (but all will be joined, so the command "cmd" should identify for which parm is emitting the error).
r=0; for parm in parm1 parm2 ... parmN; do
bash script.sh "$parm" || r=1
done
exit "$r"
You can do it like this
bash script.sh parm1 || echo "FAILED" > some.log ; bash script.sh parm2 || echo "FAILED" > some.log; ... ; bash script.sh parmN|| echo "FAILED" > some.log
Then check if there is some.log file.
|| - It's simple bash logical or ( executed if exit status of previous one is non-zero)
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
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.
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 found some strange thing in bash and I can't understand how it works.
[test ~]$ a=""
[test ~]$ $a && echo 1
1
[test ~]$ $a
[test ~]$ echo $?
0
Why does $a (which is empty) return 0? Is it somehow transformed to empty command?
If I add quotes or write empty string before &&, it will return error. While empty command returns 0.
[test ~]$ "$a" && echo 1
-bash: : command not found
[test ~]$ "" && echo 1
-bash: : command not found
[test ~]$ `` && echo 1
1
So, what is happening when I type $a?
You seem to confuse bash with some other programming language. Variables get replaced, then what is left gets executed.
"$a"
This is the content of a, between quotation marks. a is empty, so this is equivalent to:
""
That is not a command. "Command not found." As there was an error, the execution was not successful (shell return code is not 0), so the second half of the command -- && echo 1 -- does not get executed.
Backticks...
``
...execute whatever is between them, with the output of that command replacing the whole construct. (There is also $() which does the same, and is less prone to being overlooked in a script.) So...
`echo "foo"`
...would evaluate to...
foo
...which would then be executed. So your...
``
...evaluates to...
<empty>
...which is then "executed successfully" (since there is no error).
If you want to test the contents of a, and execute echo 1 only if a is not empty, you should use the test command:
test -n "$a" && echo 1
There is a convenient alias for test, which is [, which also conveniently ignores a trailing ]...
[ -n "$a" ] && echo 1
...and a bash-ism [[ that "knows" about variable replacement and thus does not need quotation marks to avoid complaining about a missing argument if $a does indeed evaluate to empty...
[[ -n $a ]] && echo 1
...or, of course, the more verbose...
if [[ -n $a ]]
then
echo 1
fi
Ah. Missed the core part of the question:
$a && echo 1
This is two statements, separated by &&. The second statement only gets executed if the first one executes OK. The bash takes the line apart and executes the first statement:
$a
This is...
<empty>
...which is "successful", so the second statement gets executed. Opposed to that...
&& echo 1
...is a syntax error because there is no first statement. ;-) (Tricky, I know, but that's the way this cookie crumbles.)
a=""
or
a=" " #a long empty string
then
$> $a
will return 0
$> $noExistVar
will also return 0.
They get "executed", in fact, nothing gets executed. same as you press enter or pressing 10 spaces then enter, you get return code 0 too.
$> && echo 1
this will fail, because bash will try to execute the first part, in this case it is missing.
$> $notExistVar && echo 1
Here it works, I guess bash found the first part the $whatever, therefore no syntax error. Then "execute" it, well nothing to execute, return 0, (same as pressing enter after prompt), then check, if first part returned 0, exec the cmd after &&.
I said guess because I didn't check bash's source codes. Please correct me if it is wrong.
the $> " " && echo 1 case, I think it is clear, don't need to explain.