What does $? mean in shell scripting? - shell

I encountered $? in one of the shell scripts I work on integrating (not written by me).
Just wanted to confirm that it means the return code of the previous command.
The usage is something like
runSomeCommand $VAR1 $VAR2 $VAR3
processResult $?

$? is the exit status of the last executed command.
ls
....
echo $?
0
$ ls notexistingfile
ls: cannot access notexistingfile: No such file or directory
echo $?
2

Related

Behaviour of 'ECHO' in csh and bash

I was writing few scripts and suddenly stroke me to think the following commands in CSH and BASH shells.
In csh shell
$ echo $?BASH
0
$ echo $?HOME
1
$ echo $? home
0 home
In bash shell
$ echo $?BASH
0BASH
$ echo $?HOME
0HOME
$ echo $? home
0 home
Someone explain the above behaviour. I know the echo $? prints the exit status of the last command but if added with some string then how its working?
If you review the documentation csh indicates:
$?name
${?name}
Substitutes the string 1 if the variable name is set, 0 if it is not.
$?0
Substitutes 1 if the current input file name is known, 0 if it is not.
http://www.mkssoftware.com/docs/man1/csh.1.asp
be careful, $? is diferent to $?name

Unix utility which executes a command and tests its exit status

I am writing automated tests for the Gasoline, an OCaml library implementing application templates. Applications are expected to fail with a prescribed exit code in certain circumstances, like exit code 64 EXIT_USAGE when the application is called with an ill-formed command line:
% ./punishment.byte -x
punishment.byte: illegal option -- x
Usage: punishment.byte [-n number] [-p paragraph] [-c configfile]
Exit 64
Is there a standard Unix utility that can be used to run the subcommand ./punishment.byte -x and exit with status code 0 if the subcommand exited with status code 64? Something like
% expect_status 64 ./punishment.byte -x
punishment.byte: illegal option -- x
Usage: punishment.byte [-n number] [-p paragraph] [-c configfile]
Exit 0
As I am using a Makefile to orchestrate the tests, a legible statement such as expect_status 64 ./punishment.byte -x would be nice to have.
Notes
The Exit line in console interaction examples is informative and not part of the output.
I am well aware that I can write such a tool and how to do it, I just want to be sure there is no standard command doing that already.
The answer to your question is no. There is no standard utility on *nix systems for running a command and testing its exit code against a specific value. Probably because it's trivial to write one yourself.
I'm guessing from the % in your code that you're using zsh. If you're actually using csh (or tcsh), then things work differently.
That said, you can easily write a shell function to do this:
expect_status() {
local expected=$1
shift
"$#"
(( $? == expected ))
}
But that will run the command inside your current shell environment, which may have side effects you don't want. It would probably be better realized as a script - just save it somewhere in your $PATH with the filename expect_status and give it read and execute permission:
#!/bin/bash
expected=$1
shift
"$#"
(( $? == expected ))
Or, eschewing bashisms:
#!/bin/sh
expected=$1
shift
${1+"$#"}
[ $? -eq $expected ]
As suggested, you can check exit code of last command execution by referencing shell variable "$?".
$ ls -bogusOption
ls: invalid option -- 'O'
Try 'ls --help' for more information.
$ echo $?
2
shell can be used as utility to test exit code. say,
$ cat test.sh
#!/usr/bin/env bash
echo "executing bogus option"
ls -bogusOption
if [ "$?" -eq "0" ]; then
echo "command succeeded."
else
echo "command failed"
fi
$ bash -xv ./test.sh
#!/usr/bin/env bash
echo "executing bogus option"
+ echo 'executing bogus option'
executing bogus option
ls -bogusOption
+ ls -bogusOption
ls: invalid option -- 'O'
Try 'ls --help' for more information.
if [ "$?" -eq "0" ]; then
echo "command succeeded."
else
echo "command failed"
fi
+ '[' 2 -eq 0 ']'
+ echo 'command failed'
command failed
Well, in a sense, there is a standard utility: the shell itself:
command1 && command2
The above will only execute command2 if the exit code of command1 is 0. Alternatively, this:
command1 || command2
will only run command2 if the exit code of command1 was not 0.
To check for a specific exit status, you would use $? as described in the other answers:
command; [ "$?" -eq 64 ] && command2
So, the functionality you're looking for is essentially built directly into the shell and, therefore, you won't find a utility designed to do this.

why echo return value ($?) after pipeline always return "0"

