Skip to content

Style

This section outlines style guidelines that directly impact the functionality of Bash scripts. It includes best practices for quoting and variable declaration.

Using Quotes

In Bash, the type of quotes you use—single, double, or none—affects how strings and variables are interpreted. Understanding when to use each type is crucial to avoid common pitfalls, such as unintended word splitting, globbing, and parameter expansion.

Guidelines

  • First Choice: Use double quotes as the default method of quoting strings and variables.
    • Reason: Double quotes allow the shell to interpret expansions (such as variables, command substitutions, and escape sequences) while preventing word splitting and globbing.

Example

name="John Doe"
echo "Hello, $name"
  • String Literals: Use single quotes to define string literals.
    • Reason: Single quotes preserve the literal value of every character in a string, preventing the shell from performing expansions or substitutions. This behavior is essential for commands like find, grep, and awk, which have their own rules for interpreting special characters. Using single quotes ensures that these commands receive the input exactly as written, without modification by the shell.

Example

Literal string:

echo '${C_BLUE}This is a literal ${string} with no variable substitution${C_NC}'

Command argument:

find . -name '*.txt' -exec grep 'pattern.*here' {} \; -print
  • When to Omit Quotes: In certain cases, quotes can be safely omitted without risk. Common scenarios include arithmetic operations and double bracket tests.
    • Arithmetic Operations ($(( ... ))): Arithmetic expansion treats the content as a single unit, preventing word splitting and globbing.
    • Double Bracket Tests [[ ... ]]: The [[ ... ]] syntax is a Bash built-in that protects against word splitting and globbing, allowing comparisons and checks without the need for quotes.

Example

# Arithmetic operation.
sum=$((5 + 3))

## Double brackets to perform a comparison test.
if [[ $variable == "value" ]]; then
    echo "Matched"
fi
  • Syntax Highlighting: Enable syntax highlighting in your IDE or text editor.
    • Reason: Syntax highlighting visually distinguishes different code elements, such as keywords, strings, and variables, making it easier to identify potential issues, such as missing quotes, misplaced escape sequences, or incorrect special characters. This feature enhances debugging by making errors more visible and improving overall code readability.
  • When in Doubt, Quote: If unsure whether quotes are necessary, it's recommended to use them. Quoting variables and strings by default helps prevent common pitfalls.

Declaring and Naming Variables

Bash offers several methods for declaring variables, each with implications for scope, immutability, and readability. Following best practices for naming and declaring variables is essential to maintain consistency and avoid unintended side effects

Guidelines

  • Naming Convention: Use snake_case for variable names.
  • Scope Management: Use the local keyword to limit a variable's scope to within a function.

    • Reason: Declaring a variable as local prevents accidental overwrites of global variables with the same name. It also ensures that once the function completes, the variable is unset and released from memory. (1)

      1. Variable Scope: In Bash, variables declared inside a function without the local keyword are global by default, meaning they persist beyond the function's scope and can impact other parts of the script.
    • Example:

      my_var="global value"
      
      my_function() {
          # This local variable won't overwrite the global variable.
          local my_var="local value"
          echo "$my_var"
      }
      
      my_function
      echo "$my_var"
      
      Explanation: In this example, my_var is defined both globally and locally within my_function. The local keyword ensures that the my_var inside the function is distinct from the global my_var. When the function is called, it outputs the local value, whereas the final echo statement outputs the global value.

  • Naming Conventions: Use UPPER_SNAKE_CASE with a C_ prefix for constants (e.g., C_CONFIG_FILE_PATH).

    • Reason: This naming convention helps distinguish constants from other variables and aligns with common practices across various programming languages. (1)

      1. Constants and Environment Variables: Constants typically use UPPER_SNAKE_CASE, a standard followed in many languages, such as C, Python, and Java. Adopting a common convention ensures that other developers can easily recognize constants in your scripts. However, in Unix-like systems, environment variables are also written in UPPER_SNAKE_CASE. To prevent confusion, a prefix such as C_ is used to differentiate constants from environment variables.
    • Alternative Prefix: While C_ is the default prefix for constants, other prefixes like CONST_ or CONFIG_ may be used if they better suit your naming conventions or project requirements. The key is to maintain consistency within your script or project.

      • Example: The oh-my-zsh project uses the OMZ_ prefix to clearly indicate constants as part of the project while following the UPPER_SNAKE_CASE convention.
  • Selective Use of readonly: Use readonly to define constants that must remain unchanged throughout the script.

    • Reason: The readonly keyword enforces immutability, preventing accidental modification of critical values. However, overusing readonly can make scripts overly restrictive and complicate development and debugging. Therefore, reserve readonly for values that are truly immutable.
    • Declaration: Use readonly when initializing the variable or immediately afterward.
    • Example of When to Use:
      • Global Constants: Apply readonly to global constants where changes could significantly affect script behavior or cause unexpected results.
    • Example of When to Omit:
      • Function-Dependent Initialization: Avoid using readonly when a constant's value is determined by a function or a complex operation.
        • Reason: Applying readonly to a variable initialized by a function can cause errors if the function is called multiple times. To avoid reinitialization errors, omit readonly unless safeguards are in place to prevent reassignment.
