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.
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?
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.
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 <()>
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?
Some pet peeves when it comes to bash/shell programming where I wish the alternatives were more well known/used/practiced.
basenamewill do. Stuff likefoo=x_y_z; bar=$(echo "${foo}" | sed 's/\(.*\)_.*/\1/')instead of the simplerbar=${foo%_*}or if you want to take the extension off a filename,foo=$(basename "${foo}" .ext). (${foo%.ext}works just as well.)"x${foo}" = "x"test idiom if you are using a modern shell. Use[[. (The article mentions this one.){...}) and subshells ((...)). What is particularly egregious is usingcdinstead of using a subshell with thecdinstead. 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 usingpushdandpopd. Those are okay in an interactive shell. They suck in a script.)FOO=1 BAR=1 some-commandis much nicer than exporting them then unsetting them. If you have to export them, use a subshell.set -euf -o pipefailif you’re writing any semi-serious script.[[ -z "${SOME_CUSTOM_DEBUG_NAME}" ]] || set -xat 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.Mostly in agreement, though:
-e,-u, andpipefailmake sense to me, but why-f? What I find useful isshopt -s nullglob, so you can do things likefor f in foo/*.baz; do ...; doneand have it cleanly execute zero times if no such file exists, instead of clunkily executing once withfset to the literal pattern.Does wiring this logic into your script have any advantages over just invoking it as e.g.
bash -x myscript?I use(Sorry, I forgot that it’s-fbecause I’ve been burned too many times by undefined variables.-uthat I was thinking about.) I avoid pathname expansion because, likewildcardin Makefiles, it tends to catch more than you want and is hard to debug. I also don’t use thefor f in *; do ..; doneapproach. I usefind foo | while read f; do ...; doneinstead (orwhile 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.
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!!:2for some time but never thought of$as a “last item” replacement. Nice!There’s also the keybinding
M-.(and C-M-y), seebind -Pandhelp bindOh, I am not sure if I’ve ever stumbled upon
:hbefore, 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<()>When I see questions about [ vs [[ I tell people to execute the command “which [”
This article furthers my belief that hardly anyone reads the manual anymore.
What version of Bash supports this?
shopt -s extglobadds some nice expansions.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
bashorzshmanual 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:hthere until I had a look yesterday after this post.(And found quite a few other interesting ones in ‘history expansion / modifiers’)
Not sure what you mean here.
bash 4.4-5on 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:
This has been removed.