[RHCSA] 02 – Create simple shell scripts

    02.1 – Conditionally execute code (use of: if, test, [], etc.)

    Conditionally executing code is a fundamental skill for shell scripting in Linux. It allows scripts to perform specific actions based on certain conditions. Below are detailed notes on using conditional statements in , with examples.


    1. Conditional Execution with if Statement

    Syntax:

    if [ condition ]; then
        # Commands to execute if condition is true
    fi
    • The condition is evaluated inside square brackets ([]).
    • If the condition is true (exit status 0), the commands within the if block are executed.

    Example:

    #!/bin/bash
    
    if [ -e /etc/passwd ]; then
        echo "/etc/passwd exists."
    fi

    Explanation:

    • -e checks if the file exists.
    • If /etc/passwd exists, the message is printed.

    2. Adding an else Block

    Syntax:

    if [ condition ]; then
        # Commands if condition is true
    else
        # Commands if condition is false
    fi
    

    Example:

    #!/bin/bash
    
    if [ -d /tmp ]; then
        echo "/tmp is a directory."
    else
        echo "/tmp is not a directory."
    fi

    Explanation:

    • -d checks if the path is a directory.
    • If /tmp is a directory, the first message is printed; otherwise, the second message is printed.

    3. Using elif for Multiple Conditions

    Syntax:

    if [ condition1 ]; then
        # Commands if condition1 is true
    elif [ condition2 ]; then
        # Commands if condition2 is true
    else
        # Commands if none of the conditions are true
    fi

    Example:

    #!/bin/bash
    
    num=10
    
    if [ $num -gt 20 ]; then
        echo "Number is greater than 20."
    elif [ $num -eq 10 ]; then
        echo "Number is equal to 10."
    else
        echo "Number is less than 20 but not 10."
    fi

    Explanation:

    • -gt: Greater than.
    • -eq: Equal to.
    • Conditions are checked sequentially until one evaluates to true.

    4. The test Command

    The test command evaluates conditions without using [].

    Syntax:

    test condition

    Example:

    #!/bin/bash
    
    if test -f /etc/hosts; then
        echo "/etc/hosts is a regular file."
    fi

    Explanation:

    • -f checks if the file is a regular file.

    5. Combining Conditions with Logical Operators

    Logical AND (-a) and OR (-o)

    if [ condition1 -a condition2 ]; then
        # Commands if both conditions are true
    fi

    Example:

    #!/bin/bash
    
    if [ -f /etc/hosts -a -r /etc/hosts ]; then
        echo "/etc/hosts exists and is readable."
    fi

    Explanation:

    • -f: Checks if it is a regular file.
    • -r: Checks if it is readable.

    Using && and || for Logical Operations

    if [ condition1 ] && [ condition2 ]; then
        # Commands if both conditions are true
    fi

    Example:

    #!/bin/bash
    
    if [ -w /etc/hosts ] || [ -w /tmp ]; then
        echo "Either /etc/hosts or /tmp is writable."
    fi

    Explanation:

    • -w: Checks if a file is writable.
    • ||: Logical OR.

    6. Negating Conditions

    Using ! to Negate

    if [ ! condition ]; then
        # Commands if condition is false
    fi

    Example:

    #!/bin/bash
    
    if [ ! -x /bin/ls ]; then
        echo "/bin/ls is not executable."
    fi

    Explanation:

    • -x: Checks if the file is executable.
    • !: Negates the condition.

    7. Case Study: File Management Script

    Script:

    #!/bin/bash
    
    read -p "Enter a filename: " filename
    
    if [ -e $filename ]; then
        if [ -d $filename ]; then
            echo "$filename is a directory."
        elif [ -f $filename ]; then
            echo "$filename is a regular file."
        else
            echo "$filename exists but is neither a file nor a directory."
        fi
    else
        echo "$filename does not exist."
    fi
    

    Explanation:

    • Prompts the user for a filename and checks its type using nested if statements.

    8. Common Condition Options

    OptionMeaning
    -eFile exists
    -fFile exists and is a regular file
    -dDirectory exists
    -rFile is readable
    -wFile is writable
    -xFile is executable
    -zString is empty
    -nString is not empty
    ==String comparison (equal)
    !=String comparison (not equal)
    -ltNumeric comparison (less than)
    -gtNumeric comparison (greater than)
    -leNumeric comparison (less than or equal)
    -geNumeric comparison (greater than or equal)

    This guide provides foundational knowledge for conditional execution in  scripts, which is a key topic in RHCSA certification.

    02.2 – Use Looping constructs (for, etc.) to process file, command line input

    Introduction

    Looping constructs in Linux scripting (e.g., for, while, until) are essential for automating tasks such as processing files, handling user inputs, and iterating over a list of arguments. These loops can handle repetitive tasks efficiently, making them crucial for the RHCSA exam.


    1. Using for Loop

    The for loop iterates over a list of items. It is particularly useful for processing files or command-line arguments.

    Example 1: Processing Multiple Files

    #!/bin/bash
    
    # Iterate over a list of files and display their contents
    for file in file1.txt file2.txt file3.txt; do
      if [ -f "$file" ]; then
        echo "Contents of $file:"
        cat "$file"
      else
        echo "File $file not found!"
      fi
    done

    Explanation:

    • The for loop iterates through file1.txt, file2.txt, and file3.txt.
    • The if condition checks if the file exists (-f flag).
    • If the file exists, cat is used to display its contents.

    Example 2: Processing Command-Line Input

    #!/bin/bash
    
    # Process command-line arguments
    for arg in "$@"; do
      echo "Processing argument: $arg"
    done

    Explanation:

    • $@ represents all the command-line arguments passed to the script.
    • The loop processes each argument individually.

    2. Using while Loop

    The while loop executes as long as a condition remains true. It is useful for reading files line by line or waiting for a condition.

    Example 1: Reading a File Line by Line

    #!/bin/bash
    
    # Read a file line by line
    while IFS= read -r line; do
      echo "Line: $line"
    done < input.txt

    Explanation:

    • IFS= prevents trimming of leading/trailing whitespace.
    • read -r prevents backslash escape interpretation.
    • < input.txt redirects the input file to the while loop.

    Example 2: Waiting for a Condition

    #!/bin/bash
    
    # Wait until a file exists
    while [ ! -f /tmp/myfile ]; do
      echo "Waiting for /tmp/myfile..."
      sleep 2
    done
    echo "/tmp/myfile is now available!"

    Explanation:

    • The loop checks if /tmp/myfile exists.
    • It waits (using sleep 2) and rechecks until the file is found.

    3. Using until Loop

    The until loop is the inverse of the while loop, running until a condition becomes true.

    Example 1: Create a File Until Successful

    #!/bin/bash
    
    # Try creating a file until successful
    until touch /tmp/testfile 2>/dev/null; do
      echo "Retrying to create /tmp/testfile..."
      sleep 1
    done
    echo "/tmp/testfile created successfully!"

    Explanation:

    • The loop continues if touch fails (e.g., due to permissions).
    • The 2>/dev/null suppresses error messages.

    4. Combining Loops with Command Substitution

    Command substitution allows looping constructs to iterate over command output.

    Example 1: Iterating Over a List of Users

    #!/bin/bash
    
    # List all users from /etc/passwd
    for user in $(cut -d: -f1 /etc/passwd); do
      echo "User: $user"
    done

    Explanation:

    • cut -d: -f1 /etc/passwd extracts the first field (username) from /etc/passwd.
    • The for loop processes each username.

    Example 2: Checking Disk Usage

    #!/bin/bash
    
    # Check disk usage of home directories
    for dir in /home/*; do
      du -sh "$dir"
    done

    Explanation:

    • /home/* expands to all directories in /home.
    • du -sh shows the size of each directory in human-readable format.

    5. Nested Loops

    Nested loops handle more complex scenarios.

    Example: Processing Multiple Files in Directories

    #!/bin/bash
    
    # Check disk usage of home directories
    for dir in /home/*; do
      du -sh "$dir"
    done

    Explanation:

    • The outer loop iterates over directories.
    • The inner loop processes files within each directory.

    6. Error Handling in Loops

    Always handle errors for robust scripts.

    Example: Skip Non-Readable Files

    #!/bin/bash
    
    # Read files if readable
    for file in /home/*; do
      if [ -r "$file" ]; then
        echo "Reading: $file"
        cat "$file"
      else
        echo "Skipping unreadable file: $file"
      fi
    done

    Explanation:

    • -r checks if the file is readable.
    • Unreadable files are skipped.

    Practice Exercises

    1. Write a script to list all .log files in /var/log and display their sizes.
    2. Create a script to read numbers from a file and calculate their sum using a while loop.
    3. Write a nested loop to find all empty directories in /home.

    These examples cover essential looping constructs and demonstrate their practical applications, meeting RHCSA objectives.

    02.3 – Process script inputs ($1, $2, etc.)

    In Linux shell scripting, arguments passed to a script from the command line can be accessed using positional parameters like $1, $2, and so on. This topic is important for creating dynamic scripts that behave based on user input.


    Understanding Positional Parameters

    • $0: The name of the script.
    • $1, $2, …: Positional parameters representing the arguments passed to the script.
    • $#: The total number of arguments passed to the script.
    • $@: All the arguments passed to the script as a list.
    • $*: Similar to $@, but behaves differently when quoted.
    • “$@”: Each argument as a separate quoted string.
    • “$*”: All arguments combined into a single quoted string.
    • $?: The exit status of the last executed command.
    • $$: The process ID (PID) of the current shell.
    • $!: The PID of the last background process.

    Examples with Explanations

    1. Basic Usage Create a script example.sh:

    #!/bin/bash
    
    echo "Script Name: $0"
    
    echo "First Argument: $1"
    
    echo "Second Argument: $2"
    
    echo "Total Arguments: $#"
    
    echo "All Arguments (\$*): $*"
    
    echo "All Arguments (\$@): $@"

    Run the script with arguments:

     bash example.sh arg1 arg2 arg3

    Output:

    Script Name: example.sh
    
    First Argument: arg1
    
    Second Argument: arg2
    
    Total Arguments: 3
    
    All Arguments ($*): arg1 arg2 arg3
    
    All Arguments ($@): arg1 arg2 arg3

    2. Loop Through All Arguments Use $@ in a loop to iterate through all arguments:

    #!/bin/bash
    echo "Iterating over arguments:"
    for arg in "$@"; do
      echo "$arg"
    done

    Run the script:

    bash example.sh one two three

    Output:

    Iterating over arguments:
    one
    two
    three

    3. Difference Between $* and $@

    • $*: Treats all arguments as a single string.
    • $@: Treats each argument as a separate entity.

    Example script:

    #!/bin/bash
    echo "Using \$*:"
    for arg in "$*"; do
      echo "$arg"
    done
    
    echo "Using \$@:"
    for arg in "$@"; do
      echo "$arg"
    done

    Run:

    bash example.sh "arg1 arg2" arg3

    Output:

    Using $*:
    arg1 arg2 arg3
    Using $@:
    arg1 arg2
    arg3

    4. Handling a Specific Number of Arguments Ensure a script behaves correctly when a required number of arguments is passed:

    #!/bin/bash
    if [ "$#" -ne 2 ]; then
      echo "Usage: $0 <arg1> <arg2>"
      exit 1
    fi
    
    echo "Argument 1: $1"
    echo "Argument 2: $2"

    Run:

    bash example.sh arg1

    Output:

    Usage: example.sh <arg1> <arg2>

    Run:

    bash example.sh arg1 arg2

    Output:

    Argument 1: arg1
    
    Argument 2: arg2

    5. Pass Inputs to Perform Arithmetic Pass numerical arguments and use them for calculations:

    #!/bin/bash
    if [ "$#" -lt 2 ]; then
      echo "Usage: $0 <num1> <num2>"
      exit 1
    fi
    
    num1=$1
    num2=$2
    
    sum=$((num1 + num2))
    echo "Sum of $num1 and $num2 is: $sum"

    Run:

    bash example.sh 10 20

    Output:

    Sum of 10 and 20 is: 30

    6. Debugging with $? Check the success or failure of a command:

    #!/bin/bash
    cp $1 $2
    if [ "$?" -eq 0 ]; then
      echo "File copied successfully."
    else
      echo "Failed to copy file."
    fi

    Run:

    bash example.sh source.txt destination.txt

    Output (if source.txt exists):

    File copied successfully.

    Key Points for RHCSA Exam

    1. Understand how to access and use positional parameters.
    2. Use $# to validate the number of arguments passed.
    3. Loop through $@ for processing multiple inputs.
    4. Handle error conditions using $?.
    5. Write scripts that adapt dynamically based on inputs.

    This knowledge is foundational for automating tasks in Linux, a critical skill for RHCSA certification.

    02.4 – Processing output of shell commands within a script

    Shell scripting involves using the output of commands as inputs to other commands or processes. This is a critical skill for automating tasks and writing efficient scripts. Here are detailed notes and examples for processing the output of shell commands.


    1. Using Command Substitution

    Command substitution allows you to capture the output of a shell command and use it as a variable or as input to another command.

    Syntax:

    • Backticks: `command`
    • $() Syntax: $(command)

    Example 1: Assigning Command Output to a Variable

    #!/bin/bash
    
    # Capture the current date
    current_date=$(date)
    
    # Print the date
    echo "Today's date is: $current_date"
    

    Example 2: Using Output in a Command

    #!/bin/bash
    
    # Find the total number of files in /tmp
    file_count=$(ls /tmp | wc -l)
    
    # Display the result
    echo "Number of files in /tmp: $file_count"

    2. Using Piping (|) to Process Output

    Piping sends the output of one command to another command for further processing.

    Example: Find the Largest File in a Directory

    #!/bin/bash
    
    # Find the largest file in the /var directory
    largest_file=$(du -ah /var | sort -rh | head -n 1)
    
    # Display the largest file
    echo "The largest file in /var is: $largest_file"
    

    3. Reading Command Output Line by Line

    You can process command output line by line using a while loop.

    Example: List Users and Their Home Directories

    #!/bin/bash
    
    # Get user information from /etc/passwd
    cat /etc/passwd | while IFS=: read -r username _ _ _ _ home_dir _
    do
        echo "User: $username, Home Directory: $home_dir"
    done

    4. Using read with Command Substitution

    The read command can directly capture the output of a command.

    Example: Capturing Hostname

    #!/bin/bash
    
    # Read the hostname into a variable
    read hostname < <(hostname)
    
    echo "The hostname of this machine is: $hostname"

    5. Using Process Substitution

    Process substitution allows you to use the output of a command as a file input.

    Example: Compare Two Command Outputs

    #!/bin/bash
    
    # Compare the list of files in two directories
    diff <(ls /dir1) <(ls /dir2)

    6. Parsing Output with awk and sed

    Use awk and sed for advanced processing of command output.

    Example: Extract Specific Columns with awk

    #!/bin/bash
    
    # Get disk usage and print the mount point
    df -h | awk '{print $1, $6}'
    

    Example: Modify Text with sed

    #!/bin/bash
    
    # Replace 'root' with 'admin' in /etc/passwd output
    cat /etc/passwd | sed 's/root/admin/g'

    7. Using Arrays to Process Multiple Outputs

    You can store the output of a command in an array for further processing.

    Example: Storing Directory Contents

    #!/bin/bash
    
    # Store the contents of /tmp in an array
    files=($(ls /tmp))
    
    # Loop through the array
    for file in "${files[@]}"; do
        echo "File: $file"
    done

    8. Error Handling for Commands

    Always handle errors when processing command outputs to make scripts robust.

    Example: Check if a Command Fails

    #!/bin/bash
    
    # Try to list a non-existent directory
    output=$(ls /nonexistent 2>/dev/null)
    
    if [ $? -ne 0 ]; then
        echo "Failed to list directory"
    else
        echo "Directory contents: $output"
    fi

    Best Practices

    1. Use $() for command substitution: It is more readable and supports nesting.
    2. Escape special characters: Handle filenames or outputs with special characters using quotes.
    3. Redirect errors: Use 2>/dev/null to suppress errors or 2>&1 to redirect errors to standard output.
    4. Validate outputs: Always check the exit status ($?) of commands.

    These techniques enable efficient processing of command outputs, a key skill for RHCSA and system administration tasks.


    Discover more from Altgr Blog

    Subscribe to get the latest posts sent to your email.

    Leave a Reply

    Your email address will not be published. Required fields are marked *