[HN Gopher] The Bourne shell lets you set variables in if expres...
___________________________________________________________________
The Bourne shell lets you set variables in if expressions
Author : justaj
Score : 62 points
Date : 2021-05-15 08:37 UTC (14 hours ago)
(HTM) web link (utcc.utoronto.ca)
(TXT) w3m dump (utcc.utoronto.ca)
| erdos4d wrote:
| Bash rarely impresses me but this is a feature I love about it. I
| write Python normally, I really wish it had this.
| frumiousirc wrote:
| https://www.python.org/dev/peps/pep-0572/
| nijave wrote:
| tldr; `:=` assignment does this in Python 3.8+
|
| while data := something_that_may_be_false():
| # do something with data
| 1337shadow wrote:
| Like many other languages such as PHP, Python, JS, Go ...
| wrnr wrote:
| There is legit use of this in the wild, my favourite example is
| from prosemirror: https://github.com/ProseMirror/prosemirror-
| example-setup/blo...
| rwmj wrote:
| As a regular shell user I find this quite surprising. The article
| omits the important point that the shell variable persists after
| the conditional expression: $ foo= ; if foo=bar;
| then echo $foo ; fi ; echo $foo bar bar
|
| However this work as expected. foo is only set as an environment
| variable for the duration of the command (true) in the condition.
| $ foo= ; if foo=bar true; then echo $foo ; fi ; echo $foo
| (prints two blank lines)
|
| (My examples above use bash rather than the plain Bourne shell).
|
| Now I'm wondering if the shell special-cases this.
|
| Edit: No it doesn't. It just executes the command as normal using
| execute_command in the same context as the "if" statement, so any
| assignments take effect in the script, not in any sub-context.
| https://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd....
| ipnon wrote:
| I use this to great affect in Elixir to name magic numbers as one
| off arguments. Process.sleep(_minute = 1000 * 60)
| twooster wrote:
| You should almost always be running Bash in `-e` (exit-on-error)
| mode. This necessitates precisely the construct mentioned in this
| article.
|
| For example: set -e var="$( false )"
| if [ $? -eq 0 ] ; then echo Ok: "$var" else
| echo Not ok: $? fi
|
| If you run this program, neither "Ok" or "Not ok" will be echoed,
| because the program will exit with an error on the `var=` line.
| (Not to mention the $? on the not-ok line won't work because it
| will be the exit code of the `[` test command in the conditional,
| not the exit code of the captured subshell command).
|
| Instead the following will work: set -e
| if var="$( false )" ; then echo Ok: "$var" else
| echo Not ok: $? fi
|
| Note that this will _not_ work: if ! var="$(
| false )"; then echo Not ok: $? fi
|
| Your output will be "Not ok: 0". This is because negation impacts
| the exit code of the previous command.
| Dr_Emann wrote:
| For simple cases, I'll do that, but for longer commands, I've
| taken to doing
|
| rc=0 long command || rc = $? if (( rc == 0 ))...
| matvore wrote:
| `set -e` has a couple of surprising corner cases and in the
| details is pretty hard to understand. The documentation in `man
| bash` for the `set -e` flag is 28 lines in my terminal, and the
| other flags are 2 or 3 lines.
|
| One such corner case is in pipelines. Only the last command in
| a pipeline can cause the script to terminate.
|
| Another corner case is `foo && bar`, often used as an
| abbreviated `if`, will not exit when `foo` fails.
|
| It is not a significant task to just add `|| exit $?` after any
| command whose failure should cause an abort.
| mgerdts wrote:
| My scripts tend to start with
|
| set -euo pipefail
|
| This addresses the pipeline case you mention and also notices
| use of unitialized variables.
| shawnz wrote:
| Unfortunately Ubuntu's new bash replacement "dash" doesn't
| support "-o pipefail" and will error if it's present
| throw0101a wrote:
| _dash_ is not a _bash_ "replacement". _dash_ is an
| implementation of the Bourne /POSIX shell.
|
| _bash_ , when called as _sh_ , also implements the
| Bourne shell as well (but badly, because it leaks bash-
| isms), but when _bash_ is called as _bash_ it is a super-
| set of Bourne.
|
| * https://en.wikipedia.org/wiki/Almquist_shell#dash
|
| * https://en.wikipedia.org/wiki/Bourne_shell
|
| * https://en.wikipedia.org/wiki/Comparison_of_command_she
| lls
|
| If you want to use super-set functionality adjust your
| shebang accordingly.
| shawnz wrote:
| It replaced what they were using previously, which was
| bash. I am not saying that it is a superset of bash's
| functionality or that it should be expected to support
| bash features.
|
| EDIT: I see what you are saying now. Bash is still
| installed by default despite it not being aliased to
| /bin/sh. So it's still possible to rely on bash features
| if you use it explicitly. For some reason I was under the
| impression bash had also been aliased to dash in the
| default installation. Thanks for the information.
| ufo wrote:
| To be more precise, dash replaced /bin/sh. Ubuntu still
| includes bash in the default installation, IIRC.
| throw0101a wrote:
| As does Debian.
|
| There was a lot of gnashing of teeth when we upgraded to
| Debian 6 and people's (alleged) "/bin/sh" scripts broke.
| Most folks elected to simply change things to
| "/bin/bash".
| [deleted]
| [deleted]
| andsens wrote:
| It's worse than that actually, `set -e`/"errexit" is disabled
| for function calls in ifs. Meaning this: set
| -e fn() { false echo "didn't
| exit" } if fn; then echo "fn()
| succeeded" fi
|
| will output didn't exit fn()
| succeeded
| jbrot wrote:
| Yep, likewise it's also disabled in function calls and sub
| shells that are invoked in an && or || block (i.e., in the
| above case of you change the if statement to "fn && echo ..."
| you'll see the same behavior).
|
| Even worse, you can add the line "set -e" inside the function
| explicitly re-enabling it and it still won't change the
| outcome because errexit wasn't technically unset!
| chubot wrote:
| Yes, Oil fixes this with strict_errexit:
|
| sibling comment:
| https://news.ycombinator.com/item?id=27166719
|
| blog post: https://www.oilshell.org/blog/2020/10/osh-
| features.html
|
| It needs some official documentation, but if you download
| Oil and see any other problems I'm interested! I think I
| fixed all of them.
|
| https://github.com/oilshell/oil/wiki/Where-To-Send-Feedback
| chubot wrote:
| No that is dangerous, consider this: set -e
| myfunc() { date %x # syntax error; returns 1, should
| be +%x echo 'should not get here' }
| if var=$(myfunc); then echo $var # erroneously
| prints 'should not get here' else echo failed
| fi
|
| Then you will ignore failure, which is bad.
|
| This is a variant of the issue that the sibling comment brought
| up -- error handling is disabled inside "if" conditions.
|
| In Oil the whole construct is unconditionally disabled by
| strict_errexit. It's too subtle.
|
| Oil has 2 ways of capturing the exit code, including
| run --assign :status -- mycommand # exit 0 but assign the
| status to a var
|
| and shopt --unset errexit { # explicitly
| disable error handling if you want mycmd
| var status = $? }
|
| I'm looking for feedback to make sure that Oil has indeed fixed
| all of this stuff: https://www.oilshell.org/blog/2020/10/osh-
| features.html
|
| Basically the situation is "damned if you do and damned if you
| don't" in Bourne shell, so you need language/interpreter
| changes to really fix it. The rules are too tricky to remember
| even for shell experts -- there are persistent arguments on
| POSIX behavior that is over 20 years old, simply because it's
| so confusing.
|
| https://github.com/oilshell/oil/wiki/Where-To-Send-Feedback
| jaytaylor wrote:
| That is a pretty subtle and nasty sharp edge! I consider
| myself quite proficient with bash and best practices, and it
| still took me a moment to think through this to understand
| how where things went 'wrong' in your example.
|
| Thanks for your work on Oil shell, I don't yet use it
| regularly but hope it becomes mainstream. I'm definitely
| rooting for you, chubot!
| xyzzy_plugh wrote:
| > The rules are too tricky to remember even for shell experts
| -- there are persistent arguments on POSIX behavior that is
| over 20 years old, simply because it's so confusing.
|
| I don't know, I find it easier to not use set -e. I find it
| significantly easier to just explicitly handle all my errors.
| Having my script exit at some arbitrary point is almost never
| desirable.
|
| I find chaining && and || pretty intuitive.
| var=$(myfunc) && echo OK || { echo
| Not OK exit 1 }
|
| This is pretty contrived. I'd probably put the error handling
| in a function and then only handle the failure scenario:
| var=$(myfunc) || die 'Not OK' echo OK
|
| I never run into problems, this always works as expected, I
| don't need any language or interpreter changes to fix it.
| Once you realize `if` is just syntactic sugar and [ is just
| `test` then the world gets pretty simple.
| chubot wrote:
| The rules without -e are definitely less hairy than the
| rules with -e.
|
| Are you regularly writing shell scripts that check all
| their errors? I'd be curious to take a look if any of them
| are public.
|
| It's not impossible to do this -- git's shell scripts seem
| to do a decent job. However I think the vast majority of
| shell scripts don't check errors, so "set -e" is "closer to
| right", if not right. (And I claim it's nearly impossible
| to be "right" with state of the art with -e -- fixes in the
| shell itself are needed.)
|
| I'll also note that Alpine Linux's apk package manager
| switched to "set -e" a few years ago. They are shell
| experts and even they found it difficult to check all their
| errors without it. apk is more than 1000 lines of shell
| IIRC.
| posix_me_less wrote:
| You don't need quotes in
|
| res="$(... whatever ...)"
|
| This is sufficient:
|
| res=$(... whatever ...)
| adolph wrote:
| It depends on what is being returned and how the result is
| used. In my inexpert experience cat $(find -name <some file
| with spaces in the name>) will fail.
| jolmg wrote:
| They mean specifically on assignment. Though, when in doubt,
| quoting never hurts.
| nijave wrote:
| It's specifically an edge case with variable assignment. If
| it were
|
| export val="$( ... )"
|
| you'd need the quotes again. You don't need quotes in plain
| variable assignments and afaik it goes back to how space
| splitting works in Bash (whether or not you're in a
| "splitting context" or whatever the docs call it). There's a
| semi-related set of rules for when * gets expanded and when
| it's passed through to the underlying command as a literal
| '*' character
___________________________________________________________________
(page generated 2021-05-15 23:01 UTC)