Bash: Elegant way to set status?
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 (whichtrue
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. Thusexit 1
andexit 2
andexit 3
- all are irrelevant to&&
; failure is failure regardless of$a
value; that is whyecho 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
Related videos on Youtube
![Ole Tange](https://i.stack.imgur.com/cP1F2.jpg?s=256&g=1)
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, 2022Comments
-
Ole Tange almost 2 years
true && echo foo
will printfoo
.false && echo foo
will not printfoo
.bash -c "exit $a" && echo foo
will printfoo
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 andexit
will causeecho 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 over 6 years@OleTange Yes, I agree with that. The
(( !a ))
thing was a bit fragile. This (above) is actually portable :-) -
mtraceur over 6 yearsI'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 anexec
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 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 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 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 over 6 yearsFair point. Thanks for providing the additional perspective into how you interpreted OP's statement on the matter.
-
mtraceur over 6 yearsSidenote: 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 over 6 yearsOn 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 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 over 6 years@Kusalananda I think in this decision you're probably wiser than most people here :)
-
Sergiy Kolodyazhnyy over 6 yearsI updated my answer , please review the solution
-
Sergiy Kolodyazhnyy over 6 yearsI took a bit of inspiration out of your approach. Care to review my answer ?
-
Ole Tange over 6 yearsIF 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 over 6 yearsI find this cumbersome to write and remember, and much harder to understand than Kusalananda's solution.
-
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"
.