8

Is it possible to keep the last command exit status ($?) unaltered after a test?

E.g., I would like to do:

command -p sudo ...
[ $? -ne 1 ] && exit $?

The last exit $? should return the sudo exit status, but instead it always returns 0 (the exit code of the test).

Is it possible to do that without a temporary variable?

Another example to clarify further:

 spd-say "$@"
 [ $? -ne 127 ] && exit $?

In this case i want to exit only if the first command is found (exit code != 127). And i want to exit with the actual spd-say exit code (it may not be 0).

EDIT: I forgot to mention that i prefer a POSIX-complaint solution for better portability.

I use this construct in scripts where i want to provide alternatives for the same command. For instance, see my crc32 script.

The problem with temporary variables is that they could shadow other variables, and to avoid that you must use long names, which is not good for code readability.

4
  • No, but you can just do if ! command -p sudo; then exit; fi which would have the same results for your example.
    – jordanm
    Jun 13, 2015 at 13:44
  • ok, what if i want to test for the 127 code instead? (eg. [ $? -ne 127 ] && exit $?)
    – eadmaster
    Jun 13, 2015 at 13:47
  • 1
    @jordanm: Really?  If the OP has presented the code/logic he meant to, and if I'm reading it correctly, he wants the script to exit if the sudo command succeeds (i.e., if sudo exits with status 0).  But, in your code, the script keeps running (doesn't exit) if sudo succeeds Jun 13, 2015 at 14:37
  • @G-Man - I'm pretty sure the asker's conditions are actually based on the return of command and not sudo at all. That's what is meant by i want to exit only if the first command is found (exit code != 127) and is a specified return for command when the command it invokes is not found. I guess the problem is that invoking sudo as part of the test allows for sudo squashing the return of command in the first place and so skewing the test.
    – mikeserv
    Jun 14, 2015 at 3:15

6 Answers 6

9

There are various options to handle the exit status reliably without overhead, depending on the actual requirements.

You can save the exit status using a variable:

command -p sudo ...
rc=$?
[ "$rc" -ne 1 ] && echo "$rc"

You can directly check success or failure:

if  command -p sudo ...
then  echo success
else  echo failure
fi

Or use a case construct to differentiate the exit status:

command -p sudo ...
case $? in
(1) ... ;;
(127) ... ;;
(*) echo $? ;;
esac

with the special case asked in the question:

command -p sudo ...
case $? in (1) :;; (*) echo $?;; esac

All those options have the advantage that they are conforming to the POSIX standard.

(Note: For illustration I used echo commands above; replace by appropriate exit statements to match the requested function.)

