Quirks
Bash is known to have many “sharp edges” and quirks. Because it is such an old language, its syntax may be unfamiliar, and data flow can be hard to understand when reading Bash scripts after learning a more modern language.
One page in a hobbyist’s documentation is nowhere near enough to cover the sheer number of edge cases, unexpected behavior, and arcane rules hidden deep within Bash, but I can still document some of the more common footguns.
Shebang
Every Bash script should start with a “shebang,” which is a string of text that tells the shell which interpreter to use. The most common example you’ve likely seen before is #!/bin/bash, or sometimes #!/usr/bin/bash.
This works fine for the majority of scripts, but a string that’s compatible with more environments and shells is #!/usr/bin/env bash. This string will find the bash executable for your environment, which may very well be /bin/bash, but could be in a different location. This shebang uses the env command to display environment variables, and select the first available location for Bash from the $PATH variable.
#!/usr/bin/env bash will always resolve to a working Bash interpreter if one is installed, and so it’s recommended for maximum compatibility.
Return types
Bash beginners often find it difficult to master Bash return types. In other languages, you might be used to returning a value or object, but in Bash, the return keyword returns a numeric value meant to represent success with 0 or an error with 1 (or another number).
As an example, say you want to echo a value to the command line. You might be inclined to write something like:
#!/usr/bin/env bash
function say_hello() {
local name
name="$1"
echo "Hello, ${name}"
}
function get_name() {
read -r -p "What is your name? " _name
return "${_name}"
}
user_name=$(get_name)
say_hello "${user_name}"But this would result in an error like this:
$ ./ex.sh
What is your name? jack
./ex.sh: line 13: return: jack: numeric argument required
Hello,This is because the return keyword expects a numeric value to indicate success/failure. You can still “return” strings from a function by echoing them and capturing them in a variable:
#!/usr/bin/env bash
function say_hello() {
local name
name="$1"
echo "Hello, ${name}"
}
function get_name() {
read -r -p "What is your name? " _name
# return "${_name}"
echo "${_name}"
}
user_name=$(get_name)
say_hello "${user_name}"$ ./ex.sh
What is your name? jack
Hello, jackVariables and curly braces
One seemingly stylistic choice in Bash that actually has a specific purpose is surronding Bash variables in ${curly_braces}. You will often see Bash variables referenced one of 4 ways:
$var${var}"$var""${var}"
The curly braces are optional in a lot of cases, and explicitly required in others.
When to use curly braces when referencing a variable name
There is an argument that you should always surround Bash variable references in braces, the reason being it will almost never cause a problem, and it will always avoid specific edge cases.
The times where curly braces are required are when concatenating a string, iterating over an array variable, positional parameters 10 and above, and when expanding or mutating a variable.
Curly braces and string concat
Curly braces are required when concatenating string in a script, you must use curly braces to separate the variable from the rest of the word. If you do not surround the variable in braces, Bash will think the variable and the word you’re concatenating are all 1 thing:
#!/usr/bin/env
## Set a word to variable $word
word="back"
## Concatenate
echo "$wordpack"This code would echo an empty string because ‘$wordpack’ was not declared.
Instead, surround the variable name in braces:
#!/usr/bin/env bash
word="back"
## Surround variable in braces to separate it from the concatenated text
echo "${word}pack"
## Echoes: "backpack"Since "$var" and "${var}" are equivalent, there is no real reason not to get into the habit of surrounding variable references in curly braces.
Iterating over array values
In Bash, an array is a variable declared with values inside (parentheses), like:
The declare -a keyword
It is generally good practice, but not required, to use declare -a before declaring a variable, like: declare -a array_variable=().
Bash does not require this, but it helps with clarity when reading over a script later.
#!/usr/bin/env bash
declare -a arrvar=(test test1 test2)
echo "${arrvar}"The above example is deceptive, because it will work, printing test test1 test2. This is because Bash prints the array as a string. However, if you were to try iterating over items in the array, like:
#!/usr/bin/env bash
declare -a arrvar=(test test1 test2)
for v in "${arrvar}"; do
echo "${v}"
doneYou would see only 1 iteration where it printed all 3 values on 1 line. This is because to iterate over an array, you have to expand it with [@]:
#!/usr/bin/env bash
declare -a arrvar=(test test1 test2)
for v in "${arrvar[@]}"; do
echo "${v}"
doneThis would print each value on a newline, i.e.:
test
test1
test2Without the [@], referencing an array like ${varname} results in Bash treating it like a string, essentially.
For positional parameters
When referencing positional parameters in a script, you must surround any value 10 or greater.
For example, if you have a script like:
#!/usr/bin/env bash
echo $1This will echo the first value passed after the script, i.e. ./script_name.sh something would echo “something.”
If you are passing 10 or more parameters, you must surround them in curly braces:
#!/usr/bin/env bash
echo "The 10th parameter is: ${10}"This is because Bash reads $10 as $1, followed by a literal 0.
Parameter expansion
Bash can “expand” parameters by using special symbols inside curly braces. For example, to set a default value for a script but allow reassignment later, you can write:
${var:-default_value}Or to get the length/number of characters in a variable’s value:
${#var}You can also convert the case of a string with expansion:
| Example | Casing |
|---|---|
| ${var^^} | UPPERCASE |
| ${var,,} | lowercase |
| ${var^} | Uppercase first character |
| ${var,} | lowercase first character |