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.

  • 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 '15 at 13:44
  • ok, what if i want to test for the 127 code instead? (eg. [ $? -ne 127 ] && exit $?) – eadmaster Jun 13 '15 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 – G-Man Jun 13 '15 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 '15 at 3:15
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

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
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.)

  • Comments are not for extended discussion; this conversation has been moved to chat. – terdon Jun 14 '15 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 '15 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 '15 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 '15 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 '15 at 5:36
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.

  • @mikeserv I'm not sure I understand - are you talking about manually assigning to $? or something like that? – David Z Jun 14 '15 at 1:59
7
command -p ...
test 1 -ne $? && exit $_

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

  • 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). – Dan Cornilescu Jun 13 '15 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 '15 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 '15 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 '15 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 '15 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.