1. 41
  1.  

  2. 9

    I get the desire for stuff like this. For many years I would try to find shell solutions for my programming problems. But if its a choice between shell:

    trim_string() {
       : "${1#"${1%%[![:space:]]*}"}"
       : "${_%"${_##*[![:space:]]}"}"
       printf '%s\n' "$_"
    }
    

    and PHP:

    trim();
    

    I am going to take PHP, every time. Use the right tool for the job. Note PHP performance is probably an order of magnitude better as well. Sub PHP for your language of choice.

    1. 8

      Please don’t, do as less as possible with shell scripts and keep them short!

      What most shell scripts that I have encountered so far have in common is that they don’t have any tests. As @cup already said, I would choose Python or Go anytime over those “clever” bash hacks because I can make sure that they do what I expect they do and do not break on the slightest change.

      Edit: formatting

      1. 9

        I always enjoy seeing bash scripts that demonstrate the full capability of the language, but I never find myself writing them. The point at which I need an array in bash (for example), is usually the point at which I rewrite in Python or Perl.

        1. 4

          I like starting with shell scripts, working to a pain point, then going into Ruby or Python - but only briefly. Those little programs are to get me through the suck (like deeply nested JSON which I can never remember how to make jq handle). They eat stdin and barf to stdout so they fit with the pipeline. Then it’s back to shell.

          I like this because when I rewrite I always end up with a solution that’s tightly fitted to the specific problem; when I stick with shell + helper ruby I end up with more reusable widgets.

        2. 8

          If you find yourself writing (or debugging) bash (or sh) you should use shellcheck as well https://www.shellcheck.net/

          1. 3

            Great advice, shellcheck is awesome.

            I always use shellcheck, and all of my bash scripts start with

            #!/bin/bash
            
            set -euo pipefail
            

            Together those two things save me from so much frustration!

            1. 4

              I can’t help but point out that should be #!/usr/bin/env bash. But good work on the set statement. Sorry ;)

              1. 3

                Well, I guess it depends. I mostly write shell scripts for my job as sysadmin, and I prefer to use /bin/bash for those. Not all systems are fully under my control, and /usr/bin/env bash would mean that I’d be at mercy of the PATH variable. Since I know that I have system bash available in /bin/bash I prefer to hardcode that.

                For more portable scripts, or for scripts in other languages, yes, then I’d use /usr/bin/env.

                1. 3

                  Another thing I like to do is to fail fast; avoid trying to do error handling unless there’s a good reason to do so. It’s often just as good to just fail hard and log what went wrong.

                  I sometimes add a trap for the ERR signal, which causes bash to call the trap function upon exit:

                  A trap on ERR, if set, is executed before the shell exits.

                  Something like this:

                  declare -ri             \
                          EXIT_SUCCESS=0  \
                          EXIT_WARNING=1  \
                          EXIT_CRITICAL=2 \
                          EXIT_UNKNOWN=3
                  
                  declare -i EXIT=$EXIT_UNKNOWN
                  declare STATUS='UNKNOWN - Exit before logic'
                  
                  function _exit() {
                    # Status and quit
                    echo "${STATUS}"
                    exit $EXIT
                  }
                  
                  trap _exit ERR
                  

                  This ensures that no matter how the script exits my exit handler function will always get called. The particular snippet above is from an Icinga check I’m writing right now :)

                  1. 1

                    From my live NixOS system:

                    bb010g ~ % file /{,usr/}bin/bash
                    /bin/bash:     cannot open `/bin/bash' (No such file or directory)
                    /usr/bin/bash: cannot open `/usr/bin/bash' (No such file or directory)
                    
                    1. 2

                      Yes, which is why I wrote that for more portable scripts I’d use /usr/bin/env ;)

                      (…that does exist, doesn’t it?)

                      I’ve played around with Guix a bit, same thing there. But the systems I manage are neither GuixSD nor NixOS, so my approach works just fine for my purposes.

            2. 3

              (Even) Perl is more readable.

              1. 1

                It’s a cool idea, but these hardly scratch the surface of annoyances I struggle with in bash.

                These snippets though recommend things I don’t agree with:

                • I think relying on special parameters like _ to avoid temp variables to be a disservice to readability.
                • Many of the expansions remind me of some of the more extreme symbol noise one liners I complain about in zshell, especially the trim_string snippet. Breaking these into individual steps helps significantly in my opinion.
                • Even though sticking to built-ins are admirable, things like the urlencode method will very likely be slower than using an external utility. Spawning processes is not nearly as expensive as the author makes it sound.