[HN Gopher] Advanced Bash-Scripting Guide (2014)
       ___________________________________________________________________
        
       Advanced Bash-Scripting Guide (2014)
        
       Author : jolux
       Score  : 103 points
       Date   : 2022-08-19 16:28 UTC (6 hours ago)
        
 (HTM) web link (tldp.org)
 (TXT) w3m dump (tldp.org)
        
       | [deleted]
        
       | [deleted]
        
       | uhtred wrote:
       | Cue all the "when your program gets longer than one line you
       | should consider using python" comments.
        
         | gpderetta wrote:
         | To be fair you can do a _lot_ in a bash one liner.
        
           | dmtroyer wrote:
           | doesn't mean you should, for the sake of the next person.
        
         | vlunkr wrote:
         | My rule is when you start needing functions or arrays you
         | should consider using something else. Bash is really neat and
         | easy to get started with, but maintaining a large codebase is
         | terrible.
        
         | metadat wrote:
         | 100% sane advice on the "should" front, but I don't always
         | follow the shoulds..
         | 
         | Bash is just too damn fast to whip things up in (even at the
         | risk of endless footguns). Build up a pipeline of commands,
         | translate into script, done. It's powerful and can scale if
         | common conventions and good practices are used.
         | 
         | For easier debugging I always add a simple flag along the lines
         | of: if $1 = -v then set -x; shift
         | 
         | Obviously shellscripts are a terrible or highly suspect idea
         | when production-grade transactional integrity is critical, but
         | in many cases the swiftness of shell development outweighs the
         | cons when the goal is to Get Things Done.
         | 
         | The shell is a pretty sweet interface.
        
           | cjvirtucio wrote:
           | it's so much easier to glue tools together with it. I'd end
           | up writing more code if I had used python's subprocess
           | package or ruby's IO package. as long as I don't need a
           | complex data structure of some sort.
        
         | bryanlarsen wrote:
         | If your primary consideration is that it's generally available
         | on a random developer's machine, what are the options? IOW out
         | of the box on OSX and the major Linux distributions, and
         | readily available on Windows.
         | 
         | Python is pretty safe, but your code would have to work with
         | both 2 & 3 and not pull in any external dependencies. And
         | recommended practices have changed so drastically on how to
         | install Python over the years that a random dev could have
         | theirs in a fairly weird state.
         | 
         | Perl would work, but hacked-together Perl has the similar
         | drawbacks to hacked-together Bash, especially when it's me
         | doing the coding.
         | 
         | Ruby has problems similar to Python.
         | 
         | Make & awk have problems similar to Perl & Bash.
         | 
         | Therefore my latest rust package contains bash scripts.
        
           | vaporup wrote:
           | TCL
        
         | js2 wrote:
         | Last week I wrote a bash script that ended up being about 250
         | lines. It was mostly running other processes. For technical
         | reasons, it needed to re-invoke itself a few times via sudo.
         | 
         | When I was done I looked over it and thought: this is a nice
         | piece of code right now. It's well documented. It's self
         | contained. It passes my tests. I could maintain this code.
         | 
         | But it made use of quite a few bash'isms: arrays and so forth.
         | I wasn't confident in the ability of my colleagues to keep it
         | in good state.
         | 
         | So I rewrote it in Python. The Python version ended up being a
         | little longer, but that was mostly due to formatting
         | difference. The Python version in addition I could reformat
         | with black and isort, I added type annotations so I could test
         | it with mypy.
         | 
         | (I had formatting the shell version with shfmt and checked it
         | with shellcheck, but still.)
         | 
         | The Python version is much easier to read, and I think will be
         | more maintainable.
         | 
         | I consider myself competent in both languages so it's a
         | courtesy to my colleagues if nothing else to ship the Python
         | version.
        
         | aaaaaaaaaaab wrote:
         | _Yawn..._
         | 
         | Python sucks for building processing pipelines.
         | 
         | How many lines of Python code would you need to implement this?
         | paste -d \\n <(foo ...) <(bar ...) | while read -r f && read -r
         | b; do         baz "$f" "$b"       done
         | 
         | Mind you, `foo` and `bar` run in parallel.
        
           | slt2021 wrote:
           | replace bash one-liner with python? No, thanks
           | 
           | replace 200+ bash script with multiple subroutines in python?
           | yes
        
             | dmtroyer wrote:
             | as someone who is maintaining mountains of legacy bash, for
             | all that is holy, yes. or Go. or Rust. anything.
        
           | idispatch wrote:
           | Mind you, before this goes to prod please add error handling
           | and logging. What happens when bar fails? What about baz?
           | Now, please rewrite this so your team mates can maintain and
           | extend. Yawn, I'll wait for the PR.
        
             | aaaaaaaaaaab wrote:
             | Gotcha, thanks for the feedback! We should also totally A/B
             | test this! I'll make sure that this ties in with the
             | quarterly OKRs, too! I hope I'll get promoted to E6 for
             | that sweet 10% raise next cycle :fingers_crossed:
        
       | alganet wrote:
       | I am on the portable shell team. It is hard to write for all
       | shells, but totally worth it. If we all did it, reusing shell
       | code would be easier.
       | 
       | We never left the hell caused by the war between shells to
       | provide interactive features. They were all feature creeped for
       | terminal usage and the scripting API was left to rot.
       | 
       | I get the people that say we should use python or something else
       | instead. It won't work though if that language is not capable of
       | replacing the shell _as a build dependency_. As long as we need
       | the shell to build stuff, we're stuck to it.
       | 
       | Dockerfiles, GitHub Actions YAMLs, all these tools accept shell
       | input. This is shiny new software that could have picked any
       | language for their proprietary DSLs, but the shell was chosen.
       | 
       | What the shell needs is what happened to JavaScript around the
       | 2000s. Different interpreters started to align, standards were
       | written, reusable libraries started to pop up everywhere, and now
       | it runs in our brains. Before all of that, JavaScript was merely
       | a cute toy to animate pages.
        
         | js2 wrote:
         | The one thing I really miss in portable shell code is arrays.
         | 
         | There's a place reserved in hell for whoever thought it was a
         | good idea to design a build system around shell code in yaml
         | files.
         | 
         | I'm in the process of moving thousands of lines of such code
         | into standalone scripts that then get called from the yaml
         | files. Keep the yaml as minimal as possible. Then I can
         | shellcheck the shell code or easily port bits to other
         | languages as needed.
        
           | [deleted]
        
           | alganet wrote:
           | You can reimplement arrays using pure shell!
           | 
           | I saw it first here: https://github.com/shellfire-
           | dev/core/blob/master/variable/a...
           | 
           | For many cases, this is even better than bash native arrays.
           | You can pass these as arguments and preserve the structure,
           | while the bash ones will autoexpand if you try that.
           | 
           | I ended up cooking my own array implementation later, but it
           | is a bit more unorthodox (I create extra vars with numeric
           | pointers to handle the values). Let me know if you want to
           | see it, it's not complete but it works.
           | 
           | I'm not very fond of YAML either, I feel your pain :(
        
             | js2 wrote:
             | I knew I should have qualified my comment about arrays
             | with: "without using a hack such as storing the values in a
             | string with a custom delimiter." Those aren't really
             | arrays.
             | 
             | What happens if the delimiter appears among the values you
             | want to store? This is a pretty big disclaimer:
             | 
             | > Do not use arrays with file paths if there's any chance
             | they could contain \r.
        
               | alganet wrote:
               | That's why I did my own implementation using pointers.
               | 
               | var lorem = text_create "Hello"
               | 
               | var foo = array_create
               | 
               | array_push $foo $lorem
               | 
               | `var`, `array_push` and `array_create` are shell
               | functions working together. They create pointers and
               | return just integers. You don't even have to quote the
               | variable.
               | 
               | The code above should create the following variables:
               | 
               | _123="Hello" _124=1 _124_0=_123 foo=_124 lorem=_123
               | 
               | This way, I can even implement more complex data
               | structures.
               | 
               | The numbers start from zero and the program will stop if
               | the global variable counter overflows. My current
               | implementation also has a primitive GC (ARC-based), which
               | could be used to avoid that (but I didn't, yet).
               | 
               | Despite all of this runtime processing and jerry rigging,
               | I have some evidence that it could be faster than typical
               | shell scripts. One single subshell or path lookup is
               | often more expensive than these fake pointer arrays.
               | These strucutres have zero subshells and run on PATH='',
               | just with builtins. The subshell only becomes faster if
               | the volume of data is larger than a few kilobytes (when
               | the cost of forking and exec'in is worth the payoff).
               | 
               | I know this is far from ideal. I should try to put these
               | ideas in a new interpreter, not hack them on existing
               | ones (JS calls them polyfills, maybe I should do that).
               | If I did another interpreter though, it would end up like
               | the fish shell (nice, fast, doesn't compile the kernel
               | and no one uses it).
               | 
               | Maybe I should probably stick to bash, ksh and zsh that
               | offers the most complete set of features. Sounds like
               | "best viewed in internet explorer" though.
        
       | dotancohen wrote:
       | This is a good guide for Bash, but I wouldn't use the code as-is
       | in production. For example, the following code is recommended for
       | cleaning up a log file and leaving only the last 50 lines:
       | tail -n $lines messages > mesg.temp       mv mesg.temp messages
       | 
       | That code has a clear race condition and will lose lines on a
       | busy server.
        
         | metadat wrote:
         | Yes, use `mktemp', just as you would in Python, Go, Rust, C, or
         | Java.
        
           | metadat wrote:
           | Actually, to protect against the clobber of the original
           | file, you could use a flocker lock on the process to
           | serialize the operations. Otherwise it will be unsafe.
        
         | wswope wrote:
         | How would you fix that race condition?
        
           | formerly_proven wrote:
           | Have the writing process do it.
           | 
           | Which kind of reminds me of that product running on a pseudo-
           | distributed NoSQL database built out of text files with
           | "record per line" structure and sort of a compacting garbage
           | collector for these files to remove dead records. And there's
           | just absolutely nothing that can go wrong with that.
        
       | metadat wrote:
       | Lots of good refreshers, the cat + heredoc combo is powerful but
       | lots of cryptically subtle variations in behavior between:
       | 
       | <<EOF (expands variables etc)
       | 
       | <<'EOF' (doesn't expand variables etc)
       | 
       | <<-EOF (trims leading tabs)
       | 
       | Am I missing any more of the tricks?
        
         | 0xbadcafebee wrote:
         | https://linux.die.net/man/1/bash "Here Documents" and "Here
         | Strings" sections.
        
         | dotopotoro wrote:
         | Trimming version does not trim whitespace indentation. I guess
         | point goes to "tabs" camp.
        
           | metadat wrote:
           | Correct, though you could pipe to sed and trim leading spaces
           | with relative ease:
           | 
           | sed 's/^ *//' <<EOF
           | 
           | Which I'd prefer anyways because it's less cryptic. I try to
           | stay away from the obscure, rarely used notations because
           | I'll always have to look it up in StackOverflow anytime I
           | come back to it. These can be confusingly sharp edges.
        
         | alganet wrote:
         | You can do it in compound commands as well:
         | 
         | while read -r LINE
         | 
         | do printf %s "$LINE"
         | 
         | done <<FOO
         | 
         | foo
         | 
         | bar
         | 
         | baz
         | 
         | FOO
         | 
         | This is faster than using cat (one less path lookup, one less
         | fork, one less exec, read is builtin), works on all shells.
         | 
         | There is a <<-'EOF' as well (trims left tabs and does not
         | expand vars).
         | 
         | Another cool trick that only bash and zsh have is `printf -v
         | NAME`, which prints to a variable instead of stdout. You can
         | use it to avoid subshells (one less fork for each subshell
         | avoided).
        
       | stjohnswarts wrote:
       | Doesn't this get posted like every other month?
        
       | 0xbadcafebee wrote:
       | The best guide to using Bash is to read the manual
       | (https://linux.die.net/man/1/bash).
       | 
       | Nobody who has ever used Bash has read the whole manual. It is
       | actually good and useful, please read it. Yes, it is very big.
       | But you will be using this shell for the rest of your life (even
       | if you use Zsh, you will end up using other people's Bash
       | scripts). Consider it an investment in your future.
       | 
       | ...and now that I say that - try to restrict shell scripts to
       | POSIX semantics (https://betterprogramming.pub/24-bashism-to-
       | avoid-for-posix-...). POSIX is very simple and most shells can
       | run it. If you need a Bashism, try Bash v3 first so it works on a
       | Mac without Homebrew. Simple is better/portabler than complex. If
       | you need something Bash doesn't provide, use any language that
       | the majority of your team knows well.
       | 
       | If you want to test a specific version of Bash, there's
       | containers @ https://hub.docker.com/_/bash (Macs use v3.2.57).
       | Test POSIX scripts with _`bash --posix`_. Definitely use
       | Shellcheck.
        
         | jpitz wrote:
         | Not everyone has portability as a first-class requirement for
         | their scripts. For 95% of what I write, bash 4 is entirely
         | appropriate.
        
         | rascul wrote:
         | > The best guide to using Bash is to read the manual
         | (https://linux.die.net/man/1/bash).
         | 
         | Here's the link to the latest official manual on GNU's site:
         | 
         | https://www.gnu.org/software/bash/manual/html_node/index.htm...
        
         | joenoob wrote:
         | > try to restrict shell scripts to POSIX semantics
         | 
         | I have to admit, while i admire the idea of and understand the
         | reasoning behind striving for POSIX-compatibility, i never
         | couldn't fully get past viewing this as some kind of
         | hypothetical exercise. The chance that any script i (or
         | presumably most people who get this advice on pages like SO)
         | will ever write will have to run on a 25 years old Tru64
         | machine or even a modern Mac, BSD, QNX, ... is relatively
         | small. Truth is (in my case), most of the scripts will never
         | even run on anything but the system they were written on. As i
         | said, i admire the idealism though.
        
         | ithrow wrote:
         | A quick ToC for the bash manual: 'man bash | grep ^[A-Z]'
        
         | chasil wrote:
         | Careful readers of the manual will find this commentary from
         | the bash maintainers:                 $ man bash | sed -n
         | '/BUGS/,/^$/p'       BUGS              It's too big and too
         | slow.
         | 
         | It is at this point that such a reader will discover the
         | reasons behind Debian dash.
        
         | stjohnswarts wrote:
         | I think that fish is supplanting zsh as the "developer's"
         | shell. Bash is like C though, gonna be here for another century
         | at least.
        
         | asojfdowgh wrote:
         | > Test POSIX scripts with `bash --posix`. Definitely use
         | Shellcheck.
         | 
         | Except that doesn't disable all bashisms so you end up leaving
         | in bashisms as a result, which is what caused all the issues
         | for debian trying to migrate to dash
        
         | dijit wrote:
         | >Nobody who has ever used Bash has read the whole manual.
         | 
         | Bold claim and patently untrue because _I have_ read the whole
         | manual.
         | 
         | However:
         | 
         | 1) Things you don't use often leak out of your brain at a rate
         | that is absurd.
         | 
         | 2) Different versions have different features and there's wide
         | variations between versions on different platforms, you can't
         | actually depend on a lot of bash stuff existing, so instead
         | it's usually better to target POSIX sh
        
           | jagged-chisel wrote:
           | I, too, experience Swiss-cheese-brain.
           | 
           | I once thought I'd solve this by taking notes, organizing a
           | journal, and writing ... Well, it was a manual. That had the
           | same problem as other manuals - too long, not gonna read.
           | 
           | Full circle.
        
             | nocman wrote:
             | Your note taking, organizing and writing will have helped
             | you retain a lot more info for a longer period of time. It
             | wasn't wasted effort. Still, there is a such thing as "too
             | much". Higher degrees of success are achieved by regularly
             | evaluating where you are in the "too little" to "too much"
             | spectrum.
        
       | dejj wrote:
       | Don't forget to shellcheck1 your script before checking it in.
       | Also available in VSCode, IntelliJ, and others.
       | 
       | [1] https://www.shellcheck.net/
        
       | nixcraft wrote:
       | I also like, BASH Frequently Asked Questions
       | https://mywiki.wooledge.org/BashFAQ and linter
       | https://github.com/koalaman/shellcheck
        
         | michaelsbradley wrote:
         | Bash Hackers Wiki is also a nice resource: https://wiki.bash-
         | hackers.org/
        
       | ufo wrote:
       | Sometimes I pause to think how we're held back by needing to make
       | our shell scripts backwards compatible with Bash or POSIX,
       | because that's what is installed by default. Shell scripting
       | would be nicer if the shell could be less crufty. For example,
       | get rid of all the footguns about expanding unquoted variables...
        
         | arinlen wrote:
         | > _Sometimes I pause to think how we 're held back by needing
         | to make our shell scripts backwards compatible with Bash or
         | POSIX, because that's what is installed by default._
         | 
         | I think you're missing the forest for the trees.
         | 
         | Bash is installed by default because Bash is specified in an
         | international standard dubbed the Portable Operating System
         | Interface (POSIX). Your bash scripts are not backwards
         | compatible. Your bash scripts are compatible, and portable, and
         | standardized, because that's their point, and the whole point
         | of POSIX.
         | 
         | No one is held back by bash or POSIX. You are free to use
         | anything that suits your fancy. Feel free to whip out scripts
         | in Python or Perl or Ruby. Some people do, and last time I
         | checked some operating systems like macOS shipped their
         | interpreters by default. Odds are you are free to easily
         | install those interpreters in those who don't.
        
       | redleader55 wrote:
       | I'm getting a "404 No Found" on my mobile. Is this supposed to be
       | META?
       | 
       | Edit: it's a genuine question, I have no idea what everyone else
       | is seeing.
        
         | dejj wrote:
         | Backup here:
         | http://web.archive.org/web/20220819180012/https://tldp.org/L...
        
       | Cwizard wrote:
       | I'm getting a 404
        
       ___________________________________________________________________
       (page generated 2022-08-19 23:00 UTC)