Basics of Bash Scripting

Bash scripting allows you to automate tasks by writing sequences of commands in a file, known as a script.

  • Execution of Commands: Scripts can run any command you can type in the terminal.
  • Control Flow Expressions: Utilize if, for, while, and other control structures to make decisions.
  • Tailored for Shell Tasks: Ideal for automating command-line tasks, file manipulation, and system administration.


Variables and Strings in Bash

In Bash scripting, you can assign values to variables and use them throughout your script.

  • Assigning Variables: Use the syntax variable_name=value without spaces around the =.

    foo=bar
  • Accessing Variables: Prefix the variable name with $ to access its value.

    echo "$foo"  # Outputs: bar
  • Quoting Strings:

    • Double Quotes ("): Variables inside double quotes are expanded (their values are used).
    • Single Quotes (')}: Variables inside single quotes are treated as literal strings (no expansion).

    Example:

    echo "$foo"  # Prints the value of foo, which is 'bar'
    echo '$foo'  # Prints the string '$foo' literally

Special Variables in Bash

Bash provides several special variables that give you information about your script and the environment:

  • $0: The name of the script itself.

  • $1 to $9: The first nine arguments passed to the script. $1 is the first argument, $2 is the second, and so on.

  • $@: All the arguments passed to the script as an array.

  • $#: The number of arguments passed to the script.

  • $?: The return code (exit status) of the last executed command. A value of 0 typically means success.

  • $$: The process ID (PID) of the current script.

  • !!: The entire last command executed in the shell, including arguments. Useful for repeating commands, especially with sudo:

    sudo !!
  • $_: The last argument of the previous command. In interactive shells, you can also retrieve this by pressing Esc followed by . or Alt + ..

TIP

As noted, $9 is the highest numbered positional parameter. To access more than nine arguments, use ${10}, ${11}, and so on until ${ARG_MAX}

For a comprehensive list of special characters and variables in Bash, refer to Special Characters in Bash.


Command Execution and Return Codes

Understanding how commands execute and return codes is essential in Bash scripting.

  • Conditional Execution:
    • &&: Executes the next command only if the previous command succeeded (returned exit status 0).
    • ||: Executes the next command only if the previous command failed (non-zero exit status).
    • ;: Executes commands sequentially, regardless of the exit status of previous commands.
  • true and false Commands:
    • true: A command that does nothing and returns a success exit status (0).
    • false: A command that does nothing and returns a failure exit status (1).

Example:

false || echo "Oops, fail"
# Output: Oops, fail
 
true || echo "Will not be printed"
# No output, since 'true' succeeded
 
true && echo "Things went well"
# Output: Things went well
 
false && echo "Will not be printed"
# No output, since 'false' failed
 
true ; echo "This will always run"
# Output:
# This will always run
 
false ; echo "This will always run"
# Output:
# This will always run

Permissions – Brief Recap

Unix-like operating systems use a permission system to control access to files and directories.

Permission Types:

  1. Read (r):

    • Files: Allows viewing the contents of a file.
    • Directories: Allows listing the contents within the directory.
  2. Write (w):

    • Files: Allows modifying or deleting the file’s contents.
    • Directories: Allows adding, removing, or renaming files within the directory.
  3. Execute (x):

    • Files: Allows executing the file as a program or script.
    • Directories: Allows accessing or traversing into the directory.

User Categories:

  • User (u): The owner of the file or directory.
  • Group (g): Users who are part of the file’s group.
  • Other (o): All other users not covered by user or group.

Permissions are often represented in the format rwxrwxrwx, where each set of rwx corresponds to user, group, and other.

TIP

r = 4, w = 2, x = 1 in case you need to set permissions numerically.


Default permissions

When a new file is created in Unix, it typically has the following default permissions:

  • User (Owner): Read and Write (rw-)
  • Group: Read-only (r--)
  • Other: Read-only (r--)

This is depicted as:

rw-r--r--

These defaults can be modified using the umask command, which sets default permission masks.


Script Example

Below is an example of a Bash script that demonstrates variable usage, command substitution, and control flow.

NOTE

It’s recommended to use [[ ]] for conditional expressions as they are more robust than [ ].

#!/bin/bash
 
echo "Starting program at $(date)" # Outputs the current date and time
 
echo "Running program $0 with $# arguments with pid $$"
 
for file in "$@"; do
    grep foobar "$file" > /dev/null 2> /dev/null
    # The 'grep' command searches for 'foobar' in the file.
    # Output is redirected to '/dev/null' to suppress it.
    # STDOUT and STDERR are redirected since we don't need them.
 
    if [[ $? -ne 0 ]]; then
        # If 'grep' did not find 'foobar' (exit status not equal to 0)
        echo "File $file does not have any 'foobar', adding one"
        echo "# foobar" >> "$file"
        # Appends '# foobar' to the file
    fi
done

Command Substitution Using $( CMD )

Command substitution allows you to capture the output of a command and use it within another command.

Example in a Dockerfile:

GNUPGHOME="$(mktemp -d)" && \
export GNUPGHOME && \
for key in \
    "B46DC71E03FEEB7F89D1F2491F7A8F87B9D8F501" \
  ; do \
    gpg --batch --keyserver "keyserver.ubuntu.com" --recv-keys "$key" ; \
done && \
gpg --verify "/tmp/$QD_NAME.tar.gz.sha256.asc" "/tmp/$QD_NAME.tar.gz.sha256" && \
(cd /tmp && sha256sum --check --status "$QD_NAME.tar.gz.sha256") && \
  • GNUPGHOME="$(mktemp -d)": Creates a temporary directory and assigns it to GNUPGHOME.
  • gpg --recv-keys "$key": Imports a GPG key.
  • Command substitution is used to set environment variables or capture command outputs within scripts.

Control Flow and Functions

Bash supports typical control flow structures:

  • Conditional Statements: if, else, elif
  • Case Statements: case for matching patterns
  • Loops:
    • while: Repeats as long as a condition is true.
    • for: Iterates over a list of items.

Function Example: Creating and Entering a Directory

You can define functions to encapsulate reusable code.

mcd () {           # Define a function named 'mcd'
    mkdir -p "$1"  # Create a directory and parent directories if they don't exist
    cd "$1"        # Change to the created directory
    # $1 is the first argument passed to the function
}

Usage:

mcd my_new_directory

This command will create my_new_directory and then change into it.


Shell Functions vs. Scripts

Understanding the difference between functions and scripts is important:

  • Functions:
    • Written in the shell’s language.
    • Loaded into memory once when the shell starts or when the function is defined.
    • Can modify the shell’s environment (e.g., change directories, set variables).
    • Promote code modularity, reuse, and clarity.
    • Typically included in your shell configuration files like .bashrc.
  • Scripts:
    • It can be written in any scripting language (Bash, Python, Perl, etc.).
    • Executed as separate processes each time they are run.
    • Cannot modify the parent shell’s environment directly (changes are limited to the script’s process).
    • Useful for automating tasks and can be invoked from any shell.

In summary,

  • use functions for quick, shell-specific tasks that may need to interact with the shell environment.
  • use scripts for more complex tasks or when using different languages.

Bash Comparisons and Globbing

Comparisons:

  • Use [[ ]] for conditional expressions in Bash. It’s more powerful and safer than [ ]. Examples:

    if [[ "$variable" -eq 10 ]]; then
        echo "Variable is equal to 10"
    fi

Globbing:

  • Wildcards are used to match file names and patterns.

    • *: Matches any number of any characters (including none).
    • ?: Matches exactly one character.
    • [ ]: Matches any one character inside the brackets.
    • {}: Brace expansion for generating arbitrary strings.

    Examples:

    ls *.txt           # Lists all files ending with '.txt'
    ls file?.txt       # Matches 'file1.txt', 'fileA.txt', etc.
    mv file{1,2}.txt   # Expands to 'mv file1.txt file2.txt'

Globbing is a powerful feature for working with multiple files and automating tasks.


More Shell tools

Discovering Command Usage

When working with the shell, you might encounter unfamiliar commands or need to recall how to use a command.

Resources:

  • --help: Most commands provide a quick help message.

    command --help
  • man Command: Access the manual pages for detailed information.

    man rm
  • tldr Pages: Simplified and community-driven man pages.

    tldr tar

    Install tldr with:

    sudo apt install tldr    # On Debian/Ubuntu
    brew install tldr        # On macOS with Homebrew

These tools help you quickly understand command usage and options.


Finding Files

Searching for files in the file system is a common task.

Tools:

  • find:

    • Searches for files in a directory hierarchy.

    • Syntax:

      find /path -name "filename"
    • Example:

      find /home/user -name "*.txt"
  • fd:

    • A simpler and faster alternative to find.

    • Syntax:

      fd pattern [path]
    • Example:

      fd ".txt$" /home/user
  • locate:

    • Searches a database of files for quick results.

    • Requires updating the database periodically with updatedb.

    • Example:

      locate myfile.txt

Usage Scenarios:

  • Use find when you need complex search criteria or actions.
  • Use fd for simpler and faster searches.
  • Use locate when you need quick results and the database is up-to-date.

Finding Code

Searching within files for specific patterns is essential for code and text analysis.

Tools:

  • grep:
    • Searches for patterns in files.

    • Syntax:

      grep "pattern" file.txt
  • Alternatives:
    • ack: Designed for programmers, faster than grep.
    • ag (The Silver Searcher): Very fast code searching tool.
    • rg (Ripgrep): Extremely fast search tool, respects .gitignore by default.

Pattern Searching Examples:

  • Search recursively for a pattern in all .py files:

    grep -r --include="*.py" "def my_function" .
  • Using rg for the same task:

    rg "def my_function" -t py

These tools help you find code snippets, functions, or any text within files quickly.


Finding Shell Commands

Recall previous commands or search through your command history.

Tools and Techniques:

  • history Command:

    history
  • Reverse Search with Ctrl + R:

    • Press Ctrl + R and start typing to search backward through your history.
  • fzf (Fuzzy Finder):

    • An interactive command-line fuzzy finder.

    • Can be integrated with shell history for faster searching.

    • Installation:

      sudo apt install fzf    # On Debian/Ubuntu
      brew install fzf        # On macOS with Homebrew
    • Usage:

      history | fzf

Autosuggestions:

  • Fish Shell or Zsh with plugins can provide command autosuggestions based on history.

Protecting Sensitive Entries:

  • Be cautious with sensitive data in commands (like passwords).
  • Avoid typing sensitive information directly; use input prompts or configuration files.

Directory Navigation

Advanced tools can make navigating directories more efficient.

Tools:

  • tree:

    • Displays directories and files in a tree-like format.

    • Usage:

      tree
  • broot:

    • A modern, interactive tree command with fuzzy search.

    • Installation:

      brew install broot     # On macOS
  • nnn:

    • A terminal-based file browser with minimalistic design.
    • Supports plugins for extended functionality.
  • ranger:

    • A console file manager with VI key bindings.
    • Provides a preview of the selected file.

These tools enhance your ability to navigate and manage the file system from the command line.


NOTE

We will not cover regular expressions , but they are a powerful tool for pattern matching and text processing in the shell. For more practice with regular expressions, visit RegexOne.