Bash: completion, the complete builtin
In the previous article we've looked at how Bash completion works at the simplest level. We've also seen that the complete builtin comes with 26 options to influence its behavior. Let's break them down piece by piece, until we have a complete understanding of complete. Grouping options While the number of options to complete seems overwhelming, every option actually falls into one of a handful of groups: generators supply possible words to complete (-G for globs, -W for word lists, -A for ...
Read post
Bash: completion, introduction
If you have ever used the TAB key in your terminal, you have interacted with your shell's programmable completion facilities. Sometimes you probably have found existing completions to be lacking sometimes and wondered about how you can add your own. The Bash manual explains this in great detail, but not in a very accessible manner. The Process Bash is going through a series of steps to arrive at the list of completion candidates that it is ultimately displaying: command --> compspec ...
Read post
Bash: a workflow for developing scripts
REPL-driven development Like in any programming environment, developing larger chunks of code follows the common cycle of making a change, running your script and observing the result, and finally repeating the process. Shell scripts aren't different in this regard. One important difference however is that since fundamentally the shell is a highly interactive environment, we can easily achieve a nice REPL experience by tweaking a few things in the code we are working on and adding a few defin...
Read post
Bash: re-using code
Changes are that you have a lot of bash scripts in your project(s) and that some of those scripts contain bits and pieces that you would like to re-use. The prospect of making your shell scripts reusable might seem daunting, but Bash actually comes with a couple of mechanisms that make this easier than you might have thought. Using the source The easiest way to re-use a piece of Bash code is to load it into the current process. This is exactly what source does. Actually, you can think of sou...
Read post
Bash: quoting code
Quoting, in reverse While quoting inputs to Bash requires some care, little is talked about quoting output in a way that is safe for Bash to evaluate. This opens the gate to metaprogramming: if programs like Bash can generate correctly quoted Bash code, we can safely evaluate the output of those programs. This functionality silently snuck into popular tools, making Bash a valid output format, which opens up exciting possibilities. The testing function q Let's define a function q (for quote)...
Read post
Bash: declare in depth: Part 5: variable scoping
tl;dr declare outside of a function defines a global variable, declare inside of a function defines a local variable, declare -g inside of a function defines a global variable. dynamic vs lexical scoping Before we can look more deeply at how variables are scoped in bash, a quick refresher on dynamic vs lexical scoping is in order. The value and visibility of a dynamically scoped variable depend on the call stack, whereas a lexically scoped variable is only visible in its lexical (read: sou...
Read post
Bash: declare in depth: Part 4: the oddballs
We've covered almost all of declare's options, but there are two that stand out for being of questionable usefulness: -l and -u. These flags change the case of a variable to lowercase (-l) and uppercase (-u) respectively when they are assigned. In total, I counted three ways for changing casing in Bash: declare with -u or -l parameter expansion with ^^ and ,,: ${to_upper^^} and ${to_lower,,} using an @ parameter during expansion: ${to_upper@U} and {to_lower@L} Interaction with namerefs So...
Read post
Bash: declare in depth, Part 3: arrays
Bash supports two types of arrays: numerically indexed arrays and associative arrays (i.e. indexed by a string). To create an array, pass -a or -A (-A for associative arrays) to declare (or any of its variable-defining cousins like local and readonly). Some people think that needing arrays is the point where you should switch to another language. In some contexts this can certainly be true (e.g. nobody on the team knows how arrays work in Bash), but comes at a cost: shells are still the most ...
Read post
Bash: declare in depth, Part 2: quoting and eval
Quoting bash scripts correctly is considered difficult by many, and rightfully so if you don't know about Bash's builtin facilities for safely quoting code. Why would you need to quote bash code? For meta-programming! In total, bash has three ways for you to quote code: printf using %q will quote any string suitable as input to Bash, the Q operator in parameter expansion will quote a variable: ${var@Q} declare can dump both functions and variables of all types as bash code! Quoting with de...
Read post
Bash: using regular expressions
Working with strings Bash comes in with a few built-in features during parameter expansion to work with strings: Generic find and replace: ${var//source/dest} replaces all occurrences of source in $var with dest Removing prefixes and suffixes: ${var##prefix} removes the longest prefix from $var, where prefix can be any Bash pattern (replace ## with %% to remove a suffix instead). This is useful for working with paths: p=/usr/local/lib/libexample.so printf "filename: %s\n" "${p##*/}" # prin...
Read post
Bash: declare in depth, Part 1
If you run help declare in Bash, you will see that this builtin supports an overwhelming 14 different options for defining variables. Actually, declare is a family of functions and you have used some of its cousins like export and local already. Some of these options allow for powerful metaprogramming (like namerefs), others help you make your scripts more robust. Let's look into some of the more commonly useful options. constants You can declare a variable as read-only (or constant), by us...
Read post
Bash: Indirectly referencing variables
Sometimes it comes in handy to modify a variable in a function by name. Usually the first tool people reach for in this case eval: # sets a variable to a new value setvar() { eval $(printf "%s=%q" "$1" "$2") } setvar x 2 printf "x=%s\n" "$x" # prints x=2 This works but making sure that everything is quoted correctly is cumbersome. Bash supports a safer way for this: namerefs To use a nameref you must declare a variable as a nameref using the -n argument. After that, any changes made to th...
Read post
hello, world
hello, world The Bash shell is used by many people every day as their many interface to their computer. Yet, how many people actually know what Bash is capable off? It is easy to learn the first 20% of Bash to do useful things and then stop learning. Here I'll share some of the lesser-known features of Bash and how to integrate them into your workflow. Stay tuned! ...
Read post