[HN Gopher] Shellcheck finds bugs in your shell scripts
       ___________________________________________________________________
        
       Shellcheck finds bugs in your shell scripts
        
       Author : mooreds
       Score  : 211 points
       Date   : 2023-11-23 19:16 UTC (3 hours ago)
        
 (HTM) web link (www.shellcheck.net)
 (TXT) w3m dump (www.shellcheck.net)
        
       | DamonHD wrote:
       | Nice: I have learnt some things from this on the very first
       | production /bin/sh script that I pointed it at, and I've been
       | hacking such scripts since the 80s!
        
       | Dries007 wrote:
       | Shameless self-plug:
       | 
       | I very much live by the "fix all warnings before you commit" (or
       | at least before you merge), so I have Shellcheck and a bunch of
       | other linters set up in my pre-commit configurations. But the
       | majority of the shell in most of my projects ends up embedded in
       | .gitlab-ci.yml files, where it's hard to check. So I made a
       | wrapper that does that automatically:
       | https://pypi.org/project/glscpc/.
       | 
       | It uses the Shellcheck project and some magic to give you the
       | Shellcheck remarks with (mostly) accurate line-numbers.
        
         | brirec wrote:
         | I'd love to see a project that would do this, but more
         | generally.
         | 
         | I don't use GitLab CI, but I do use a good handful of other
         | file types that essentially inline shell scripts. Dockerfiles,
         | GitHub Actions, and Justfiles, just to name a couple.
         | 
         | Usually, and almost exclusively for the sake of ShellCheck, I
         | make a point of putting anything more complex than a couple of
         | commands into their own shell scripts that I call from the
         | inlined script in my Dockerfile.
         | 
         | (This pattern also helps me keep my CI from being too locked
         | into GitHub Actions)
        
           | stouset wrote:
           | This is the way
        
           | iamjackg wrote:
           | Absolutely agree. The main downside of that pattern is that
           | it doesn't work with jobs included from other projects in
           | GitLab CI, since the job runs in the context of the project
           | that imported it and therefore can't find the script in its
           | original repo. Huge bummer.
        
             | vinnymac wrote:
             | Bundle up configuration and scripts, and now we have
             | containerized CI infrastructure.
        
           | vinnymac wrote:
           | I'd rather see CI services add a mode that would enforce all
           | scripts must live in separate files, rather than inline.
           | 
           | It's really not necessary to support inline except for single
           | lines that are very short (under 30 chars).
        
             | Piraty wrote:
             | so much this. it will help CI not become a holy, super-
             | hard-to-debug, unreproducible mammoth. write scripts, call
             | them from CI
        
             | Dries007 wrote:
             | I would say: stick to straight command sequences with
             | variables, avoid if/for/while/functions etc. Subshells and
             | pipes only for trivial things.
             | 
             | Setting something like a 30 char limit will inevitably lead
             | to code-golfing to fit a mess into the 30 char limit.
             | 
             | But that becomes harder to automatically check, that's why
             | you should still have good peer reviews based on written
             | standards somewhere.
        
             | plorkyeran wrote:
             | I am not generally a fan of Xcode Cloud's design, but this
             | is one thing I think it gets very right. Rather than let
             | you specify the build actions in the CI settings, it
             | invokes scripts with fixed names in the `ci_scripts`
             | directory of your repo if they exist. There just isn't a
             | mechanism for creating jobs which aren't something that you
             | can build locally and test independently of Xcode Cloud.
        
           | verdverm wrote:
           | You might like Dagger, building images with code, uses the
           | same buildkit engine under the hood
           | 
           | No more linear Dockerfile, use the powers of your preferred
           | language
        
           | marcosnils wrote:
           | Hi there, Dagger contributor here. We're solving this exact
           | same problem by allowing you to encode your CI/CD pipelines
           | in your favorite programming language and run them the same
           | way locally and/or any CI provider (Gitlab, Github Actions,
           | Jenkins, etc).
           | 
           | We're very active in our Discord server if you have any
           | questions! https://discord.gg/invite/dagger-io
        
             | Dries007 wrote:
             | We've been experimenting with Dagger here, as part of the
             | alternative to writing glscpc actually, but I'm not
             | convinced it's ready to replace Gitlab CI.
             | 
             | Dagger has one very big downside IMO: It does not have
             | native integration with Gitlab, so you end up having to use
             | Docker-in-Docker and just running dagger as a job in your
             | pipeline.
             | 
             | This causes a number of issues:
             | 
             | It clumps all your previously separated steps into a single
             | step in the Gitlab pipeline. That doesn't matter too much
             | for console output (although it does when your different
             | steps should run on different runners), but is very
             | annoying if you use Gitlab CI's built in parsing of
             | junit/coverage/... files, since you now have extra layers
             | of context to dig trough when tests fail etc. Plus not all
             | of these allow for multiple input files, so now you have to
             | add extra merging steps.
             | 
             | If your job already uses Docker-in-Docker for something,
             | you have to be careful not to end up with Docker-in-Docker-
             | in-Docker situations, or container name conflicts if you
             | just pass through the DOCKER_HOST variable.
             | 
             | The one thing that would make this worth it is being able
             | to run the pipelines locally to debug, but I've just
             | written quick-and-dirty scripts to do that every time I've
             | needed it. For example, running the test job in our
             | pipeline on every Python version:
             | https://gitlab.com/Qteal/oss/gitlabci-shellcheck-
             | precommit/-...
        
           | Dries007 wrote:
           | Generalizing this is non-trivial (I tried initially) but I'm
           | sure others can build in the same principles.
           | 
           | I think this comes close for Dockerfiles:
           | https://hadolint.github.io/hadolint/ Just have to write a
           | pre-commit hook for it.
        
         | iamjackg wrote:
         | Oh, that's really cool. I was trying to solve this from a
         | different perspective a while ago: I wanted to add some pre-
         | processing that would take a "normal" shell script and render
         | it to the `script` part of the corresponding job at build time,
         | the advantage being that you still have everything self-
         | contained in the gitlab-ci job.
         | 
         | I stopped working on it because dealing with shell shenanigans
         | in the GitLab CI runner environment is such a miserable
         | experience that we're in the process of moving all our jobs to
         | python scripts.
        
           | Dries007 wrote:
           | Yea, Pythonifying the scripts is also generally my
           | preferences the moment they become somewhat complex. But even
           | then it's nice that you can be reasonably sure you're not
           | forgetting quotes around variables or using bash constructs
           | where you only have sh.
        
       | ulrischa wrote:
       | Saves a lot of time for tons of legacy shell scripts
        
         | hapulala89 wrote:
         | I have a colleague that writes alot of shellscripts and there
         | is an ongoing discussion if shellcripts or scripting languages
         | like python is better.
        
           | agumonkey wrote:
           | i remember trying to rewrite so fragile scraping bash script
           | in python, and even with some effort to use nice libs and
           | create some cool helpers it ended up as long and not much
           | more solid
           | 
           | bash is infectious in the bad sense :D
        
           | pram wrote:
           | It's perfectly fine for glue type stuff in a CI pipeline imo.
           | There's frankly no easier way to work with files.
        
           | sneed_chucker wrote:
           | Python's subprocess/shell-out story is just bad enough that I
           | still find myself writing a lot of shell scripts if the task
           | at hand warrants more than 2-3 subprocess calls.
           | 
           | Realistically, Perl or Ruby would fill this role fine, but I
           | hate adding another language to a project just for that
           | purpose.
        
             | pletnes wrote:
             | Agree, and also you have to write a few lines of shell to
             | get your python going (and same for node or ruby or
             | whatever).
        
           | ovex wrote:
           | From a security point of view, Python is better because it is
           | less of a footgun. So if you expose an interface to untrusted
           | users, you should use Python because its behavior is more
           | intuitive. An arithmetic expansion or missing quotes do not
           | easily become a vulnerability in Python.
        
           | Schnitz wrote:
           | We ported all shell scripts to Python at a company that I've
           | worked for. Scripts just kept getting longer and more
           | complex. As a language Python is great, super fast and easy
           | to code in, very little inherent complexity. The reason I
           | wouldn't choose Python again is distribution. It's easy
           | enough in Docker, you can just bite the bullet and vendor the
           | same Python in all Docker containers. Mac was a pain though,
           | pyenv etc all had their own issues and collisions with
           | homebrew and dependency management with pip is a hassle as
           | big as npm. A real bummer given how well Python works as a
           | language for scripting.
        
       | wittekm wrote:
       | Shellcheck is great, but dealing with source/imports is suuuch a
       | pain. Not their fault sh is a nightmare.
        
         | johnchristopher wrote:
         | Well, it's possible to do this:                   # shellcheck
         | source=./deployment/deployment-example.env         . "${1}"
         | 
         | But I see how it's a pain point when you have multiple subshell
         | scripts and files to source.
        
       | eddtries wrote:
       | I also recommend https://github.com/bach-sh/bach when you have to
       | use Bash for things long enough it probably shouldn't be!
        
       | seb1204 wrote:
       | The page is thanking Mercedes Benz? That came unexpected.
        
         | popcalc wrote:
         | https://github.com/orgs/mercedes-benz/sponsoring
         | 
         | They're sponsoring quite a few devs. Caddy, curl, and SeaweedFS
         | notably.
        
           | belval wrote:
           | That gives me a new found respect for Mercedes-benz.
        
       | frizlab wrote:
       | But not zsh scripts, sadly
        
         | cglong wrote:
         | zsh was originally supported, but unceremoniously removed:
         | https://github.com/koalaman/shellcheck/issues/298
         | 
         | I've had great experiences with this tool, but, for some
         | reason, this issue always makes me question taking too great a
         | dependency on it.
        
       | rascul wrote:
       | There is also a bash language server.
       | 
       | https://github.com/bash-lsp/bash-language-server/
        
       | lolc wrote:
       | My take is that bugs in sh scripts are best avoided by not
       | programming in sh. So my preferred tool for sh linting is git-rm.
       | It's not always possible, but driving down the sh line count sure
       | helps against bugs from this language and its weird expansion
       | rules.
       | 
       | Most people don't even know the language has expansion rules and
       | write stuff that accidentally works after the fourth try. This
       | lang wants to become obsolete.
        
         | dimitar wrote:
         | I agree, but some bash can be unavoidable. I've found that even
         | trivial looking bash can be helped with shell-check; this is a
         | testament to the issues in bash more than anything.
        
           | jzwinck wrote:
           | What bash is unavoidable? The aliases and functions you
           | define in your personal shell, sure. But what else?
        
             | auselen wrote:
             | Piping stuff, job control?
        
         | synergy20 wrote:
         | there are many cases that you have to use sh scripts, e.g. many
         | IoT devices, embedded devices etc where python etc are just
         | huge and slow.
        
           | uxp8u61q wrote:
           | If you wouldn't program it in Python, you wouldn't program it
           | in sh either. You don't run shell scripts on embedded
           | devices! Typically, an embedded device runs _one_ program
           | that basically acts as the whole OS for the device. There 's
           | no kernel or userspace to speak of. You're directly
           | interfacing with the hardware, and that's it.
        
             | pletnes wrote:
             | This just isn't true. Smart TVs run some linux/android, so
             | do car infotainment, the list goes on.
             | 
             | Old tumble dryers, vacuum cleaners - sure.
        
               | uxp8u61q wrote:
               | These appliances can run python scripts just fine, then.
               | Try to read the whole context instead of focusing on one
               | part of the comment. I'm writing in the context of an
               | appliance that can't run python script. That means the
               | resources are heavily constrained.
        
               | treis wrote:
               | Eh, there's like a 10x difference in speed between Python
               | and Java/Go and probably like a 100x between Python and
               | shell stuff. Definitely some devices in that range that
               | can do shell stuff but not Python.
        
               | cjaybo wrote:
               | Are you implying that Bash is 10x faster than Java or Go?
        
               | treis wrote:
               | Not Bash but the libraries they call out to.
        
               | lachlan_gray wrote:
               | This probably explains why Mercedes-Benz is on the list
               | of sponsors
        
           | IshKebab wrote:
           | Properly designed IoT devices wouldn't have Bash at all in my
           | book.
        
             | kkfx wrote:
             | Did you know properly designed IoT devices on sale?
             | Personally I have some IoT at home to automate the home
             | itself especially for p.v. self-consumption and the best I
             | was able to find and integrate can be described as crap...
             | I failed to fined anything else...
             | 
             | A simple example: I like to have some electricity
             | switch/breakers automation, the best I've found are from
             | Shelly Pro series, witch have a not really useful webserver
             | built-in and not really useful APIs the rest are even worse
             | having no wired versions at all. Why the hell not offer
             | manual breaker + two wires for modbus so I do not need to
             | fit ethernet wires and power in the same place?
             | 
             | Why just finding classic ModBUS-tcp/MQTT wired devices is
             | so hard?
             | 
             | Things meant to be integrated does not need shiny UIs, need
             | effective ways to integrate them, simple and reliable coms.
             | My VMC witch is not a dirty cheap device have mobus
             | support, unfortunately even the vendor do not know a full
             | list of all registry and many of them does works
             | "sometimes" like "write a 1 to switch from heating to
             | passive ventilation", "sometimes works", so to integrate it
             | I need to check if the command was "accepted" after 30"
             | then re-check it after 40 because sometimes it flip back
             | for unknown reasons... And the list is long...
        
             | morelisp wrote:
             | Not strident enough. Properly designed Ts wouldn't have I
             | at all in my book.
        
           | diego_sandoval wrote:
           | Possibly dumb question: Why not write it in a compiled
           | language like C or Go?
        
             | synergy20 wrote:
             | because it's a script, we have bash and c on a linux and we
             | need both, same to embedded devices.
        
             | paulddraper wrote:
             | Because then you need a computer and build process.
             | 
             | Or, pre compile it across all target platforms and have an
             | install process.
        
             | kkfx wrote:
             | Personally because it's quick. Sometimes I just need to
             | automate some set of CLI commands... Of course sometimes
             | things evolve and it's time to replace the script with
             | something more easy to handle at the new scale, but for
             | simple stuff, meaning something that can fit a single page
             | or two they are far quicker.
             | 
             | BTW in a broad topic: a classic system with a user-
             | programming language as the topmost human computer
             | automation/interface is obviously better, but we have had
             | such systems in the past and commercial reasons have push
             | them to oblivion so...
        
             | justapassenger wrote:
             | Bash is perfect for interacting with console tools.
             | 
             | If that's what you need to do, C/go won't only be much
             | longer code, but also much harder and error prone. Command
             | line tools are complex to deal with and there's no magic
             | bullet language for that.
        
         | bluGill wrote:
         | Bash is usful for 100 lines scripts that do little logic and
         | mostly chain together various commands. Setup the right CC
         | variables and call make.
        
           | leosarev wrote:
           | I say ten. Ten lines maximum
        
         | paulddraper wrote:
         | Okay, let's say that you want a script that counts the number
         | of lines in files versioned by git.
         | 
         | You'd write a Python3 script I assume? With subprocess?
        
           | jzwinck wrote:
           | Toy examples should not guide larger decisions. And even if
           | that trivial script you describe is really what goes into
           | production today, tomorrow someone will modify it and
           | introduce a quoting bug or a poorly-done command line option
           | facility or whatever.
        
             | paulddraper wrote:
             | Huh?
             | 
             | What makes this a plaything?
             | 
             | Did you respond to the right comment?
        
               | jzwinck wrote:
               | You asked about:
               | 
               | > a script that counts the number of lines in files
               | versioned by git
               | 
               | I'm saying that is not a realistic production program
               | that most of us would need.
               | 
               | If you want it as a personal utility to use in your own
               | shell, absolutely you can use bash. I'm responding to the
               | idea that such a trivial script would have long term use
               | in production.
        
             | jenscow wrote:
             | To mitigate that, recently there was something posted on HN
             | that checks your shell script for those types of bugs.
             | 
             | However, let's not produce any software at all, in case
             | someone introduces a bug in it later.
        
         | tgv wrote:
         | Don't use bash, don't use C, don't use C++, don't use Python,
         | don't use Javascript, don't use Ruby, ...
        
           | paulddraper wrote:
           | Don't use computers.
           | 
           | Only winning move
        
         | fooker wrote:
         | There are two kinds of languages, ones that everyone complains
         | about, and those that nobody uses.
        
           | devnullbrain wrote:
           | Everyone uses Python
        
             | spoiler wrote:
             | People complain about various python and its ecosystem's
             | quirks all the time, though!
        
         | justapassenger wrote:
         | If you have to interact with console tools, nothing beats bash.
         | 
         | If you don't have to, you should never use it.
        
         | Alupis wrote:
         | One does not program in bash. It is a scripting language -
         | there's a difference, even if subtle.
         | 
         | Just like any other tool, commit the time to learn it instead
         | of just complaining it's hard.
         | 
         | Developers tend to think they can write amazing things with
         | minimal effort and then curse the tool/lang when things turn
         | out different.
         | 
         | The world runs on C and bash scripts... and it's just fine.
        
       | goombacloud wrote:
       | To spot more common problems I recommend:                 alias
       | shellcheck='shellcheck -o all -e SC2292 -e SC2250'
        
         | throw0101a wrote:
         | SC2292: Prefer [[ ]] over [ ] for tests in Bash/Ksh.
         | 
         | * https://www.shellcheck.net/wiki/SC2292
         | 
         | SC2250: Prefer putting braces around variable references
         | (${my_var}) even when not strictly required.
         | 
         | * https://www.shellcheck.net/wiki/SC2250
        
       | ovex wrote:
       | Recently, I found a privilege escalation vulnerability in a shell
       | script as a result of arithmetic expansion (similar to the one
       | described at https://research.nccgroup.com/2020/05/12/shell-
       | arithmetic-ex...). For example, $((1 + ENV_VAR)) allows you to
       | inject code if you can control $ENV_VAR.
       | 
       | Unfortunately, shellcheck did not catch that. At least not with
       | the default settings. But if you are implementing anything
       | remotely security-critical, you should not be using shell anyway.
        
       | mmsc wrote:
       | Shellcheck is great. Unfortunately, its checks pale at the
       | idiosyncrasies of per-version bashism.
       | 
       | For example:                 set u       ignored_users=()
       | for i in "${ignored_users[@]}"; do         echo "$i"       done
       | 
       | passes shellcheck's checks, however bash <= 4.3 will crash with
       | "bash: ignored_users[@]: unbound variable". Therefore, set -u
       | isn't available to use in this (valid) use-case.
       | 
       | Shellcheck also doesn't catch the expansion of variables as key
       | names in testing assoc arrays:                 declare -A
       | my_array       un='$anything'            [[ -v my_array["$un"] ]]
       | && return 1
       | 
       | will will fail as "my_array: bad array subscript" because "$un"
       | gets expanded to "$anything", which on a second pass, gets
       | expanded to "", making the check [[ -v my_array[] ]]. Even worse,
       | a value of                 un='$(huh)'
       | 
       | actually gets executed:                 [[ -v my_array["$un"] ]]
       | && return 1       -bash: huh: command not found
       | 
       | Here's another one: in versions older than 4.3 (maybe?) these -v
       | checks don't even work:                 $ declare -A my_array
       | $ my_array["key"]=1       $ [[ -v 'my_array["key"]' ]] && echo
       | exists       $ [[ -v my_array["key"] ]] && echo exists       $ [[
       | -v $my_array["key"] ]] && echo exists       $ [[ -v
       | "$my_array["key"]" ]] && echo exists       $ bash --version
       | GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)
       | 
       | I've recently been documenting some of this on my website:
       | https://joshua.hu/more-fun-with-bash-ssh-and-ssh-keygen-vers...
        
       | throw0101a wrote:
       | Lots of mentions of this:
       | 
       | * https://news.ycombinator.com/from?site=shellcheck.net
       | 
       | with the last large-scale discussion (301 points; 54 comments)
       | being in 2021:
       | 
       | * https://news.ycombinator.com/item?id=27030504
        
         | pvg wrote:
         | It's actually 'follow-up dupe' of this
         | https://news.ycombinator.com/item?id=38387464 where it comes up
         | repeatedly.
        
       | hyllos wrote:
       | I've turned some time ago a build and deploy script (single
       | production server) some bash scripts into Haskell using Turtle
       | [1]. What I enjoyed was the ability to reduce redundancies
       | significantly. It was significantly shorter code afterwards.
       | 
       | [1] https://hackage.haskell.org/package/turtle
        
         | mrkeen wrote:
         | I recently tried Turtle but ended up throwing it out in favour
         | of typed-process.
         | 
         | Afaik a Turtle program has a single current directory, which
         | makes it hard when you want to run concurrent jobs that need to
         | be executed from particular directories. I partially solved the
         | problem by using locks/queues/workers. But it got too much for
         | me when Turtle started failing due to its current directory
         | being deleted.
         | 
         | In contrast, typed-process lets you spawn separate processes,
         | and execute within a working dir (rather than needing to _cd_
         | there), so it works great for big, complicated workflows.
         | 
         | And it also has good support for OverloadedStrings, which means
         | you can generally copy & paste what you would have typed into
         | bash, and it just works.
         | 
         | I also use the _interpolate_ package (with QuasiQuotes) to make
         | the raw strings nicer in the source code, but it 's not
         | compatible with hlint, so I'm thinking of looking for a
         | different package for string-handling.
        
       | mr-wendel wrote:
       | Some tips of my own:
       | 
       | - It's almost always preferable to put `-u` (nounset) in your
       | shebang to cause using undeclared variables to be an error. The
       | only exception I typically run across is expansion of arrays
       | using "${arr[@]}" syntax -- if the array is empty, this is
       | considered unbound.
       | 
       | - You can use `-n` (noexec) as a poor-man's attempt at a dry-run,
       | as this will prevent execution of commands.
       | 
       | - Also handy is `-e` (errexit), but you must take care to observe
       | that essentially, this only causes "naked" commands that fail to
       | cause an exit. Personally, I prefer to avoid this and append `||
       | fail "..."` to commands liberally.
        
         | Calzifer wrote:
         | > to put `-u` (nounset) in your shebang
         | 
         | Any particular reason why in the shebang instead of set -u?
         | 
         | > The only exception I typically run across is expansion of
         | arrays using "${arr[@]}" syntax
         | 
         | In Bash? Works for me. Edit: another comment mentions it as
         | well. Seem to behave better in newer versions of Bash and only
         | problematic in <= 4.3
         | https://news.ycombinator.com/item?id=38397241                 $
         | bash -uc 'unset x; echo "=> ${x[@]}"'       =>       $ bash -uc
         | 'x=(); echo "=> ${x[@]}"'       =>       $ bash -uc 'x=(); echo
         | "=> ${x[0]}"'       bash: x[0]: unbound variable
         | 
         | Zsh does not like the first example but both should support:
         | $ bash -uc 'unset x; echo "=> ${x[@]:-null}"'       => null
         | 
         | > Also handy is `-e` (errexit),
         | 
         | It is unfortunately very confusion with functions. Made me like
         | it less over the years.
        
           | mr-wendel wrote:
           | Using the shebang just helps highlight the fact that the rule
           | is in use globally, but otherwise has no advantage to using
           | `set -u`.
           | 
           | The clarifications on `-u` and arrays are useful. I'm
           | definitely used to assuming newer (... non-ancient?) versions
           | of Bash are what is available.
        
             | Xophmeister wrote:
             | Using `set -u` is more portable. If your shebang is
             | `/usr/bin/env bash`, which it probably should be, then you
             | can't add additional command line arguments in Linux with
             | older coreutils. macOS supports additional arguments,
             | regardless, and in Linux, coreutils 8.30 added the `-S`
             | option to `env` to get around this problem.
        
         | augusto-moura wrote:
         | The problem with "${arr[@]}" only exists on bash 3 and before,
         | since bash 4, [@] will never throw unbound variables even in
         | cases where the variable is truly undefined. This is still a
         | problem however, because macOS, to this day, still installs
         | bash v3 by default and doesn't update it automatically
         | (absolute madness, the last release of bash 3 it's from 20
         | years ago!).
         | 
         | In any case, you can workaround expanding empty arrays throwing
         | unbound by using the ${var+alter} expansion
         | echo "${arr+${arr[@]}}"
        
           | mmsc wrote:
           | The problem with "${arr[@]}" only exists on bash 3 and
           | before, since bash 4
           | 
           | 4.4 fixed it:                 $ bash --version       GNU
           | bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)       $
           | declare -A arr       $ set -u       $ "${arr[@]}"
           | -bash: arr[@]: unbound variable
        
             | augusto-moura wrote:
             | Ah, that's true, I couldn't recall which version fixed it.
             | Usually I assume v4 because any other distro automatically
             | updates to the latest v4 version or latest version af all.
             | 
             | macOS is the only one out there missing on this. The other
             | big feature that was only added after bash 3 and is missing
             | on mac is associative arrays
        
       | jamespwilliams wrote:
       | Shellcheck is a godsend
       | 
       | https://github.com/jamespwilliams/strictbash, I wrote this little
       | wrapper a while back that you can use as a shebang for scripts.
       | It runs shellcheck for you before the script executes, so it's
       | not possible to run the script at all if there are failures. It
       | also sets all the bash "strict mode" [0] flags.
       | 
       | [0] http://redsymbol.net/articles/unofficial-bash-strict-mode/
        
       | w10-1 wrote:
       | Shellcheck is great, but requires some investment to tailor to
       | your style
       | 
       | Disable default checks or enable optional ones using directives:
       | https://www.shellcheck.net/wiki/Directive
       | 
       | The error checks can be pretty arcane:
       | https://github.com/koalaman/shellcheck/wiki/Checks
       | 
       | I appreciate that the text for each check is brief and usually
       | includes a suggestion. I end up disabling 26xx's a lot (for
       | unquoted variables to be interpreted as multiple values).
       | 
       | Python is probably the best alternative to bash, but Swift is
       | getting surprisingly good.
       | 
       | With shwift[1] you get NIO/async APIs, operator overloading for
       | shell-like locutions, and trivial access to existing executables:
       | /// Piping between two executables         try await echo("Foo",
       | "Bar") | sed("s/Bar/Baz/")              /// Piping to a builtin
       | try await echo("Foo", "Bar")             | map {
       | $0.replacingOccurrences(of: "Bar", with: "Baz") }
       | 
       | Scripts can easily be configured with libraries and run pre-
       | compiled by using clutch[2].
       | 
       | For cross-platform use, be sure only use libraries on all
       | platforms (i.e., not Foundation). It's a pain, but at least the
       | error shows up typically at compile-time instead of run-time.
       | 
       | [1] - [shwift](https://github.com/GeorgeLyon/Shwift)
       | 
       | [2] - [clutch - any Swift scripts in a common
       | nest](https://github.com/swift-nest/clutch)
        
       | 1vuio0pswjnm7 wrote:
       | What about finding bugs in other peoples' shell scripts.
        
       ___________________________________________________________________
       (page generated 2023-11-23 23:00 UTC)