[HN Gopher] Bash Error Handling ___________________________________________________________________ Bash Error Handling Author : sohkamyung Score : 212 points Date : 2020-10-09 06:53 UTC (1 days ago) (HTM) web link (wizardzines.com) (TXT) w3m dump (wizardzines.com) | major505 wrote: | This is usefull. I know a guy who didnt treat unset vairable, so | in the script where he would remove with rm -rf /$dir-old-back | his script removed all directories. The problem is that all his | backups were in an external drive mounted in a directory, so he | removed all his backups also. A hell month for him . | loevborg wrote: | `-o pipefail` is not without its gotchas bash | -eo pipefail -c "cat /etc/services | head -1; echo never got | here" | aidenn0 wrote: | I hate pipefail. | | I find set -e also has some gotchas. '|| die' added to the end | of each line is uglier but more obvious in many cases. | [deleted] | stonewareslord wrote: | What is the gotcha here? You're telling it to pipefail and exit | on fail, so it exits. | tux1968 wrote: | The gotcha is perhaps failing to recognize that cat will | actually fail in this simple pipeline because its output is | truncated "unexpectedly" by head exiting after completing its | job properly. (Maybe you could argue that head should be a | good citizen and read its input to completion) | | Where this will work as expected: bash -eo | pipefail -c "head < /etc/services -1|cat ; echo got here" | arendtio wrote: | I like the notion of 'set -e' and at the same time I hate it. | | First, because it behaves inconsistently across shells/versions | [1] and second, because it doesn't always work as expected. For | example, when you depend on the 'set -e' behavior in a function | and _call_ the function from within a condition, the 'set -e' | has no effect at all. So you better don't count on 'set -e'. | | But don't expect me to follow my own advice, as not using 'set | -e' isn't a good option either... | | [1] https://www.in-ulm.de/~mascheck/various/set-e/ | chubot wrote: | Yup, I am fixing this with Oil: | https://news.ycombinator.com/item?id=24740842 (2 of 4 problems | fixed so far, more problems welcome) | | Try it out on your shell scripts and let me know what happens | :) OSH has the "broken" POSIX/bash behavior to maintain | compatibility, while Oil opts you in to the better error | semantics. | JeremyBanks wrote: | https://dev.to/banks/stop-ignoring-errors-in-bash-3co5 | polyrand wrote: | In Bash 4.4+ you can use: shopt -s | inherit_errexit 2>/dev/null || true | | To make subprocesses inherit the errexit flag. | chinigo wrote: | Lifesaver! Without this, error handling in bash functions is a | gigantic, unintuitive pain. Thank you for bringing this to my | attention. | lazyant wrote: | I'd add TRAP DEBUG (running line by line) to the toolset | harisund wrote: | I thought people shouldn't post anything about bash on HN? The | minute you post something about bash immediately you will draw | out a whole bunch of folks from the wood works talking about how | bash sucks and should never be used for anything more than 3 or 4 | lines and how they replaced bash with python or some thing else, | immediately in turn drawing out a bunch of other folks talking | about how bash should be replaced with power shell and how you | can parse objects better ... . | exdsq wrote: | I had a really fun project earlier in the year prototyping a | load testing tool for a blockchain in Bash while 4 other | developers wrote a 'better' one in Haskell. Bash can get | results quickly, although it's not maintainable! Still, a | decent kloc or two of bash with performance results within the | sprint. | heresie-dabord wrote: | I had become annoyed by the Python bigots who will tell us | about how easy to read their language is because its notation | is clean, how all its functionality is "intuitive", how any | combination of Python-based Rube-Goldberg Machine systems is | the best. | | But then came the PowerShell people... | michaelcampbell wrote: | Any programming topic spawns a bunch of comments about | readability, or examples of code-golf. It's the nature of | things. | asicsp wrote: | Others in this on-going series: | | * https://wizardzines.com/comics/environment-variables | | * https://wizardzines.com/comics/brackets-cheatsheet | | * https://wizardzines.com/comics/bash-quotes | | * https://wizardzines.com/comics/bash-if-statements/ | GolDDranks wrote: | I almost always write POSIX shell instead of bash for | compatibility; it would be nice to see collections for tips and | tricks specifically for POSIX shell. I know, for example, that -o | pipefail doesn't exist in plain POSIX shell. I wonder what's are | the best practices when you can't use it. | peterwwillis wrote: | Read the _ash_ or _dash_ man pages and just use what they give | you. That 's pretty much Bourne shell which is pretty much | POSIX. | still_grokking wrote: | > -o pipefail doesn't exist in plain POSIX shell | | I think this changed lately[1]. No clue where's implemented, | though. | | [1] https://www.austingroupbugs.net/view.php?id=789 | m463 wrote: | ok some low-grade tips: | | cleanup="true" | | later: if $cleanup; then a ; else b ; fi | | or slightly uglier $cleanup && rm -f mytempfile | | I like one-line functions: die() { echo "$@" | 1>&2; exit 1; } | asicsp wrote: | > _it would be nice to see collections for tips and tricks | specifically for POSIX shell_ | | Check out https://freebsdfrau.gitbook.io/serious-shell- | programming/ | sigzero wrote: | > it would be nice to see collections for tips and tricks | specifically for POSIX shell | | https://www.shellscript.sh/ | | There is a Facebook group and I have emailed the author as well | with questions. Nice guy. | fpoling wrote: | I also write my scripts to stay /bin/sh compatible. If this is | not enough, then a real scripting language should be used, not | bash. | | But I very much agree that lack of pipefail is painful. If I | know that output on the left of the pipe is small, I read it | into a variable and then use printf | right part. If the output | can be big, I use a helper function to emulate it that I copy- | paste. | tpoacher wrote: | I'm surprised nobody's mentioned | [expect](https://likegeeks.com/expect-command/) yet in this | thread :) | x87678r wrote: | bash is not fit for any moderately complex task. | | I dont understand its popularity. Normally sane people who like | unit testing, CICD, SOLID principles, quality tools end up with a | bunch of crappy scripts holding everything together. Please | avoid. | peterwwillis wrote: | It's fantastic for simple tasks, which is why so many people | use it. It also turns out that many complex tasks can be | reduced to a collection of simple tasks, otherwise known as "a | bunch of crappy scripts". | | I encourage all my teams to avoid built-in CI/CD features and | plugins and just script what they want in a Docker container. | It ends up being easier to maintain, breaks less often, and is | more portable. | chubot wrote: | https://www.oilshell.org/blog/2018/01/28.html#shouldnt-we-di... | pferde wrote: | Also known as "bash strict mode". It's a godsend if you're | unlucky enough to have to deal with bash scripting. | | http://redsymbol.net/articles/unofficial-bash-strict-mode/ | | EDIT: The comic strip would be better in three rows of two panels | - a row for each set flag. | pzmarzly wrote: | Another useful flag is -x, which enables tracing (Bash will print | each command before executing it). | | You can disable it again with set +x (same goes for +e, +u and | afaik +o pipefail) | | Also, please use shellcheck - https://www.shellcheck.net/ | | EDIT: Also, please don't modify scripts while they are running. | aidenn0 wrote: | Here's a workaround for modifying scripts while they are | running; it works because function bodies are read and parsed | entirely when they are defined. main() { | script goes here exit } main "$@" | 0xmohit wrote: | Specifying PS4 when using the -x flag can be even more helpful | while debugging. The variable PS4 denotes the value is the | prompt printed before the command line is echoed when the -x | option is set and defaults to : followed by space. | | A number of useful debugging tips are listed at | <https://wiki.bash-hackers.org/scripting/debuggingtips>. | _where wrote: | If it's not by default there's a reason. Bash is literally | running commands in a shell session. Think terminal session. When | a command fails, would you want the terminal session to end? | That'd be annoying. | | Same theory for unset variable. Referencing an undefined variable | shouldn't break your session. Why initialize it anyway? It's more | code to change if you don't use it if you have to initialize it | when it might not be needed. And, you'd have to call the script | with A= just to check A wasn't defined, and in the process now | you have A assigned to an empty string, instead of only | defaulting to one when called, which uses more memory and | execution time. | | The pipeline doesn't die because && and || and parens are | seriously helpful for one-liners. | | Don't think of it as a script. Think of it as a script for a | shell. | pwdisswordfish4 wrote: | Sometimes the only reason is backwards compatibility. | IshKebab wrote: | A compelling argument for why Bash scripts shouldn't exist at | all. | chubot wrote: | https://www.oilshell.org/blog/2018/01/28.html#shouldnt-we- | di... | Waterluvian wrote: | Thinking of it that way, it becomes clear why you would want to | switch to something like Python for more complex workflows. | | Bash is brilliantly useful for a lot of things. I'm not bashing | it. | cryptonector wrote: | Except `set -e` is stupid because it is disabled all the way down | when a command occurs in a conditional, meaning this: | function foo { false; true; } foo || echo foo failed | | prints nothing! | | From the bash manual page: | | | If a compound command other than a subshell returns a non-zero | status because a command failed while -e was being ignored, the | shell does not exit. | | POSIX says the same thing, so this is true of all POSIX-y shells. | | This means you really have to check for errors you care about, | and `set -e` is useless. Ugh! | mehrdadn wrote: | Wait until you realize set -e; (false && true); | echo hi | | does nothing, but set -e; false && true ; echo | hi | | prints hi. | cryptonector wrote: | That one is OK. What's NOT OK is that conditionals disable set | -e in functions that appear in the conditionals, so: | function foo { false; true; } foo || echo foo failed | | prints nothing. | mehrdadn wrote: | Ouch, that's horrible. Thanks! | chubot wrote: | Yeah this is a variation on problem 2 I mentioned here, except | with subshells rather than functions. | | https://news.ycombinator.com/item?id=24740842 | | The problem actually has more to do with the definition of $? | than the set -e behavior itself. And the fact that POSIX | specifies that the error a the LHS of && is ignored (a | fundamental confusion between true/false and success/error) | | The exit code of the function is not what you expect, or the | exit code of the subshell is not what you expect. | | I made a note of it on the bug ... still thinking about what | the solution to that one is. | | (The other solutions are inherit_errexit, more_errexit, and a | "catch" builtin.) | Sebb767 wrote: | It's a bit surprising at first, but I'd like to think most | people deeper into bash are aware of Subshells. | mehrdadn wrote: | Merely knowing about when subshells occur doesn't explain | this. You need to know that && and || suppress -e. | alanbernstein wrote: | I know $() invokes a subshell, but does () ? | | My guess was that the () affects the order of operations | between ; and &&, so the first line is three commands, while | the second line is two. | asicsp wrote: | See https://www.gnu.org/savannah- | checkouts/gnu/bash/manual/bash.... | | "Placing a list of commands between parentheses causes a | subshell environment to be created" | | "Placing a list of commands between curly braces causes the | list to be executed in the current shell context" | gravitas wrote: | It's horribly more complex than just that, the person | you're replying to is "more correct" because of the rules | of `set -e` which is critical in this thread; a singular | command followed by && is treated differently than a | compound command. Pull up `man bash` and search | `/set.*abef` to read the rather long and involved | paragraph for the `-e` option, running in a subshell is | only part of it. | asicsp wrote: | >I know $() invokes a subshell, but _does ()_ ? | | >My guess was that the _() affects the order of | operations_ between ; and &&, so the first line is three | commands, while the second line is two. | | Emphasis mine. My understanding was that the question is | simply about whether () invokes a subshell or not | (irrespective of set -e) | aidenn0 wrote: | It's a subshell. Order of operations is unchanged, and if | you just want a block of commands you can use { } | exdsq wrote: | Other tips I've found after a year or two of using Bash for many | things it shouldn't be used for: | | - Use shellcheck (static analysis/linter) | https://www.shellcheck.net/ | | - Use shunit2 (unit tests) https://github.com/kward/shunit2 | | - Use 'local' or 'readonly' to annotate your variables | | - Trap ctrl+c to gracefully exit (details here | https://www.tothenew.com/blog/foolproof-your-bash-script-som...) | | - Stick to long-form options for readability (--delete over -d | for example) | | - #!/usr/bin/env > #!/bin/bash for portability | | - Consider setting variable values in a file and importing it at | the top of your script to improve refactoring | | - You will eventually forget how your scripts work - seriously | consider if Bash is your best option for anything that needs to | last a while! | txutxu wrote: | > - #!/usr/bin/env > #!/bin/bash for portability | | Yes... but do not take that as a "cargo cult script shebang". | | If you're a sysadmin writing a script for a company with 2k | linux servers, that has a policy of "we only use linux version | Foo X"... and we do not use other bash in the system than | /bin/bash (no bash compiled by hand, no multiple versions of | bash, etc)... then portability via "env" does not make sense. | | If you have two laptops and a raspberry at home, with debian or | arch, and you write a script for yourself... then portability | via "env" does not make sense. | | And last but not least... using env is slower. | | See: strace -fc /bin/bash -c ':' | | Vs strace -fc /usr/bin/env bash -c ':' | | On my system, that's 92 syscalls and 3 errors, Vs 152 syscalls | and 8 errors. | | Just to start procesing. | | Diferent levels of system bloat (environment, library paths, | etc) can give different results than my example. | | And as others said... if you're not using GNU/bash syntax and | the script is really simple, the best for portbility is to go | with /bin/sh. strace -fc /bin/sh -c ':' | | On my system 41 syscalls and 1 error... (and less RAM, CPU and | pagefaults). | | If you're not using associative arrays, array indexes, non | POSIX builtin options, and other bash extensions... if the | script is just to join a few commands and variables... it pays | the effort to write it in simple sh, both, for portability and | performance. | asicsp wrote: | Some more useful links: | | * https://mywiki.wooledge.org/BashFAQ | | * https://mywiki.wooledge.org/BashGuide/Practices | | * https://mywiki.wooledge.org/BashPitfalls | | * https://devmanual.gentoo.org/tools-reference/bash/index.html | amarshall wrote: | Note that /bin/bash isn't completely portable (though /bin/sh | is, that's not Bash) as some systems have it at | /usr/local/bin/bash. | m463 wrote: | That's what i've learned... #!/bin/sh is generally what I use | and it survives porting. | | If I need more power I usually switch to python. (Probably | not relevant to a bash discussion though, sorry) | laumars wrote: | You're just reiterating the GPs point of using env instead of | calling bash directly from the shebang: | #!/usr/bin/env bash | | is more portable than #!/bin/bash | | Though personally if I was worried about portability then I'd | just write the script in vanilla sh instead. | spurgu wrote: | > using env instead | | If this method is superior, why hasn't it caught on with | Bash I wonder (it's considered standard with Python and | Node at least)? | | > Though personally if I was worried about portability then | I'd just write the script in vanilla sh instead. | | Agreed. | laumars wrote: | > _If this method is superior, why hasn 't it caught on | with Bash I wonder (it's considered standard with Python | and Node at least)?_ | | Because if you need a portable bash then you target sh. | But if you want a portable Python then you still target | Python. | josefx wrote: | > But if you want a portable Python then you still target | Python | | I am still supporting systems that came with Python 2. | You get portable Python the same way you get portable | bash: build and deploy the interpreter with your code. | laumars wrote: | You're missing my point. If you're targeting Python then | you need Python installed however you don't always know | where that executable might live. Whereas if you're | targeting shell scripts then you can always fallback to | regular Bourne shell if you need portability and that | should always have an executable or simlink in /bin/sh. | | The Python 2 problem is a whole other topic :) | shakna wrote: | I believe this statement agrees with you: | | > #!/usr/bin/env > #!/bin/bash for portability | | Perhaps you misread it? env bash is greater for portability | than bin/bash. | jhardy54 wrote: | Oh, I misread that as an arrow pointing from 'before' to | 'after'. Makes sense now! | williamdclt wrote: | You're not the only one, I spent a minute thinking about | a discussion explaining why the `env` way was better, I | was going to have a rant about people giving | contradictory advice for "portability"! | l0b0 wrote: | I'm working on a book about Bash scripting which is currently | in the review phase, and it includes most of these. For | graceful exit I recommend `trap cleanup EXIT` rather than | specifically trapping SIGINT, mostly because the special exit | signal is triggered no matter why the script is interrupted. I | wouldn't normally recommend pulling out variables into a | separate files until those variables are used by more than one | script. I'd be interested in the rationale for why that helps | refactoring. | corytheboyd wrote: | Huge +1 to using long form options in scrips, even if you're | the sole maintainer of the script. Also if you have a command | that takes many flags, breaking them out onto new lines can | help keep it readable | jancsika wrote: | > - Trap ctrl+c to gracefully exit (details here | https://www.tothenew.com/blog/foolproof-your-bash-script- | som...) | | I'd rather that most devs don't touch that signal. Using that | binding and having a GUI or CLI program continue hanging | because the dev screwed up the cleanup is a real pain. And | someone writing a Bash script is highly likely for doing | something "very clever" with that signal to make my life | harder. | | Or if you're going to do something with it, at least make it | clear you're trolling me. Show me a text add that forces me to | choose my favorite Korean boy band before I can exit, or | something in that vein. | jmholla wrote: | In that case, you could not catch CTRL-C in the cleanup. | nemetroid wrote: | Agree that developers should be very careful about messing up | Ctrl-C. However, as others have pointed out, it can make | sense for long-running processes (especially in cases where | there's an intermediate result that can be output instead of | the final result). I think a good compromise is to only ever | trap Ctrl-C once, so that a double Ctrl-C always successfully | interrupts. | exdsq wrote: | That's fair! But sometimes I want to have a hook that | basically says "Are you sure?" to catch mistakes in the wrong | terminal or something. One thing I wrote recently took | several hours to run and it'd suck to accidentally close it | because I type without looking. | gkfasdfasdf wrote: | This is a great list. Also while reading about 'readonly' bash | variables I ran across this amazing project which lets you call | native functions from bash [0]. My mind is spinning from the | possibilities... | | [0]: https://github.com/taviso/ctypes.sh | mehrdadn wrote: | Do you want to trap INT, or EXIT? | exdsq wrote: | EXIT is trapped in the same way as 0, it's something that | happens when your shell exits. Ctrl+C sends the SIGINT signal | but you can catch it with INT too. You want to do the latter | because your gracefully exiting the script, if you want to | have some cleanup after that you could trap EXIT (for | deleting tmp files or something). | jwilk wrote: | Writing good SIGINT handler is a bit tricky. You should re- | raise the signal, so that bash notices that the program was | actually interrupted: | | https://www.cons.org/cracauer/sigint.html (section "How to | be a proper program") | | Most of the time, especially for short lived script, adding | signal handlers is not worth the trouble. | hivacruz wrote: | I would also add shfmt, to format scripts: | https://github.com/mvdan/sh | JdeBP wrote: | I wonder how much Clippy is to blame for the visual motif used in | this comic strip. | | Common motifs, elsewhere, for a shell are a dollar sign and an | underscore or a greater than sign and an underscore. (The latter | is somewhat odd for a shell, given that it more resembles the | prompts on Microsoft/IBM command interpreters, and not the PS1 | prompts of Unix shells, which are commonly dollar symbols, | hashes, or percent signs rather than greater than.) | | * https://www.redbubble.com/i/sticker/zsh-by-zoerab/20363330.E... | | * https://commons.wikimedia.org/wiki/File:Bash_Logo_black_and_... | | * https://commons.wikimedia.org/wiki/File:PowerShell_5.0_icon.... | | * https://icon-library.net/icon/commands-icon-5.html | | * https://icon-library.com/icon/bash-icon-10.html | | * https://dribbble.com/shots/6101482-Bash-Automation | chubot wrote: | I'm working on fixing errexit / set -e in Oil: | | https://github.com/oilshell/oil/issues/709 | | Summary of problems: | | 1. The "if myfunc" problem -- error checking is skipped. This is | specified by POSIX, but it's undesirable. | | 2. The f() { test -d /tmp && echo "exists" } problem. The exit | code of the function is probably not what you expect! | | 3. the local x=$(false) problem. This happens in all shells | because of the definition of $?. (local is a shell builtin with | its own exit code, not part of the language) This one is already | fixed with shopt -s more_errexit in Oil. | | 4. the x=$(false) problem. This is a bash problem with command | substitution. For example, dash and zsh don't have this problem. | Test case: bash -c 'set -e; x=$(false); echo | should not get here' | | Newer versions of bash fix it with inherit_errexit. Oil also | implements inherit_errexit and turns it on by default! (in Oil, | not OSH) | | ----- | | So 1 and 2 relate to the confusing combination of shell functions | and errexit. | | And 3 and 4 relate to command subs. Oil has fixed these with opt- | in OSH shell options (on by default in Oil), but not 1 and 2. | | If you know of any other problems, please chime in on the bug! | a1369209993 wrote: | > 2. The f() { test -d /tmp && echo "exists" } problem. The | exit code of the function is probably not what you expect! | | The exit code is 0 (assuming /tmp/, stdout, /bin/test and | /bin/echo are all working correctly; with /tmp1 it's 1), as | expected; is this referencing a bug in sh and/or bash that I've | fixed locally and then forgotten about? | | (Also, I'm pretty sure it should be: f() { test | -d /tmp && echo "exists"; } | | unless the parse error for missing ';' was your point (I | haven't bothered to fix that one, but maybe Oil has).) ___________________________________________________________________ (page generated 2020-10-10 23:00 UTC)