Example
#!/bin/bash

## Constant variables using the naming convention with 'C_' prefix.
readonly C_CONFIG_FILE_PATH="/etc/myapp/config.conf"
readonly C_MAX_RETRIES=5
readonly C_API_ENDPOINT="https://api.example.com/data"

## Function that determines a value based on conditions.
initialize_dynamic_constant() {
    local environment="$1"

    if [[ "$environment" == "production" ]]; then
        C_DYNAMIC_URL="https://prod.example.com/api"
    else
        C_DYNAMIC_URL="https://dev.example.com/api"
    fi
}

# Call the function to initialize the dynamic constant.
initialize_dynamic_constant "production"

# Use the initialized values (note that C_DYNAMIC_URL is not readonly).
echo "API Endpoint: $C_API_ENDPOINT"
echo "Max Retries: $C_MAX_RETRIES"
echo "Configuration File Path: $C_CONFIG_FILE_PATH"
echo "Dynamic URL: $C_DYNAMIC_URL"

# Attempting to modify a readonly variable will result in an error.
# Uncommenting the line below will cause the script to fail.
#C_API_ENDPOINT="https://newapi.example.com"

## Function to perform an operation based on the constants.
perform_operation() {
    for ((i = 1; i <= C_MAX_RETRIES; i++)); do
        echo "Attempt $i: Fetching data from $C_API_ENDPOINT"
        sleep 1  # Simulate API call.
        break  # Break after first attempt for demo purposes.
    done
}

# Perform the operation using the constants.
perform_operation
  • Naming Conventions: Use UPPER_SNAKE_CASE with an E_ prefix for exported variables (e.g., E_PATH).

    • Reason: This naming convention differentiates exported variables from other variable types and aligns with the Unix naming convention for environment variables. (1)

      1. Exported and Environment Variables: Under Unix conventions, environment variables are written in UPPER_SNAKE_CASE. However, just like constants, using the same naming convention for exported variables can cause confusion and lead to accidental modifications of existing environment variables. A prefix such as E_ is used to differentiate exported variables from others while adhering to the UPPER_SNAKE_CASE convention.
  • Declaration: Use export when initializing an exported variable or immediately afterward.

Example
# Overwrites the existing $PATH environment variable.
export PATH="$HOME/.local/bin/"

## Does NOT overwrite the existing $PATH environment variable.
E_PATH="$HOME/.local/bin/"
export E_PATH
  • Selective Use: Use the declare command to manage advanced variable attributes, such as associative arrays. For simple variable declarations, or when keywords like local and readonly are sufficient, avoid using declare.
    • Reason: The declare command is useful for managing complex variable types, such as associative arrays, or when specific attributes are needed. However, using declare for basic variable assignments can reduce readability and is often unnecessary.
Example
# Associative array containing the configuration settings for sshd_config.
declare -A C_SSHD_CONFIG=(
    ["LogLevel"]="VERBOSE"
    ["LoginGraceTime"]="30"
    ["PermitRootLogin"]="no"
    ["MaxAuthTries"]="3"
    ["MaxSessions"]="2"
    ["PubkeyAuthentication"]="yes"
    ["PermitEmptyPasswords"]="no"
    ["ChallengeResponseAuthentication"]="no"
    ["KbdInteractiveAuthentication"]="no"
    ["UsePAM"]="yes"
    ["AllowAgentForwarding"]="no"
    ["AllowTcpForwarding"]="no"
    ["X11Forwarding"]="no"
    ["PrintMotd"]="no"
    ["TCPKeepAlive"]="no"
    ["Compression"]="no"
    ["ClientAliveInterval"]="300"
    ["ClientAliveCountMax"]="2"
)
readonly C_SSHD_CONFIG