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.

Evgeny
  • 5,476
eadmaster
  • 1,643
  • 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 Says 'Reinstate Monica' 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 03:15

6 Answers6

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

Janis
  • 14,222
  • Comments are not for extended discussion; this conversation has been moved to chat. – terdon Jun 14 '15 at 09: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 08: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 04: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 04:58
  • That's a fair point - the first one. The second - well, I don't recall that you did. I would think I'd remember it. The first point, though, still doesn't answer to the case not working at all in dash, zsh. And I really don't believe there is any such standard - I've combed it in the past - fine-toothed style - and I would probably remember that too. – mikeserv Jun 17 '15 at 05:00
  • 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 05:36
  • No, it was not mentioned alone. dash, by the way, is the standard /bin/sh on Debians - which is (unfortunately) also the most widely used Linux distribution variety out there. I don't want to argue about it - it's not that important. I just wish the answer could be better - as is, it's kinduva lie. – mikeserv Jun 17 '15 at 05:45
7
command -p ...
test 1 -ne $? && exit $_

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

eadmaster
  • 1,643
llua
  • 6,900
  • this is tricky, concise, and the $_ variable seems POSIX-compliant, so i definitively like it. Just change the first line to avoid confusion.. – eadmaster Jun 13 '15 at 15:29
  • 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
  • Are you sure it's POSIX? - I doubt it. - It doesn't work, e.g., with ksh, since $_ is the last argument of the previous command that's not in a conditional command sequence. - In any case it's non-portable. – Janis Jun 13 '15 at 17:11
  • Confirmed; it is not in the POSIX standard (see chapter 2.5.2, "Special Parameters"). (And in the ksh reference book it's also described as "ksh-feature not in POSIX".) Thus non-standard, not portable. I wouldn't use it. – Janis Jun 13 '15 at 17:33
  • 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
  • @llua; You completely missed the obvious point. There was a very first comment here about POSIX, and I explained that this statement was wrong. I think giving such wrong impressions should be corrected to prevent spreading suggestions based on wrong facts. - Feel free to provide specifc bash solutions. And the poster may of course use what he likes. - Elsethread I said: "It's beyond me why he prefers complicated to simple methods.", and I emphasize here "It's beyond me why one would prefer non-portable tricks* if keeping a widely usable solution is so simply."* - YMMV. – Janis Jun 14 '15 at 03:58
  • @Janis - well, in bash anyway, the behavior for $_ is well-defined, at least. The same is not true for your own answer, the behavior of which can be easily altered by shell expansion side-effects. $_ does not have that liability. – mikeserv Jun 14 '15 at 06:16
  • 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 07: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 07:56
  • @mikeserv; Yes, maybe I don't, or you. - POSIX: "If there is no command name, but the command contained a command substitution, the command shall complete with the exit status of the last command substitution performed." – Janis Jun 14 '15 at 08:01
  • 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 08:27
  • @Janis - Call it what you want, but in shell, people sometimes do use command substitutions to produce a value. It happens. And so if there were some command i could run to print an errno for which i needed to test, and i included that in my case pattern in a command substitution, fully confident that the answer given here by you would surely satisfy all of my expectations and more, i believe i would be sorely disappointed with the results. That is, i would be when i finally noticed and eventually tracked the inconsistent returns of my script. And provided it worked at all in the first place. – mikeserv Jun 14 '15 at 09:01
  • sorry for messing with POSIX-compilancy (and for quoting bash in the title), this won't be my favourite answer then. – eadmaster Jun 16 '15 at 17:09
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.

mikeserv
  • 58,310
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.

David Z
  • 912
  • @mikeserv I'm not sure I understand - are you talking about manually assigning to $? or something like that? – David Z Jun 14 '15 at 01: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
Junaid
  • 121