1. 36
  1.  

  2. 10

    Some pet peeves when it comes to bash/shell programming where I wish the alternatives were more well known/used/practiced.

    • Using a process when the expansion rules or basename will do. Stuff like foo=x_y_z; bar=$(echo "${foo}" | sed 's/\(.*\)_.*/\1/') instead of the simpler bar=${foo%_*} or if you want to take the extension off a filename, foo=$(basename "${foo}" .ext). (${foo%.ext} works just as well.)
    • No need for that "x${foo}" = "x" test idiom if you are using a modern shell. Use [[. (The article mentions this one.)
    • Remembering that you have sequences ({...}) and subshells ((...)). What is particularly egregious is using cd instead of using a subshell with the cd instead. Otherwise, your script is now running in who-knows-where and have fun trying to follow the script when you’re reading it. (Even worse is using pushd and popd. Those are okay in an interactive shell. They suck in a script.)
    • Setting environment variables/parameters for a single command. FOO=1 BAR=1 some-command is much nicer than exporting them then unsetting them. If you have to export them, use a subshell.
    • set -euf -o pipefail if you’re writing any semi-serious script.
    • [[ -z "${SOME_CUSTOM_DEBUG_NAME}" ]] || set -x at the very top so you can trace it easily if necessary.

    Another thing I’ve seen that is truly horrendous: creating an enviornment “stack” using tons of variables and eval. That is, each environment variable becomes like a stack. You can “pop” the environment and restore it to a previous state. All that insane machination goes away if you use subshells.

    1. 3

      Mostly in agreement, though:

      • set -euf -o pipefail if you’re writing any semi-serious script.

      -e, -u, and pipefail make sense to me, but why -f? What I find useful is shopt -s nullglob, so you can do things like for f in foo/*.baz; do ...; done and have it cleanly execute zero times if no such file exists, instead of clunkily executing once with f set to the literal pattern.

      • [[ -z “${SOME_CUSTOM_DEBUG_NAME}” ]] || set -x at the very top so you can trace it easily if necessary.

      Does wiring this logic into your script have any advantages over just invoking it as e.g. bash -x myscript?

      1. 2

        I use -f because I’ve been burned too many times by undefined variables. (Sorry, I forgot that it’s -u that I was thinking about.) I avoid pathname expansion because, like wildcard in Makefiles, it tends to catch more than you want and is hard to debug. I also don’t use the for f in *; do ..; done approach. I use find foo | while read f; do ...; done instead (or while read f; do ...; done < <(find foo) if the shell supports process substitution).

        As for the “DEBUG” thing, I prefer that because if your script is invoked by something else you don’t have to change the invocation.

    2. 6

      Wow, I considered myself a bash advanced user, but you always learn something new with one of these posts!

      In this case, I found a new substitution command, !$. I’d been using !!:2 for some time but never thought of $ as a “last item” replacement. Nice!

      1. 1

        There’s also the keybinding M-. (and C-M-y), see bind -P and help bind

      2. 2

        Oh, I am not sure if I’ve ever stumbled upon :h before, but It looks really useful! And I must admit, that I had just forgotten about <(). Had seen it before, but never really used it, forgot it and used temp files just two weeks ago, going to replace them with <()>

        1. 2

          When I see questions about [ vs [[ I tell people to execute the command “which [”

          1. 1

            This article furthers my belief that hardly anyone reads the manual anymore.

            :h

            What version of Bash supports this?

            1. globbing vs regexps

            shopt -s extglob adds some nice expansions.

            1. 2

              This article furthers my belief that hardly anyone reads the manual anymore.

              Could be, at least not the manuals of their shells. I work with Linux systems for a living but I do admit, that I never read the whole bash or zsh manual from top to bottom. Most of my knowledge has been acquired from books, coworkers, and searches. I do often search in the bash manpage before searching online, but I never really stumbled upon the section about :h there until I had a look yesterday after this post.

              (And found quite a few other interesting ones in ‘history expansion / modifiers’)

              What version of Bash supports this?

              Not sure what you mean here. bash 4.4-5 on my debian stretch machine does support it, has it been in the first release already?

              1. 1

                bash 4.4-5 on my debian stretch machine does support it, has it been in the first release already?

                I see the problem. The author’s original post contained an error:

                grep isthere /long/path/to/some/file/or/other.txt
                ls /long/path/to/some/file/or/other.txt:h
                

                This has been removed.