Bash: Elegant way to set status?

5,577

Solution 1

Use a subshell that exits with the specified value.

This will output 5:

a=5; ( exit $a ) && echo foo; echo $?

This will output foo and 0 (note, this zero is set by echo succeeding, not by the exit in the subshell):

a=0; ( exit $a ) && echo foo; echo $?

In your shortened form (without visibly setting a or investigating $? explicitly):

( exit $a ) && echo foo

My answer is one that comes from just taking the question at face value and solving it as a curious quiz, "how may we set $? (using handy notation)". I make no attempt to search for any deeper meaning or function or even context in this case (because non was given). I additionally do not judge the validity, efficiency or usability of the solution. I sometimes, in my answers, do say things like "... but don't do that, because...", followed up with "instead, do this...". In this case, I'm choosing to interpret the question as simply a curious quiz.


Further background was now added to the question.

My take on the issue of forking a subshell is that scripts like GNU configure does this all the time, and that it's not terribly slow unless done in a really tight (long-running) loop.

I can't say much about the use of this in/with GNU parallel as I'm not a frequent user of that software.

Solution 2

Well, return works in a function, so let's make one:

$ ret() { return "${1-0}"; }
$ ret 1 || echo foo
foo
$ ret 123 ; echo $?
123

It needs a bit more setup than a subshell, but is shorter to use after that, and doesn't require forking a subshell.

Solution 3

One option (probably faster but not really more elegant):

test "$a" -eq 0 && echo foo

It executes echo but does not set the exit value to $a, so it is a mediocre solution.

Solution 4

Let's examine your assumptions:

  • true && echo foo will print foo. Correct because && adheres to AND with short circuit logic; because of left-associativity in && and || operators if left-most command returned success exit status 0 (which true does always) , the right-most command to this operator will run.

  • false && echo foo will not print foo. Exactly the opposite case of explained above.

  • bash -c "exit $a" && echo foo will print foo depending on $a. Essentially wrong assumption that $a controls &&. Because && is AND logic, it recognizes only two states - success or failure, where failure is any exit status above 0. Thus exit 1 and exit 2 and exit 3 - all are irrelevant to &&; failure is failure regardless of $a value; that is why echo foo doesn't run.

Therefore the answer is no, there is no more elegant way to do something that operates under wrong assumption anyway. However, if your purpose is to set exit status via command, that's sufficient to use what Kusalananda's answer suggests - a subshell. I personally don't see that any different from bash -c part, except that subshell may be implemented as child process/forked process instead of separate process; still not ideal. Note that $? also shouldn't be set by user via variable - it is intended for commands to communicate success or failure.

Let's switch focus on $a for a second. Why set $? and echo foo, if you can directly check $a ? It makes no sense to place $a value where it doesn't belong anyway, i.e. in $?. I would recommend shifting focus from "elegant"( whatever that means ) to "logical" and "working".


Alright, but let's play your game. Taking a modest inspiration from ilkkachu's answer. This approach, however, removes need to force echo conditional execution. It will go to echo if an only if it provided variable indicates success, and return prevents its execution.

$ thing(){ [ "${1-0}" -eq 0 ] || return "$1" ; echo "foo"; }
$ thing
foo
$ thing 0
foo
$ thing 1
$ echo $?
1
$ thing 2
$ echo $?
2

What may not be obvious is that you can do thing $a

Share:
5,577

Related videos on Youtube

Ole Tange
Author by

Ole Tange

I am strong believer in free software. I do not believe in Santa, ghosts, fairies, leprechauns, unicorns, goblins, and gods. Author of GNU Parallel.

Updated on September 18, 2022

