Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

If you take the time and effort to understand how bash wants you to think, you can learn how to right elegant scripts that are as maintainable as anything else.


I've written a lot in bash over the years, and I feel like I understand it pretty well. But I would never say that even elegant bash scripts are as maintainable as "anything else". It is a clunky programming environment, born of compromises, with many traps that are easy to miss in code review.


There's `shellcheck`, as part of Syntastic, for vim. I find it quite useful. You could call it a Bash linter.


Shellcheck is great and is a standalone cli program, as well as a website if you don’t want to install it locally. https://www.shellcheck.net/


I'd rather always check my code locally if the alternative is pasting it into a random web from.

On the other hand, I wonder if the web page keeps track and could offer "best of bash mistakes".


Well, you can. From the website itself:

"ShellCheck is...

* GPLv3: free as in freedom

* available on GitHub (as is this website)

* already packaged for your distro or package manager

* supported as an integrated linter in major editors

* available in CodeClimate, Codacy and CodeFactor to auto-check your GitHub repo

* written in Haskell, if you're into that sort of thing. "

Sometimes you are not in your computer, the script does not have private information (i.e. open source or something you don't care to be public). Sometimes the website is simply more convenient.

*edit: formatting


A random web form can exfiltrate the data you paste into it (and whatever your browser lets it gather). A local program can exfiltrate ... approximately everything of value on the machine?


That's why Linux users typically install things from their distro's package manager. The bar to get malicious software in there is very very high (though it is not impossible).

But if you're still using Windows, then yes, I agree.


While I understand the sentiment, I'm not sure how bash could ever be as maintainable as a something written in e.g. Python (or even better, a strongly-typed language).

The thing with bash is, it's great for tying things together and quick bits and pieces, but it's not set up for writing maintainable code. Arrays, functions, even if statements comparisons can all be done in bash (as first-class features), but are just... easier in other languages. And then think about the refactoring, linting, testing tools available in bash vs other languages. And then on top of that, there's the issue of handling non-zero return codes from programs you call; do you `set -e`, and exit on any non-zero return code even if you wanted to continue, or not `set -e`, ignoring any errors as your script just continues.

Personally, when I feel I want to use a function (or array, or other similar, non-trivial thing), in bash, it's time to reach for another language.

Having said that, there are some nice programs written in bash. https://www.passwordstore.org/ being one that comes to mind.


> e.g. Python (or even better, a strongly-typed language).

Python is strongly typed. Maybe you meant "statically"? (As opposed to dynamically.)


Yes, you are right, and statically is what I meant, thanks for the correction.


> Personally, when I feel I want to use a ... non-trivial thing ... in bash, it's time to reach for another language.

Then you must not be writing any bash at all. Functions are useful for almost anything beyond a one-liner script.

Typical functions used in innumerable non-trivial scripts:

* print_usage()

* die() (see: https://stackoverflow.com/q/7868818/1593077)

:-)


You are kind of right, I don't write much bash, but I do write some simple scripts that I can call quickly and easily (e.g. start this program with these args, write the log file here with the filename as the current date, etc). Although regarding "Then you must not be writing any bash at all"; I'm not sure how you could have deduced this!

With regards to `print_usage()` and `die()`, yes, I would reach for Python 3 then. The `argparse` module and `throw` are first-class members of the stdlib/language and are better and more standard between programs than if I threw together these myself (and with `throw` you get a stack trace, which is nice).


Can you point me towards an elegant bash script? I honestly have never seen one that is more then a few lines.


I like to think this thing I wrote isn't so bad and is fairly readable, even if you don't know anything about shell programming:

https://github.com/GavinRay97/hasura-ci-cd-action/blob/a9731...

This is out of necessity. I'm not the sharpest tool in the shed, so I have to go out of my way to write things such that when I come back to them in months or years, I still understand what they do.

For this same reason, is also why I never use shorthand flags for scripts.

I have no clue what IE: "-s -y -o" might do (or even worse, "-syo", dear god my eyes! Also -- is that one special command, or a series of individual commands?!).

But "--silent --assume-yes --output-file" is pretty easy to grok immediately.


I agree with you that it seems readable, nice job on it. But there is comment there that says

Oh man this is so ugly

Not something I would expect in an "elegant" script. Don't think that it is your fault, just it is very hard to write anything elegant in bash.


Ah yes, that section.

Hahaha -- fair point. Readable: maybe. Elegant? No. Bash is far from elegant =P


I would add a variable ENDPOINT, initialised to "" and set to " --endpoint $THEENDPOINTVALUE" if the endpoint value was passed. Then include that in every invocation?

Sorry if I missed something in the logic, reading on my phone, but from the comment, this feels like something I do frequently...


Or, (thanks, Amazon): `--no-paginate` `--no-cli-pager` (dang that pisses me off! thank goodness for `jq`).



I write a lot of bash, and can kinda agree that it’s full of footguns. The most of them are not even in bash, but in Unix tools, which you have to use due to the lack of standard libraries.


Somehow I noticed more footguns in bash than in utilities.


To have variable typos result in errors with `set -u`, then expand an array that is defined but may be empty, I need write it as `${ARRAY[@]+"${ARRAY[@]}"`. (Further explanation: https://stackoverflow.com/questions/7577052/bash-empty-array...) But maybe bash arrays are too new a feature, and should be avoided, so let's look at something simpler, like command-line arguments.

Parsing command-line arguments is easy, but parsing them correctly is ridiculously hard. `getopts` doesn't handle long flags, so it's out. `getopt` doesn't handle arguments with spaces in them, unless you have a version with non-standard extensions, so it's out. You're left with manual parsing, and it's a royal pain to make sure you handle every expected case (short/long flags, clusters of short flags, trailing arguments to be passed through to a subprocess, short/long options whose value is in the next argument, long options whose value is after an equals sign, and several others I'm probably forgetting about). And this is just to get the arguments into your script, before you actually do anything with it.

I agree that bash has effective idioms, and learning those idioms can make scripts easier to write. I strongly disagree that bash is "as maintainable as anything else", and scripts beyond a few hundred lines should be rewritten before they can continue growing.


Bash `getopts` handles short and long arguments gnu-style without any problem. The following code handles args like "-h", "-i $input-file", "-i$input-file", "--in=$input-file", and "--help":

  while getopts :i:h-: option
  do case $option in
         # accept -i $input-file, -i$input-file
         i ) input_file=$OPTARG;;
         h ) print_help;;
         - ) case $OPTARG in
                 help ) print_help;;
                 help=* ) echo "Option has unexpected argument: ${OPTARG%%=*}" >&2; exit 1;;
                 in=* ) input_file=${OPTARG##*=};;
                 in ) echo "Option missing argument: $OPTARG" >&2; exit 1;;
                 * ) echo "Bad option $OPTARG" >&2; exit 1;;
             esac;;
         '?' ) echo "Unknown option $OPTARG" >&2; exit 1;;
         : ) echo "Option missing argument: $OPTARG" >&2; exit 1;;
         * ) echo "Bad state in getopts" >&2; exit 1;;
     esac
  done
  shift $((OPTIND-1))


You can use bash-modules arguments library to generate argument parser for you.


Thank you, I haven't heard of that one before. However, all the examples look like it gets called as an external library, which would either need to be distributed with a script, or to already be installed by a user.

Is there a feature I'm not seeing that would generate an argument parser without external dependencies?


Yes, arguments::generate_parser function. Install bash-modules, then run `. import.sh log arguments`, then call `arguments::generate_parser '--foo)FOO;String'` , and it will generate function with parser, which include parser for foo option for both `--foo VALUE` and `--foo=VALUE`:

  ...
  
        --foo)
          FOO="${2:?ERROR: String value is required for \"-f|--foo\" option. See --help for details.}"
          shift 2
        ;;
        --foo=*)
          FOO="${1#*=}"
          shift 1
        ;;
  ...


One way to answer this would be to find the largest (in terms of LoC) Bash script you can find and try to add a feature or fix a bug.


But in reality you never see such scripts.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: