[HN Gopher] How to do things safely in Bash (2018) ___________________________________________________________________ How to do things safely in Bash (2018) Author : soheilpro Score : 103 points Date : 2021-04-03 15:12 UTC (7 hours ago) (HTM) web link (github.com) (TXT) w3m dump (github.com) | belter wrote: | Every time I think of writing some Bash I spend some time here: | https://mywiki.wooledge.org/BashPitfalls and then ...I use | something else. | renewiltord wrote: | Embarrassingly, instead of fixing the code, I fix the data for | bash. I just make sure my input is well-formed haha. I don't | think I have any files or folders with spaces in them on my | computer. | | It may not be the way but it is a way. | lpapez wrote: | I tend to do this as well. | | This is the way. | maxrev17 wrote: | See the same debate ensue each time with bash - use python, they | shout. Perhaps the overriding factor here is familiarity? Past | looping and some basic flow control I'm guessing most people run | out of being comfortable in bash and can get things done quicker | in their fave Lang? | kaba0 wrote: | It's not about being comfortable, but about the million ways | your shell script will fail. A file has a space in the name? | Good chance your script failed. No file founs? Same. Multiple | files found after expansion? Same. | nunez wrote: | If you're gonna Bash, definitely use shellcheck for everything. | If you're writing Bash in vim, I'd highly recommend installing | Syntastic and enabling the checker below: | | ``` let g:syntastic_sh_checkers = ["shellcheck", "-e", "SC1090"] | ``` | geocrasher wrote: | I've been shell scripting intermittently for a long time and have | written some bash scripts that have seen a lot of use. This | document really showed me how bad some of my programming is. But | then again, the first thing I tell people when demonstrating one | of these programs: "I am not a programmer. I am a bash scripter. | And, a bad one." The stuff still works. But I would feel a lot | better if I were doing things the right way. I'll be reviewing | this next time I write a bigger s/program/script/ | inetknght wrote: | Shellcheck helped me learn a lot about bash. | | https://www.shellcheck.net/ | antegamisou wrote: | Here's a great resource for proper Bash syntax | | http://mywiki.wooledge.org/BashGuide | pwdisswordfish8 wrote: | I've recently taken to not bothering with shell scripts at all. I | just use Python instead. The type system, while it's by no means | state of the art, is still miles ahead of the stringly-typed | bash. The libraries are excellent; much can be done with the | standard library alone. The scripts are much faster (even though | the language isn't particularly tuned for performance), since I | don't need to spawn a subprocess for each little string | manipulation task. And if I ever need to 'shell out' to an | external process, there's always the subprocess module that is | much more robust than 'set -e' ever was. | | Seriously, if you can avoid it, just don't write shell scripts at | all. Use a real programming language instead. | yeowMeng wrote: | > The [python] scripts are much faster... | | Pipelines are performant; many algorithms can be expressed as | pipelines. Bash has many built-in string manipulation | mechanisms which do not require a sub shell. | | Why do people who subscribe to the "avoid shell scripts at all | costs" ideology feel so passionately about prescribing to | others? | | I like python, but if I am in a rush it's shell. | neolog wrote: | > Why do people who subscribe to the "avoid shell scripts at | all costs" ideology feel so passionately about prescribing to | others? | | Are you asking why people make recommendations in general? | marcosdumay wrote: | Pipelines are a kernel feature that can be created on Python | too. You'll just need to abstract it in a function. | IshKebab wrote: | 100% agree. This article should just be "rewrite your script in | Python". It's not the best scripting language ever, but it | works fairly well and it's about 100 times better than Bash. | lanstin wrote: | All the deployment things are just wrappers to shell commands | so if you want to help people debug user-data.sh, cron, | ansible, docker files, or puppet, you need to understand shell. | Also, it is better to have something in bash as code than not | have it be fully automated. I draw the line at arrays tho. If | you want arrays don't do bash. But if you want to just run a | bunch of commands on your compute, bash is there. | | One thing I have started doing for stuff that need to go via | ssh is I just do a here doc for an embedded shell script that I | scp to the remote and then just use ssh to invoke it. The | quoting and pseudotty stuff just got to be too dumb to | understand. | | Also, and maybe this is just for non-interactive scripts, I | find everything from writing them to using them is a lot easier | if you take pains to make them idempotent, so you can run them | one or 100 times with the same effect. | stormbrew wrote: | This, and I mean _very specifically_ this with python always as | the go-to replacement, comes up every single time anyone even | utters the words "shell" or "bash" and every time all I can | think is that the people saying it aren't writing the same | kinds of shell scripts I ever see, use, or wind up writing. | | And every time I've ever seen something that's allegedly "a | shell script replaced with a python script" it's way more | complicated than an equivalent shell script would have been. | | I just wonder, really, what "a shell script" even is to you if | not something where you need to "shell out to external | processes"? | dragonwriter wrote: | > I just wonder, really, what "a shell script" even is to you | if not something where you need to "shell out to external | processes"? | | A _replacement_ for a shell script may not itself be a shell | script. | | A fairly typical python-as-shell-replacement I have that | (among other things) creates/manipulates test resources in | AWS corresponding to git feature branches, shells out _less_ | than it would if it was a typical shell language (using boto3 | rather than shelling to the AWS CLI, etc.) but still shells | out to git. But it would be _replacing_ a shell script even | if I had a git-repository-interaction library that eliminated | the need to shell out. | blabitty wrote: | I wonder too. Things like file and directory manipulations | are way more complicated in native Python and I find the | exceptions harder to troubleshoot than native commands. I'm | not even going to get into pythons version and dependency | hell, suffice it to say I have more confidence in a standard | version of bash and standard utilities generally being | present on a modern Linux system. What I've seen from | pythonistas as reasoning for this replacememt love is usually | one or a combination of a few things - a claimed write once | run anywhere ability that I think is generally fantasy, a | belief that Python is a "real" language and shell is not | which usually just means "I don't like shell or think it is | unfashionable", or a belief that Python code can be made more | reusable. The person in question almost always has a | background of feature developer that is now doing devops type | tasks. | | All that said I like python and there are tasks I absolutely | prefer it for, usually anything to do with scraping some web | API that returns a complex piece of JSON or xml. | kubanczyk wrote: | Here is a novel idea. | | I am allowed to call 'mv' or 'cp' binaries from bash. | | I am also allowed to call 'mv' or 'cp' binaries from python | with subprocess! | | (Edit: bad example binaries, 'df' or 'tar' would be | probably better.) | | I must choose not to install bash libraries for a bash | script beyond builtins. | | I can choose not to install python libraries beyond | builtins. | | It becomes a really simple tradeoff. | | Nobody forces you to step out of python's stdlib. No extra | dependencies. And you can still reap benefits of a proper | programming language. | fillest wrote: | It is OK to call whatever one needs actually - just | minimize the usage of Bash language features (Python | provides most of them). It is not complicated and | requires no dependencies, here's a self-contained helper: | https://gist.github.com/fillest/8d64f8fa0cdb1745bfc9c683c | f39... | marcosdumay wrote: | Hum, no those people are probably writing the same kind of | script you do. Their Python versions are indeed longer and | more verbose. | | I always recommend Python anyway if you care about edge cases | (what is not always). That longer script handles them on the | obvious way, while the short and more readable shell script | does something absolutely crazy every time a detail is | different from planed. And if you try to correctly handle the | edge cases in Bash (you'll fail), you'll get something much | more complicated than the Python version anyway. | chubot wrote: | _Shouldn 't scripts over 100 lines be rewritten in Python or | Ruby?_ | | http://www.oilshell.org/blog/2021/01/why-a-new-shell.html#sh... | | tl;dr I think the main skill that is missing is being able to | write a Python script that fits well within a shell script. The | shell script often invokes tools written in other languages | like C, Rust, JavaScript, etc. (most of which you didn't write) | | Good book on this: https://www.amazon.com/UNIX-Programming- | Addison-Wesley-Profe... | | Online for free: http://www.catb.org/esr/writings/taoup/html/ | Supersaiyan_IV wrote: | At least shell follows POSIX standard which is more than one | can say about Python. Have witnessed self-proclaimed "shell | replacing" python scripts invoking commands, and never checking | the exit code. Debugging hell. | jrockway wrote: | Same. I feel like I spend 45 minutes every time I write a shell | script to determine simple things like "is this environment | variable set". Stack Overflow always has 100 answers on how to | do this, and every one has someone replying "well that doesn't | work if..." | | In general, after 20 years of using Unix, I'm starting to sour | on the "everything is a string" and "streams are just newline | separated plain-text records". The shell is a pretty good REPL, | and glue like seq, find, xargs, grep, etc. are very good. But | honestly, I find myself reaching for purpose-built tools rather | than ad-hoc pipelines more and more. fdfind does more of what I | want on average than find; rg does more of what I want than | 'find | xargs grep'. I also find myself using more and more | structured data, and write more jq pipelines than I do bash | pipelines. Finally, I am getting more and more frustrated at | basic shell mechanics like history handling. I have 5 terminals | open on average, and I don't understand why C-r in one won't | find history in another (I know why, of course, but I don't | like it). I'm getting pretty close to just writing my own shell | and toolchain. I feel like if the Unix shell needed to be | fundamentally reimagined, someone would have already done it. | But they haven't. They just hacked Unicode and remote RPCs into | shell prompts so that pressing "enter" on an empty shell prompt | takes 25 seconds to run. I'm not sure why people are so focused | on the polish and not the core inadequacies, but they are, and | I feel like I'm going to have to fix that myself in the next | couple years... | chubot wrote: | Have you tried any of the shells here? | | https://github.com/oilshell/oil/wiki/Alternative-Shells | | Discussed recently: | https://news.ycombinator.com/item?id=26121592 | | A lot of them are "fundamentally reimagining" shell -- | actually I'd say MOST of them are; whether that's good or bad | depends on the user's POV. | | Oil is reimagining shell, but also providing a graceful | upgrade path. Out of all the shells I'd say it's most focused | on the fundamental language and runtime, and less on the | interactive issues (right now). | jwilk wrote: | A portable way to test if the variable is set: | test -n "${VAR+x}" | arielb1 wrote: | When calling shell from bash (to use pipes etc.), I found it | useful to pass variables via the environment, like this: | def safe_call(command, **keywds): return | subprocess.check_call(f'set -euo pipefail; {command}', | shell=True, env=keywds) safe_call('command1 -- | "$bar" | command2 --baz="$baz" | command3 > "$output_file"', | bar=bar, baz=baz, output_file=output_file) | arielb1 wrote: | The main difficulty I have with this method is that I don't | have an easy way to pass a Python array to bash as an array of | parameters. | xonix wrote: | I personally have found that AWK is a surprisingly good | alternative to shells for scripts bigger than average. | | Why? | | 1. Portability (AWK is a part of POSIX) | | 2. Very clean syntax | | 3. Powerful associative arrays | | 4. Super-powerful string manipulations facilities | | 5. Easy shell interoperability | | As an example here is a simplistic build tool [1] I've developed | in AWK. As you can check [2] it runs unchanged under | Linux/macOS/Win (via Git Bash). | | [1] https://github.com/xonixx/makesure/blob/main/makesure.awk | | [2] https://github.com/xonixx/makesure/actions/runs/702594092 | drran wrote: | AWK is good, but Perl is better. Perl is good, but Python is | cleaner. Python is good, but Go is better. Go is good, but Rust | is faster. Rust is good, but shell is simpler. Shell is good, | but AWK is better... | hutrdvnj wrote: | I think the best way to write things safely is to not write them | in Bash in the first place. Better pick a strongly typed language | of your choice. | macintux wrote: | How many strongly typed languages are: | | + Installed everywhere | | + Even within 20% as concise as bash | effie wrote: | Indeed. Python does not win over bash in either category, and | it is slower. | | I wish there was a super-fast shell-like scripting language | everywhere that could serve as a general programming | language. Like Perl, but simpler rules and readable syntax. | But there is nothing. Who would have thought, I may have to | learn Perl eventually. | kaba0 wrote: | How is it slower/matter at all? Goddamn bash calls an | external process in if branches, so if a bash script is | enough for a given process, than running python inside a vm | inside a vm inside a vm will be still fast enough. | Scripting doesn't require performance. | drran wrote: | IMHO, we need more than one language in the shell: one to | orchestrate launching of applications, plumbing, and | processing of their inputs and outputs, another one for | computations with integers, floats, complex values, arrays, | matrices, another one for text processing and parsing, | another one for argument parsing and default values, | another one for error handling and recovery, another one | for pattern matching and state machines, another one for | object-oriented programming, another one for network | programming, another one for record-oriented programming, | another one for security and debugging, another one for | container management, another one for system manipulation, | upgrading, recovering, another one for documentation, and | so on, with advanced way to share variables and data | between these languages (environment variables on | steroids). | | It's not possible to implement such monstrosity as one | simple, portable, small, fast, and secure binary. | [deleted] | gdavisson wrote: | I agree with almost everything in this guide, but it has a couple | of recommendations that create new problems (while solving | others): | | The nullglob option ('shopt -s nullglob') makes things like 'for | f in _.txt ' work right when there are no matching files, but | make other commands like 'grep somepattern _.txt' change their | behavior in ways that can cause serious trouble. grep (and many | other commands), given no files as input, will read from standard | input (up to the EOF); if it's running interactively and input | hasn't been redirected, this causes the script to hang for no | discernible reason. If input _has_ been redirected, it steals | input that was presumably meant to be read by some other command. | In my opinion, the problems this causes are more serious than | what it solves. | | The errexit option ('set -e' or 'shopt -s errexit') can both fail | to exit when you expect/want it to (several such situations are | described in the guide) and also exit when you don't expect/want | it to. The guide mentions suppressing unexpected exits with '|| | true', but it's not always obvious when this is needed, and it's | not even consistent between versions of bash. There's a good | parable about this (and some examples) at | http://mywiki.wooledge.org/BashFAQ/105 | | I really don't like trying to predict and work around | unpredictable features like this; I'd much rather deal with | explicit error handling, like 'commandThatMightFail || { echo | "Aaaargh" >&2; exit 1; }' | dang wrote: | Discussed at the time: | | _Safe ways to do things in bash_ - | https://news.ycombinator.com/item?id=17057596 - May 2018 (240 | comments) | alblue wrote: | If you're interested in writing safe shell scripts then check out | shellcheck: | | https://github.com/koalaman/shellcheck | | If you're interested I've written a git hook for it that runs a | check when you git commit: | | https://github.com/alblue/scripts/blob/main/shellcheck-pre-c... | | You should also check out her Google shell script style guide: | | https://google.github.io/styleguide/shellguide.html | woudsma wrote: | Shellcheck and the VS Code Shellcheck extension[1] are really | good. I never write a bash script without it. | | Built-in autofixes and direct links to extensive documentation | on each rule, with easy to grasp examples. It's just awesome. | | [1]: | https://marketplace.visualstudio.com/items?itemName=timonwon... | montroser wrote: | This all has its place, and I learned a good few things from the | article. But also, in many situations when you may not need to | deal with completely arbitrary input, it can be nice to shed all | of the "correct" syntax and just optimize for readability. | nas wrote: | The fact that you have to use quoting nearly everywhere is a | design flaw in the Borne shell. Some shells, like Plan 9's rc, | for example, don't expand after variable substantiation. They | have an operator to call if you want to explicitly force | expansion. That's so much cleaner and less error prone. | rustshellscript wrote: | zsh is also doing the variable substitution better than bash. | FYI, I just released rust_cmd_lib 1.0 recently, which can do | variable substitution without any quotes: | https://github.com/rust-shell-script/rust_cmd_lib | chubot wrote: | Oil is Bourne compatible, but has a mode to opt you into the | better behavior. Example: osh$ empty='' | osh$ x='name with spaces.mp3' | | This is like Bourne shell: $ argv $empty $x | ['name', 'with', 'spaces.mp3'] # omit empty and split | $ argv "$empty" "$x" ['', 'name with | spaces.mp3'] # unchanged | | Opt into better behavior, also available with bin/oil: | $ shopt --set oil:basic $ argv $empty $x | ['', 'name with spaces.mp3'] # no splitting/elision | | If you want to omit empty strings, you can use the maybe() | function, which returns a 0 or 1 length array for SPLICING with | @: $ argv @maybe(empty) "$x" ['name | with spaces.mp3'] # omitted empty string | | Example of splicing arrays: $ array=("foo $x" | '1 2') $ argv $empty @array ['', 'foo name | with spaces.mp3', '1 2'] | | This is called "Simple Word Evaluation": | https://www.oilshell.org/release/latest/doc/simple-word-eval... | | Feedback appreciated! | | (Interestingly zsh also doesn't split words, but it silently | removes empty strings). | enriquto wrote: | > The fact that you have to use quoting nearly everywhere is a | design flaw in the Borne shell | | I disagree. This can be considered a design flaw in other | places, like filesystems that allow filenames with spaces and | other idiotic complexity-inducing things. ___________________________________________________________________ (page generated 2021-04-03 23:01 UTC)