Comments

  • Ole Tange
    Ole Tange almost 2 years
    • true && echo foo will print foo.
    • false && echo foo will not print foo.
    • bash -c "exit $a" && echo foo will print foo depending on $a.

    Is there a more elegant way to write the last one? It seems a bit much having to start a shell simply to set the exit value. I am thinking something like:

    • return $a && echo foo
    • exit $a && echo foo

    except return only works in functions and exit will cause echo foo never to be run.

    Elegant in this context means:

    • Easy to understand
    • Portable
    • Easy to read
    • Easy to write
    • Easy to remember
    • Short
    • High performance

    E.g. if true took the exit code as an argument (and this was a documented feature), it would be very elegant.

    So is there a more elegant way?

    Background

    One of the places this could be used is for:

    $ parallel 'setexitval {= $_%=3 =} || echo $?' ::: 0 1 2 3  
    

    Kusalananda's version is probably the best. It is clear what is going on. It is not long and cumbersome to write. It is easy to remember and portable. The only drawback is that it forks another shell (it costs ~0.5 ms):

    $ parallel '(exit {= $_%=3 =}) || echo $?' ::: 0 1 2 3  
    

    Another situation is when you want to simulate an error condition. Say, if you have a long running program, that sets different exit codes, and you are writing something that will react to the different exit codes. Instead of having to run the long running program, you can use (exit $a) to simulate the condition.

  • Kusalananda
    Kusalananda over 6 years
    @OleTange Yes, I agree with that. The (( !a )) thing was a bit fragile. This (above) is actually portable :-)
  • mtraceur
    mtraceur over 6 years
    I'm not -1'ing because of this, but OP explicitly says "It seems a bit much having to start a shell simply to set the exit value." - and a subshell is doing exactly that: starting a separate shell process. (It's just a fork without an exec so it's more efficient than a full shell invocation because it can take advantage of CoW memory pages and other efficiencies in modern systems, but I feel like this answer should more explicitly draw attention to this fact.
  • Kusalananda
    Kusalananda over 6 years
    @mtraceur I understand what you're saying, but I believe that the main point of the question was one of notation, and I interpreted the issue with "starting a shell" as "bach -c "exit $a" is too much to type".
  • mtraceur
    mtraceur over 6 years
    +1: good tackling the probable conception underlying the question (especially given the limited insights the question actually gives into it). As an aside, I would suggest highlighting ilkkachu's answer at least as prominently as Kusalanda's, because the function route is more efficient and with the right function naming will be more readable than the naked subshell route (and while a function wrapper for the subshell method would gain the same readability advantage, it would lose the only advantages of conciseness and radical-portability-to-ancient-Bourne-shells).
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 6 years
    @mtraceur Agreed, since ilkkachu's answer doesn't produce a separate process. Conceptually, though...even function seems as reinventing wheel where one even doesn't need a wheel
  • mtraceur
    mtraceur over 6 years
    Fair point. Thanks for providing the additional perspective into how you interpreted OP's statement on the matter.
  • mtraceur
    mtraceur over 6 years
    Sidenote: one advantage to this naked exit-in-subshell method (without a function of any sort) besides conciseness over the function-return method: radical portability to every Bourne-like shell, as far as I know. (Not that it makes a difference in practice, because every Bourne-like shell in remotely practical use today has proper functions without any of the downsides (like wiping global $@) that would effect this.)
  • mtraceur
    mtraceur over 6 years
    On further thought, I'm giving this a +1, despite my initial crticism. Because OP asked for "elegant", and this is certainly a good answer for many people's definitions of "elegant".
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 6 years
    @Kusalananda And it's a decent answer, as always when it comes to your answers. Just the "quiz" itself seems to make wrong assumptions, which OP despite repeated comments did not clarify. We can set $?, it is indeed curious how to do it, but it doesn't mean it's intended to be set by the user.
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 6 years
    @Kusalananda I think in this decision you're probably wiser than most people here :)
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 6 years
    I updated my answer , please review the solution
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 6 years
    I took a bit of inspiration out of your approach. Care to review my answer ?
  • Ole Tange
    Ole Tange over 6 years
    IF that function had been part of Bash, I would find this more elegant than Kusalananda's answer. But having to define a function is around as cumbersome as using bash -c "exit $a".
  • Ole Tange
    Ole Tange over 6 years
    I find this cumbersome to write and remember, and much harder to understand than Kusalananda's solution.
  • ilkkachu
    ilkkachu over 6 years
    @OleTange, if you'd deigned to show the context where you needed this in the first place (or what the exact definition for elegance was), I wouldn't have suggested this. But you didn't, and I explicitly mentioned this needs more setup than running a subshell (namely, defining the function). I said "a bit" since only one line isn't much in a multiline script, but it obviously is too much for a one-liner. After that setup, it's much less cumbersome than bash -c "exit $a".