7
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – terdon
    Jun 14, 2015 at 9:30
  • 1
    (I wonder why the last comment did not move to chat.) - As already thoroghly explained and backed up by POSIX quotes in the chat discussion (see there for details); 1.) ("$(get_errnos)") is an artificial addition of a command substitution where comparisons with actual error codes are asked for in the question, 2.) There's clear in the standard what changes $? (and thus what doesn't change it), and 3.) there are no side effects with that construct (unless, as you did, you introduce them unnecessarily). - OTOH, since you mind, the other answer that you prefer is clearly non-standard.
    – Janis
    Jun 15, 2015 at 8:05
  • 1
    @mikeserv; It's annoying (and misleading!) not only because you apply double standards! If you'd apply the same artificial $(get_errnos) code to any other solutions (( exit 42 ); test "$(get_errnos)" -ne $? && echo $_) they also don't work. (You preferred to bring my standard solution in miscredit, not the other non-standard hack(s).) - Of course you can add arbitrary code to any answers and spoil it. - And WRT to the change of $?; just because you can't find it doesn't mean it's not true. (I've already provided in our discussions even the keywords to search for in POSIX.)
    – Janis
    Jun 17, 2015 at 4:50
  • 1
    @mikeserv; But this is, again, prone to continue the (fruitless and endless) discussion, that should not be placed here as @terdon correctly observed.
    – Janis
    Jun 17, 2015 at 4:58
  • 1
    @mikeserv; I said quite at the beginning where you first mentioned zsh (first mentioned alone; later you added also dash) that I think it must be a bug there, given that the case exit status and setting of $? seems well defined in POSIX. - And also here, again, you apply double standards; the other hack, e.g., is neither standard, nor does it reliably work in other standard shells (e.g. not in ksh). - My proposals are standard and work in bash (mostly used on Linux) and ksh (the predominating shell in commercial Unixes).
    – Janis
    Jun 17, 2015 at 5:36
7
command -p ...
test 1 -ne $? && exit $_

Use $_, which expands to the last argument of the previous command.

13
  • 1
    Valid for this particular example, but only usable if there are no other command in between the $? and $_ references. IMHO it's better to stick to a consistent method which works in other cases (and can also help with the code readability). Jun 13, 2015 at 15:46
  • 4
    The shell was specifically named twice, once in the title and once as a tag. Nor was portability mentioned anywhere in the question, thus i gave a answer that works in said shell. What other weird restrictions are going to be made up?
    – llua
    Jun 13, 2015 at 18:03
  • 1
    @mikeserv; In case you missed it: I said: "There was a very first comment here about POSIX." which had to be corrected. No more, no less. - As thoroughly debated with you and explained there, all three suggestions in the other answer are well defined by POSIX. No need to repeat your (IMO wrong) opinion here, or start another iteration of the dispute.
    – Janis
    Jun 14, 2015 at 7:04
  • 1
    @mikeserv; The expansion side effects in the case patterns, the only place that would matter in theory (but not in the given question), is a constructed case. - In any case, your shell command substitution would propagate the result of the embedded command, usually other expansions will not affect the return state anyway if there's no commands involved that can create errors (mind x=${a!b} cases, but irrelevant here). - What do you mean by "command without command name"?
    – Janis
    Jun 14, 2015 at 7:56
  • 1
    @mikeserv; It can be clobbered only by ad hoc made up unnecessary code. There's absolutely no grey area if you take the suggestion without unnecessarily introducing artificial nonsense. The requirements were absolutely clear in this case: 1. execute a comand, 2. check exit code, 3. return exit code. - Do you get that? - You changed that requirement arbitrarily to just make up an argument.
    – Janis
    Jun 14, 2015 at 8:27
6

You can define (and use) a shell function:

check_exit_status()
{
    [ "$1" -ne 1 ] && exit "$1"
}

Then

command -p sudo ...
check_exit_status "$?"

Arguably, this is "cheating", since it makes a copy of $? in the check_exit_status argument list.

This may seem a little awkward, and it is.  (That sometimes happens when you impose arbitrary constraints on problems.)  This may seem inflexible, but it isn't.  You can make check_exit_status more complex, adding arguments that tell it what test(s) to do on the exit status value.

3

$_ will work in (at least) interactive dash, bash, zsh, ksh (though apparently not in a conditional statement as requested) and mksh shells. Of those - to my knowledge - only bash and zsh will also populate it in a scripted shell. It is not a POSIX parameter - but is fairly portable to any modern, interactive shell.

For a more portable solution you can do:

command -p sudo ...
eval '[ "$?" = 127 ] || exit '"$?"

Which basically allows you to expand the initial value for $? into the tail of the script before ever even testing its value at its head.

But anyway, since you appear to be testing whether or not the command sudo can be found in the shell's builtin -p portable path string with command, I would think you could go at it a litte more directly. Also, just to be clear, command won't test for the location of any arguments to sudo - so it is only sudo - and nothing it invokes - which is relevant to that return value.

And so anyway, if that is what you're trying to do:

command -pv sudo >/dev/null || handle_it
command -p  sudo something or another

...would work just fine as a test without any chance of errors in the command sudo runs returning in such a way that might skew the results of your test.

0

To answer your direct question, no, it's not possible to keep $? unaltered. And this is one of those cases where I suspect you're focusing on the wrong problem. A temporary variable is the standard and preferred way to get the effect you're looking for. It's so standard that I would suggest abandoning (or rethinking) whatever reason you think you have for not wanting to use one; I highly doubt that it's worth the extra complexity added by any other method.

If you're just asking out of simple curiosity, then the answer is no.

1
  • @mikeserv I'm not sure I understand - are you talking about manually assigning to $? or something like that?
    – David Z
    Jun 14, 2015 at 1:59
0

Here's my code snippet to retain a previous exit status without exiting from current script/shell

EXIT_STATUS=1
if [[ $EXIT_STATUS == 0 ]]
then
  echo yes
else
  (exit $EXIT_STATUS)
fi

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .