How to keep last exit status after test

11,781

Solution 1

$_ 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.

Solution 2

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

Solution 3

command -p ...
test 1 -ne $? && exit $_

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

Solution 4

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.

Share:
11,781

Related videos on Youtube

serhatg
Author by

serhatg

Updated on September 18, 2022

Comments

  • serhatg
    serhatg almost 2 years

    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.

  • serhatg
    serhatg about 9 years
    this is tricky, concise, and the $_ variable seems POSIX-compliant, so i definitively like it. Just change the first line to avoid confusion..
  • Dan Cornilescu
    Dan Cornilescu about 9 years
    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).
  • Janis
    Janis about 9 years
    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
    Janis about 9 years
    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.
  • llua
    llua about 9 years
    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?
  • Jerb
    Jerb about 9 years
    @mikeserv I'm not sure I understand - are you talking about manually assigning to $? or something like that?
  • Janis
    Janis about 9 years
    @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.
  • mikeserv
    mikeserv about 9 years
    @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.
  • Janis
    Janis about 9 years
    @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
    Janis about 9 years
    @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.
  • mikeserv
    mikeserv about 9 years
    @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.
  • terdon
    terdon about 9 years
    Comments are not for extended discussion; this conversation has been moved to chat.
  • Janis
    Janis about 9 years
    (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.
  • serhatg
    serhatg about 9 years
    sorry for messing with POSIX-compilancy (and for quoting bash in the title), this won't be my favourite answer then.
  • Janis
    Janis about 9 years
    @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
    Janis about 9 years
    @mikeserv; But this is, again, prone to continue the (fruitless and endless) discussion, that should not be placed here as @terdon correctly observed.
  • mikeserv
    mikeserv about 9 years
    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.
  • Janis
    Janis about 9 years
    @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).
  • mikeserv
    mikeserv about 9 years
    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.