Course Outline (Part 6)

In Part 6 of the Linux Bash Course, we shift our focus from typing ad-hoc commands on the terminal to writing automated scripts. You will learn the shebang convention, shell execution boundaries, exit codes, variable naming rules, local vs. global scopes, special parameters, and how to read user inputs securely.

For more technical background on Bash internals, refer to the GNU Bash Reference Manual.


Chapter 16: Writing Your First Script

16.1 The Shebang Line (#!/bin/bash)

The shebang (#!) is a special two-character sequence at the absolute beginning of a script file. It tells the Linux kernel program loader which interpreter binary to use to execute the script’s instructions.

  • Kernel Handling: When you run a script (e.g., ./myscript), the kernel reads the first line. If it finds #!, it runs the interpreter path specified after the shebang and passes the script file as an argument.

  • Alternative Paths: While #!/bin/bash is standard, #!/usr/bin/env bash is more portable because it searches the user’s system $PATH environment variable for the bash executable.

  • Syntax:

    #!/path/to/interpreter
  • Example Code:

    #!/bin/bash
    echo "Running in Bash"

16.2 Creating a Script File

A shell script is a plain text file containing a sequence of terminal commands. By convention, shell scripts end with the .sh file extension, although Linux determines executable status by file permissions, not by extensions. To create a script, we use file creation tools like touch, nano, or vim.

  • Syntax:
    touch script_name.sh
  • Example Command:
    touch my_backup.sh
    ls -l my_backup.sh
  • Expected Output:
    -rw-r--r-- 1 suresh suresh 0 Jun 12 13:35 my_backup.sh
  • Flag & Command Breakdown:
    • touch: Creates an empty file or updates access/modification times.
    • ls -l: Performs a long listing, displaying permissions, owner, size, and date.

16.3 Making a Script Executable: chmod +x

By default, new files created in Linux do not have execution permissions. Running ./script.sh on a non-executable file yields a Permission denied error. We modify permissions using the chmod command.

  • Syntax:
    chmod +x file_name.sh
  • Example Command:
    chmod +x my_backup.sh
    ls -l my_backup.sh
  • Expected Output:
    -rwxr-xr-x 1 suresh suresh 0 Jun 12 13:35 my_backup.sh
  • Flag & Command Breakdown:
    • chmod: Change mode (permissions).
    • +x: Add execute permission for owner, group, and others.

16.4 Running a Script (Three Methods)

There are three standard ways to invoke a Bash script, and each handles subprocesses differently:

  1. Relative/Absolute Path Execution (./script.sh or /path/to/script.sh): Runs the script in a new subshell (subprocess). Requires execute permissions.
  2. Explicit Interpreter Execution (bash script.sh): Runs the script in a new subshell using the specified bash binary. Does not require execute permissions.
  3. Sourcing (source script.sh or . script.sh): Runs the script directly in the current shell process. Variables defined inside the script persist in your terminal environment.
  • Syntax:
    # Method 1
    ./script.sh
    # Method 2
    bash script.sh
    # Method 3
    source script.sh
  • Example Command:
    echo 'TEST_VAR="Active"' > test_run.sh
    bash test_run.sh
    echo "Current Shell: $TEST_VAR"
    source test_run.sh
    echo "Current Shell: $TEST_VAR"
  • Expected Output:
    Current Shell: 
    Current Shell: Active
  • Flag & Command Breakdown:
    • bash test_run.sh: Launches a subshell. TEST_VAR is set and destroyed inside that subshell.
    • source test_run.sh: Runs commands inside the primary shell, modifying your active environment.

16.5 Script Exit Codes (0 = Success)

Every command and script in Unix returns an exit code (exit status) between 0 and 255.

  • 0 (Success): Indicates the script ran to completion without encountering errors.
  • 1 to 255 (Failure): Indicates various error conditions. For instance, 1 represents generic errors, 126 means file is not executable, and 127 represents command not found.

16.6 Checking Exit Codes: echo $?

To retrieve the exit code of the last executed command, we query the special shell parameter $?.

  • Syntax:
    echo $?
  • Example Command:
    ls /invalid_directory_path
    echo $?
  • Expected Output:
    ls: cannot access '/invalid_directory_path': No such file or directory
    2
  • Flag & Command Breakdown:
    • echo: Prints text or variables.
    • $?: The special shell variable representing the exit code of the immediately preceding command.

16.7 Using exit in Scripts

The exit command terminates the script execution immediately and returns a specific exit status to the calling parent shell. If no status is specified, the script returns the exit code of the last run command.

  • Syntax:
    exit [exit_code]
  • Example Command:
    # Create script
    cat << 'EOF' > exit_test.sh
    #!/bin/bash
    echo "Starting operations..."
    exit 42
    echo "This line will never print."
    EOF
    chmod +x exit_test.sh
    ./exit_test.sh
    echo "Script exited with: $?"
  • Expected Output:
    Starting operations...
    Script exited with: 42
  • Flag & Command Breakdown:
    • exit 42: Stops script execution and sends return code 42.

16.8 Commenting Your Code (#)

Comments are notes written inside the script to explain code logic. The shell interpreter ignores comments.

  • Single Line: Prefixed with the # symbol.
  • Block Comments Trick: Use a heredoc block with a colon (: is the null operator in Bash):
    : '
    This is a multi-line comment.
    The interpreter executes the null operator on this string.
    '

16.9 Hello World Script

Let us construct a clean, standard Hello World script applying comments, the shebang line, and proper exits.

  • Example Code (hello_world.sh):
    #!/bin/bash
    # Author: Suresh
    # Purpose: Basic system hello response script
    
    echo "Hello, World!"
    exit 0

16.10 Debugging with set -x

Bash offers trace debugging capabilities that print each command along with its expanded arguments before executing them.

  • set -x (xtrace): Turns on debugging.

  • set +x: Turns off debugging.

  • Syntax:

    set -x
    # code to debug
    set +x
  • Example Command:

    cat << 'EOF' > debug_demo.sh
    #!/bin/bash
    set -x
    VAR_NAME="TechBlog"
    echo "Hello $VAR_NAME"
    set +x
    echo "Debugging off."
    EOF
    chmod +x debug_demo.sh
    ./debug_demo.sh
  • Expected Output:

    + VAR_NAME=TechBlog
    + echo 'Hello TechBlog'
    Hello TechBlog
    + set +x
    Debugging off.
  • Flag & Command Breakdown:

    • set -x: Instructs Bash to print input lines prefixed by a + symbol as they are evaluated.

Chapter 17: Variables in Bash

17.1 Defining User Variables

User variables store temporary data.

  • Rules: Variable names are case-sensitive (usually capitalized by convention) and must begin with a letter or underscore.

  • Important: You must not put spaces around the assignment operator (=). NAME = Value is parsed as running a command named NAME with arguments = and Value.

  • Syntax:

    VARIABLE_NAME=value
  • Example Command:

    WEBSITE="freetechlearner.com"
    echo $WEBSITE
  • Expected Output:

    freetechlearner.com

17.2 Reading Variables: $VAR vs ${VAR}

To retrieve the value of a variable, prepend the variable name with a dollar sign ($).

  • $VAR: Basic variable extraction.

  • ${VAR} (Parameter Expansion): Encloses the variable name in curly braces. This is required when the variable name merges with adjacent characters to prevent naming ambiguity (e.g. ${VAR}_log).

  • Syntax:

    $VAR_NAME
    ${VAR_NAME}
  • Example Command:

    COURSE="linux-bash"
    echo "Running course-$COURSE-docs"
    echo "Running course-${COURSE}-docs"
  • Expected Output:

    Running course-
    Running course-linux-bash-docs
  • Flag & Command Breakdown:

    • $COURSE-docs failed because Bash searched for a variable named COURSE-docs which did not exist.
    • ${COURSE}-docs separated the parameter token from the surrounding text.

17.3 Read-Only Variables: readonly

Read-only variables are constants. Once declared, their values cannot be reassigned, and they cannot be unset.

  • Syntax:
    readonly VARIABLE_NAME=value
  • Example Command:
    readonly PORT=8080
    PORT=9090
  • Expected Output:
    bash: PORT: readonly variable

17.4 Unsetting Variables: unset

The unset command deletes a variable from the shell memory, freeing up system environment resources.

  • Syntax:
    unset VARIABLE_NAME
  • Example Command:
    TEMP_KEY="xyz123"
    echo "Before: $TEMP_KEY"
    unset TEMP_KEY
    echo "After: $TEMP_KEY"
  • Expected Output:
    Before: xyz123
    After: 

17.5 Local vs Global Variables in Functions

Variables declared in Bash scripts are global by default, meaning they can be modified from inside functions. To confine a variable to a function’s scope, declare it with the local keyword.

  • Syntax:
    local variable_name=value
  • Example Command:
    # Create test script
    cat << 'EOF' > scope_test.sh
    #!/bin/bash
    GLOBAL_VAR="Global Value"
    
    test_scope() {
        local LOCAL_VAR="Local Value"
        GLOBAL_VAR="Modified Globally"
        echo "Inside function: LOCAL=$LOCAL_VAR, GLOBAL=$GLOBAL_VAR"
    }
    
    test_scope
    echo "Outside function: LOCAL=$LOCAL_VAR, GLOBAL=$GLOBAL_VAR"
    EOF
    bash scope_test.sh
  • Expected Output:
    Inside function: LOCAL=Local Value, GLOBAL=Modified Globally
    Outside function: LOCAL=, GLOBAL=Modified Globally

17.6 Special Variables: $0, $1, $#

Bash reserves special variables to capture execution metadata:

  • $0: Holds the name of the script command as invoked.

  • $1 to $9: Positional arguments passed to the script. For arguments above 9, use braces (e.g. ${10}).

  • $#: The total number of command-line arguments passed to the script.

  • Syntax:

    # Inside a script
    echo $0
    echo $1
    echo $#
  • Example Command:

    cat << 'EOF' > arg_test.sh
    #!/bin/bash
    echo "Script Name: $0"
    echo "First Arg: $1"
    echo "Args Count: $#"
    EOF
    bash arg_test.sh "Ubuntu-Install" "Azure"
  • Expected Output:

    Script Name: arg_test.sh
    First Arg: Ubuntu-Install
    Args Count: 2

17.7 All Arguments: $@ vs $*

Both variables capture all positional arguments passed to the script. However, their behaviors differ when enclosed in double quotes:

  • "$*": Concatenates all parameters into a single string, separated by the first character of the IFS variable (usually space).

  • "$@": Preserves each parameter as a distinct, independent word. Always use "$@" when passing arguments to other commands to prevent breaking arguments containing spaces.

  • Syntax:

    "$*"
    "$@"
  • Example Command:

    cat << 'EOF' > print_args.sh
    #!/bin/bash
    echo "Using \$* :"
    for arg in "$*"; do
        echo "Arg: $arg"
    done
    echo "Using \$@ :"
    for arg in "$@"; do
        echo "Arg: $arg"
    done
    EOF
    bash print_args.sh "first param" "second param"
  • Expected Output:

    Using $* :
    Arg: first param second param
    Using $@ :
    Arg: first param
    Arg: second param

17.8 The Process ID: $$

The $$ special variable returns the Process ID (PID) of the current shell shell process. This is useful for generating unique filenames for temp files.

  • Syntax:
    $$
  • Example Command:
    echo "Current Process PID: $$"
  • Expected Output:
    Current Process PID: 28492

17.9 Last Background PID: $!

The $! variable yields the Process ID of the most recently executed background job or process.

  • Syntax:
    command &
    echo $!
  • Example Command:
    sleep 30 &
    echo "Sleep Background PID: $!"
    kill $!
  • Expected Output:
    Sleep Background PID: 28501
  • Flag & Command Breakdown:
    • &: Appended to a command to run it asynchronously in the background.
    • kill: Terminates a process using its PID.

17.10 Last Argument: $_

The $_ variable stores the last argument of the previously executed command.

  • Syntax:
    command argument
    echo $_
  • Example Command:
    touch config.txt
    mv config.txt $_.bak
    ls -l config.txt.bak
  • Expected Output:
    -rw-r--r-- 1 suresh suresh 0 Jun 12 13:35 config.txt.bak

Chapter 18: User Input & Read

18.1 Basic read Command

The read command is a shell built-in that reads a single line of input from standard input (stdin) and assigns it to a variable. If no variable is specified, the input is stored in the default variable $REPLY.

  • Syntax:
    read variable_name
  • Example Command:
    echo "Type your name:"
    # In interactive test we simulate input using standard input streams
    echo "Suresh" | (read USERNAME; echo "Variable contains: $USERNAME")
  • Expected Output:
    Variable contains: Suresh

18.2 Reading Multiple Variables

If you provide multiple variable names to read, it splits the input line using the characters in the Internal Field Separator (IFS) variable. The first word is assigned to the first variable, the second to the second, and any remaining words are assigned to the last variable.

  • Syntax:
    read var1 var2 var3
  • Example Command:
    echo "A B C D" | (read x y z; echo "x=$x; y=$y; z=$z")
  • Expected Output:
    x=A; y=B; z=C D

18.3 Prompting with -p

The -p flag prints a prompt string directly on standard error without a trailing newline, allowing you to prompt for input on the same line.

  • Syntax:
    read -p "Prompt message: " variable_name
  • Example Command:
    echo "test" | (read -p "Enter code: " INPUT_CODE; echo "Code: $INPUT_CODE")
  • Expected Output:
    Code: test
  • Flag & Command Breakdown:
    • -p: Specifies the prompt string that is displayed before reading the input.

18.4 Silent Input (Passwords): read -s

The -s flag turns off terminal echoing, hiding input characters as they are typed. This is essential for secure credential inputs.

  • Syntax:
    read -s -p "Enter Password: " PASSWORD_VAR
  • Example Command:
    echo "Secret123" | (read -s -p "Enter key: " SEC_KEY; echo ""; echo "Key was: $SEC_KEY")
  • Expected Output:
    
    Key was: Secret123
  • Flag & Command Breakdown:
    • -s: Disables echo mode on the terminal.

18.5 Timeout Input: read -t

The -t flag sets a timeout limit in seconds. If the user does not press enter before the timeout, read exits with a non-zero status code and terminates the input process.

  • Syntax:
    read -t [seconds] variable_name
  • Example Command:
    # Test read timeout (we use 1 second timeout)
    if read -t 1 -p "Waiting for quick input: " QUICK_IN; then
        echo "Read input: $QUICK_IN"
    else
        echo "Timeout reached."
    fi
  • Expected Output:
    Timeout reached.
  • Flag & Command Breakdown:
    • -t 1: Exits read command if no line of input is completed within 1 second.

18.6 Reading N Characters: read -n

The -n flag tells read to return immediately after reading a specified number of characters, rather than waiting for a full line terminated by a newline.

  • Syntax:
    read -n [char_count] variable_name
  • Example Command:
    echo "Yes" | (read -n 1 RESPONSE; echo ""; echo "Captured char: $RESPONSE")
  • Expected Output:
    
    Captured char: Y
  • Flag & Command Breakdown:
    • -n 1: Triggers termination of the read input loop after exactly 1 character is entered.

18.7 Reading from Files with read

The read command can process files line-by-line by redirecting standard input from a file. This is typically implemented inside a while loop.

  • Syntax:
    while read -r line; do
        # process line
    done < filename.txt
  • Example Command:
    # Create test text file
    printf "Line A\nLine B\n" > lines.txt
    while read -r line; do
        echo "Processing: $line"
    done < lines.txt
  • Expected Output:
    Processing: Line A
    Processing: Line B
  • Flag & Command Breakdown:
    • -r: Disables backslash escapes, preserving all literal backslash characters.
    • < lines.txt: Redirects standard input of the loop from the file.

18.8 Using IFS to Split Input

The IFS (Internal Field Separator) environment variable defines the characters used as field delimiters. By altering IFS temporarily before running read, you can parse CSV or customized delimited inputs directly.

  • Syntax:
    IFS="delimiter" read -r var1 var2 ...
  • Example Command:
    CSV_LINE="suresh,admin,/bin/bash"
    IFS="," read -r USER ROLE SHELL <<< "$CSV_LINE"
    echo "User $USER has role $ROLE"
  • Expected Output:
    User suresh has role admin
  • Flag & Command Breakdown:
    • IFS=",": Temporarily overrides space and tab separators with a comma.
    • <<<: Enters a “here-string”, redirecting the variable string value directly into standard input.

18.9 Validating User Input

Input validation protects scripts from running with invalid or malicious parameters. We validate variables using conditional brackets.

  • Example Script:
    #!/bin/bash
    read -p "Enter age: " USER_AGE
    # Validate that input is digits only
    if [[ "$USER_AGE" =~ ^[0-9]+$ ]]; then
        echo "Valid age: $USER_AGE"
    else
        echo "Error: Invalid numeric input."
        exit 1
    fi

18.10 Reading Command-Line Arguments

While read collects input dynamically during script runtime, positional parameters (such as $1, $2) capture arguments provided at execution time. We parse structured command arguments using loops and the shift command.

  • Syntax:
    # Loop over arguments
    for arg in "$@"; do
        echo "Arg: $arg"
    done
  • Example Script:
    #!/bin/bash
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --env)
                ENV="$2"
                shift 2
                ;;
            *)
                echo "Unknown argument: $1"
                exit 1
                ;;
        esac
    done
    echo "Configured environment: $ENV"