I realize the fact but I don't know why:
cat abc | echo $?
if abc does not exist, but above command still return 0. Anyone knows the theory about why?
The reason why it must be this way is that a pipeline is made of processes running simultaneously. cat's exit code can't possibly be passed to echo as an argument because arguments are set when the command begins running, and echo begins running before cat has finished.
echo doesn't take input from stdin, so echo on the right side of a pipe character is always a mistake.
UPDATE:
Since it is now clear that you are asking about a real problem, not just misunderstanding what you saw, I tried it myself. I get what I think is the correct result (1) from a majority of shells I tried (dash, zsh, pdksh, posh, and bash 4.2.37) but 0 from bash 4.1.10 and ksh (Version JM 93u+ 2012-02-29).
I assume the change in bash's behavior between versions is intentional, and the 4.1.x behavior is considered a bug. You'd probably find it in the changelog if you looked hard enough. Don't know about ksh.
csh and tcsh (with $status in place of $?) also say 0, but I bet nobody cares about that.
People with bigger shell collections are invited to test:
for sh in /bin/sh /bin/ksh /bin/bash /bin/zsh ...insert more shells here...; do
echo -n "$sh "
$sh -c 'false;true|echo $?'
done
It does not have anything to do with cat abc, but with the previous command you executed. So the code you get when doing cat abc | echo $? is telling if the previous command in your history was successful or not.
From man bash:
Special Parameters
? - Expands to the exit status of the most recently executed foreground pipeline.
So when you do:
cat abc | echo $?
The echo $? refers to the previous command you used, not cat abc.
Example
$ cat a
hello
$ echo $?
0
$ cat aldsjfaslkdfj
cat: aldsjfaslkdfj: No such file or directory
$ echo $?
1
So
$ cat a
$ cat a | echo $?
0
$ cat aldsjfaslkdfj
cat: aldsjfaslkdfj: No such file or directory
$ cat a | echo $?
1
echo $? will give output of previous command which you have executed before not output of piped command. So, you will always get echo $? as 0 even if command failed before pipe.
You pipe the output from 'cat abc' to 'echo $?' which is not what you want.
You want to echo the exit code of 'cat'
cat abc; echo $?
is what you want. Or simply write it in two lines if you can.

Catch failure in shell script

Pretty new to shell scripting. I am trying to do the following:
#!/bin/bash
unzip myfile.zip
#do stuff if unzip successful
I know that I can just chain the commands together in with && but there is quite a chunk, it would not be terribly maintainable.
You can use the exit status of the command explicitly in the test:
if ! unzip myfile.zip &> /dev/null; then
# handle error
fi
You can use $?. It returns:
- 0 if the command was successfully executed.
- !0 if the command was unsuccessful.
So you can do
#!/bin/bash
unzip myfile.zip
if [ "$?" -eq 0 ]; then
#do stuff on unzip successful
fi
Test
$ cat a
hello
$ echo $?
0
$ cat b
cat: b: No such file or directory
$ echo $?
1
The variable $? contains the exit status of the previous command. A successful exit status for (most) commands is (usually) 0, so just check for that...
#!/bin/bash
unzip myfile.zip
if [ $? == 0 ]
then
# Do something
fi
If you want the shell to check the result of the executed commands and stop interpretation when something returns non-zero value you can add set -e which means Exit immediately if a command exits with a non-zero status. I'm using this often in scripts.
#!/bin/sh
set -e
# here goes the rest

Calling script from script

How can I have my shell script echo to me that the script that it calls has failed?
#!/bin/sh
test="/Applications/test.sh"
sh $test
exit 0
exit 1
#!/bin/sh
if sh /Applications/test.sh; then
echo "Well done $USER"
exit 0
else
echo "script failed with code [$?]" >&2
exit 1
fi
The /Applications/test.sh script should be well coded to exit with conventional status. 0 if it's ok and > 0 if it fails.
Like you can see, no need to test the special variable $?, we use boolean expression directly.
I usually take the following approach:
#!/usr/bin/env bash
test="/Applications/test.sh"
sh "${test}"
exit_status=$?
if [[ ${exit_status} ]] ; then
echo "Error: ${test} failed with status ${exit_status}." >&2
else
echo "Success!"
fi
In terms of best practice, you should not. If a script fails, it should emit an error message before it terminates so that its parent doesn't have to. The main reason for this is that the process knows why it is failing, while the parent can only guess. In other words, you should just write:
#!/bin/sh
test="/Applications/test.sh"
sh $test
Although really, it would be more typical to just write:
#!/bin/sh
/Applications/test.sh
test.sh will emit the necessary error message, and your script will return the same value as did test.sh. Also, in its current form your script will always be successful, even if test.sh actually failed because exit 0; exit 1 is pretty pointless: the exit 1 will never be called.

Resources