Bashisms
As a reminder, this style guide is specific to Bash. When given a choice, ALWAYS prefer Bash built-ins or keywords over external commands or sh(1)
syntax.
This section covers common Bashisms, which are idiomatic practices and features specific to the Bash shell (1). By understanding and using these Bash-specific constructs, you can write scripts that are more efficient, readable (2), and robust.
- Note on Bashisms: Bashisms refer to features and practices unique to the Bash shell. However, it is worth noting that shells derived from the Bourne Again Shell, such as
dash
andzsh
, also support many of these features. - Readability: While Bashisms generally enhance readability, certain functionalities, such as parameter expansion syntax, can be confusing for beginners. However, the robustness and efficiency of Bashisms make them worth the learning curve.
Conditional Tests
Conditional tests are fundamental to any programming language, enabling decision-making based on evaluating expressions. Bash provides several constructs for these tests, including [ ... ]
, [[ ... ]]
, and the test
command.
Guidelines
- Use
[[ ... ]]
: ALWAYS use the[[ ... ]]
construct when performing conditional tests. - Avoid
[ ... ]
andtest
: Avoid using[ ... ]
and thetest
command for conditional tests.
Advantages of [[ ... ]]
- Regex Support: Enables direct regex matching within conditional expressions, eliminating the need for external tools like
grep
. - String Comparison: Offers a more consistent and reliable approach to string comparison, especially when dealing with variables containing spaces or special characters.
- Compound Conditions: Allows combining multiple conditions within a single
[[ ... ]]
block, simplifying logic and improving readability. - Safety: Prevents word splitting and globbing on variables, reducing the risk of unexpected behavior or security vulnerabilities.
Examples
Using [ ... ]
:
var="value with spaces"
if [ $var = "value with spaces" ]; then
echo "The variable matches the value."
else
echo "This will output because '$var' is treated as multiple arguments."
fi
Potential Issues: Without proper quoting, [ ... ]
can cause unexpected behavior due to word splitting, treating the variable as multiple arguments.
Using [[ ... ]]
:
var="value with spaces"
if [[ $var == "value with spaces" ]]; then
echo "The variable matches the value."
else
echo "This will output because $var is treated as multiple arguments."
fi
Advantage: [[ ... ]]
safely handles variables with spaces, whether or not they are quoted.
Using grep
for regex matching:
email="hunter@hthompson.dev"
if echo "$email" | grep -qE '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; then
echo "Valid email address."
else
echo "Invalid email address."
fi
Disadvantage: Since grep
is an external command, the potential for errors and performance issues increases due to different implementations across systems.
Using [[ ... ]]
for regex matching:
email="hunter@hthompson.dev"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Valid email address."
else
echo "Invalid email address."
fi
Advantage: [[ ... ]]
directly supports regex matching, eliminating the need for external commands and enhancing script portability.
Using [ ... ]
for compound conditions:
file="example.txt"
touch "$file" && chmod 700 "$file"
if [ -f "$file" ] && [ -r "$file" ] || [ -w "$file" ]; then
echo "File exists and is readable and/or writable."
else
echo "File is either missing, unreadable, or unwritable."
fi
Disadvantage: [ ... ]
does not support compound conditions directly, requiring additional commands or constructs to achieve the desired behavior.
Using [[ ... ]]
for compound conditions:
file="example.txt"
touch "$file" && chmod 700 "$file"
if [[ -f $file && -r $file || -w $file ]]; then
echo "File exists and is readable and/or writable."
else
echo "File is either missing, unreadable, or unwritable."
fi
Advantage: [[ ... ]]
supports compound conditions, simplifying script logic and improving readability.
Sequence Iteration
Iterating over sequences is a common task in Bash, allowing you to process elements in a range or list. Bash provides built-in mechanisms for sequence iteration, such as brace expansion and C-style for
loops.
Guidelines
- Bash Built-ins: ALWAYS use brace expansion (
{start..end}
) for fixed ranges and the C-stylefor
loop for variable limits when iterating over sequences. - Avoid Using
seq
: Avoid usingĀseq
for sequence iteration, as it is an external command and not a built-in feature of Bash.
Advantages of Built-in Iteration
- Simplicity: Built-in mechanisms like brace expansion and C-style
for
loops are native to Bash, providing a straightforward and efficient way to iterate over sequences. - Reduced Dependencies: By utilizing built-in features, you minimize external dependencies, which enhances script reliability and maintainability.
Examples
Using seq
:
Using brace expansion:
Using seq
:
Using C-style for
loop:
Command Substitution
Command substitution allows you to capture the output of commands and use it as part of another command or assignment. Bash provides two syntaxes for command substitution: $(...)
and backticks (`...`
).
Guidelines
- Use
$(...)
: ALWAYS use the$(...)
syntax for command substitution instead of backticks. - Avoid Backticks: Avoid using backticks for command substitution.
Advantages of $(...)
- Improved Readability: The
$(...)
format is visually clearer, making scripts easier to read and understand, especially as commands become more complex. - Easier Nesting: Facilitates the nesting of multiple commands without the syntactic awkwardness associated with backticks.
- Enhanced Safety: The
$(...)
syntax is more robust and less prone to errors, particularly in complex command substitutions.
Examples
Using backticks:
Using $(...)
Explanation: The $(...)
syntax is clearer and preferred for command substitution.
Using backticks:
Using $(...)
:
Explanation: Nesting commands with $(...)
is easier to read and less error-prone compared to using backticks.
Arithmetic Operations
Arithmetic operations allow for mathematical calculations within Bash, enabling you to perform calculations, comparisons, and other numeric operations. Bash supports arithmetic operations using arithmetic expansions $((...))
, conditional arithmetic expressions ((...))
, and the let
command.
Guidelines
- Use
$((...))
and((...))
: ALWAYS use$((...))
for arithmetic expansions and((...))
for conditional arithmetic expressions. - Avoid
let
: Avoid usinglet
for arithmetic. - Reasoning:
$((...))
and((...))
provide a clearer and more intuitive syntax for arithmetic operations in Bash.
Advantages of $((...))
and ((...))
- Clarity: Both
((...))
and$((...))
clearly delineate arithmetic expressions within scripts, making them easy to identify and understand. - Simplicity: These constructs are natively supported by Bash, offering a streamlined and intuitive approach to handling arithmetic.
- Increased Safety: These methods specifically evaluate arithmetic without the risk of executing other commands, unlike let, which can misinterpret expressions as commands if not carefully handled.
Examples
Using let
:
Using $((...))
:
Explanation: The$((...))
syntax is clearer and reduces the risk of errors, making it the preferred method for basic arithmetic in Bash.
Parameter Expansion
Parameter expansion is a powerful feature in Bash that allows you to manipulate variables and perform string operations directly within the shell. Bash provides a wide range of parameter expansion options, including substring extraction, string replacement, and length calculation.
Guidelines
- Bash Built-ins: Utilize parameter expansion for string manipulations whenever possible. This approach is more efficient and reduces script complexity and dependencies.
- Avoid External Tools: Avoid using external tools like
sed
andawk
for string manipulation.- Reason: While powerful, these tools are often overkill for simple string manipulations that can be efficiently handled by parameter expansion.
Advantages of Parameter Expansion
- Streamlined Scripting: Keeps string manipulations inline and shell-native, simplifying the script's logic.
- Enhanced Portability: Improves script portability across different Unix-like systems by avoiding dependency on external tools, which may vary in availability or functionality.
Examples
Using sed
Using Parameter Expansion
Explanation: Parameter expansion provides a simpler and more efficient method for extracting substrings.
Using awk
Using Parameter Expansion
Advantage: Parameter expansion is simpler and more efficient for removing a suffix, reducing the need for external tools.
Using wc
Using Parameter Expansion
Advantage: Parameter expansion can directly compute the length of a string without invoking an external command, making it a more efficient choice.
Avoiding Parsing ls
While parsing the output of ls
may seem like a convenient way to list files and directories, this approach is generally discouraged due to the potential for errors and unexpected behavior. Instead, Bash provides more reliable and secure methods for file and directory parsing, such as Bash globbing patterns (*
).
Guidelines
- Bash Built-ins: ALWAYS use Bash globbing patterns like
*
or tools likefind
for file and directory parsing. - Avoid Using
ls
: Avoid usingls
in loops or where accurate filename interpretation is critical.
Risks of Parsing ls
- Word Splitting: Filenames with spaces or special characters can cause word splitting issues when parsing
ls
output. - Command Interpretation: Filenames starting with a hyphen (
-
) can be misinterpreted as options by commands likels
, leading to unintended behavior. - Potential Vulnerabilities: Although parsing
ls
output may not directly cause security vulnerabilities like code injection, improper handling of filenames can introduce bugs or unexpected behavior, which may lead to security issues in more complex scenarios.
Alternatives to Parsing ls
- Bash Globing: Use Bash globbing patterns like
*
for listing files and directories, ensuring reliable and secure file parsing. find
Command: For more complex file operations, consider using thefind
command, which provides extensive options for file and directory traversal.read
Command: When processing files or directories, use theread
command to safely parse inputs into variables, avoiding the need for external commands likels
.stat
Command: For detailed file information, use thestat
command, which provides extensive metadata about files and directories.file
Command: To determine file types, use thefile
command, which can identify the type of a file based on its contents.
Examples
Using ls
in a loop:
Using Bash Globing:
Explanation: Bash globbing handles filenames with spaces or special characters correctly, making it more reliable than parsing ls
output.
Using ls
with wildcards:
Using find
:
Explanation: The find
command is powerful and versatile, allowing you to search for files by various criteria and execute commands on the results, making it suitable for more complex file operations.
Using ls
with while
loop:
Using find
and read
:
find /path/to/dir -type f > filelist.txt
while IFS= read -r file; do
echo "Processing $file"
done < filelist.txt
Explanation: Using read
with IFS
(Internal Field Separator) set to handle filenames correctly ensures that the script processes each file safely, even if the filenames contain spaces or special characters.
Using ls -l
:
Using stat
:
Explanation: The stat
command provides comprehensive metadata about a file, including size, permissions, and modification times, offering more detailed information than ls -l
.
Using ls
with file extension check:
Using file
:
file_type=$(file --mime-type -b /path/to/file)
if [[ $file_type == "text/plain" ]]; then
echo "This is a text file"
fi
Explanation: The file
command determines the file type based on its content, rather than just its extension, providing a more accurate and reliable method for file type detection.
Element Collections
Element collections are a common feature in Bash scripting, allowing you to manage groups of items such as filenames, user inputs, or configuration values. Bash provides three primary methods for handling collections: arrays, associative arrays, and space-separated strings.
Guidelines
- Arrays for Collections: ALWAYS use arrays when managing collections of elements.
- Avoid Space-Separated Strings: Avoid using space-separated strings for collections.
Advantages of Arrays
- Clarity and Safety: Arrays prevent errors related to word splitting and glob expansion that can occur with space-separated strings.
- Flexibility: Arrays allow for straightforward manipulation and access to individual elements, as well as simpler expansion in commands that accept multiple arguments.
- Ease of Maintenance: Code utilizing arrays is generally clearer and easier to maintain, particularly as script complexity increases.
Examples
Using Space-Separated Strings
Using Arrays
Advantage: Arrays handle items with spaces or special characters correctly, preventing unintended word splitting.
Using Space-Separated Strings
Using Arrays
Advantage: Arrays simplify command syntax and ensure all arguments are correctly passed, even if filenames contain spaces.
//// Adding Elements to a Collection
Using Space-Separated Strings
Using Arrays
Advantage: Arrays provide straightforward syntax for adding elements, improving readability and maintainability.
////
Parsing Input into Variables
Parsing input into variables is a common task in Bash scripting, allowing you to extract and process data from user inputs, files, or other sources. Bash provides the read
command for these tasks, offering a more efficient and secure alternative to external commands like awk
, sed
, or cut
.
Guidelines
- Bash Built-ins: Use
read
to safely and efficiently parse user inputs and other data directly into variables. - Customize with IFS: Adjust the Internal Field Separator (IFS) as needed when using
read
to ensure that inputs are split according to your specific requirements, enhancing the flexibility and accuracy of data parsing. - Avoid External Commands: Minimize the use of external commands like
awk
,sed
, orcut
for parsing strings.
Advantages of read
- Efficiency:
read
operates within the shell, eliminating the need for slower, resource-intensive external commands. - Controlled Parsing:
read
allows for controlled and direct parsing of inputs into variables within the shell, reducing the complexity and potential risks associated with using external commands for input processing. - Simplicity: Offers an easy-to-understand syntax for directly parsing complex data structures.
- Portability:
read
is a Bash builtin, ensuring consistent behavior across different systems and environments.
Examples
Using awk
string="one two three"
first=$(echo $string | awk '{print $1}')
second=$(echo $string | awk '{print $2}')
third=$(echo $string | awk '{print $3}')
echo "First: $first, Second: $second, Third: $third"
Using read
string="one two three"
read -r first second third <<< "$string"
echo "First: $first, Second: $second, Third: $third"
Advantage: read
provides a simpler and more efficient way to parse space-separated strings directly into variables.
Using sed
input="username:password"
username=$(echo $input | sed 's/:.*//')
password=$(echo $input | sed 's/.*://')
echo "Username: $username, Password: $password"
Using read
input="username:password"
IFS=':' read -r username password <<< "$input"
echo "Username: $username, Password: $password"
Advantage: read
directly parses the input into variables, reducing complexity and improving readability.
Efficient Array Population
The mapfile
command, also known as readarray
, is a Bash built-in that provides an efficient way to populate an array with lines from a file or the output of a command. This command simplifies the process of reading and storing multiline data, making it particularly useful when working with large datasets or when you need to process input line by line.
Guidelines
- Use
mapfile
for Multiline Input: When you need to read and store multiline data into an array, prefer mapfile over looping constructs. - Specify a Delimiter with
-d
: Use the-d
option to specify a custom delimiter if the input data is not newline-separated. - Avoid Excessive Loops:
mapfile
can efficiently replace complex loops that read and store data line by line, improving script readability and performance.
Advantages of mapfile
- Efficiency:
mapfile
reads an entire stream into an array at once, making it faster and more efficient than reading lines individually in a loop. - Simplicity: The command reduces the amount of code required to populate arrays, leading to cleaner and more maintainable scripts.
- Flexibility: With options like
-t
(to remove trailing newlines) and-d
(to specify a delimiter), mapfile offers flexibility in handling various input formats.
Examples
Using a loop:
Using mapfile
:
Advantage: mapfile
simplifies the process of reading a file into an array, reducing the code and improving efficiency.
Using a loop:
Using mapfile
:
Advantage: mapfile
efficiently reads the output of a command into an array without the need for an explicit loop.