dotlinux guide

Shell Scripting 101: Writing Your First Script

Table of Contents

What is a Shell Script?

A shell script is a text file containing a sequence of commands executed by a Unix shell (e.g., Bash, Zsh, or Dash). It acts as a “recipe” for the shell, automating tasks like file management, system monitoring, or data processing.

Most Linux/macOS systems use Bash (Bourne Again Shell) as the default shell, so we’ll focus on Bash scripting here. Scripts are saved with a .sh extension (convention, not requirement) and executed directly from the command line.

Prerequisites

Before diving in, ensure you have:

  • Access to a Unix-like terminal (Linux, macOS, or WSL on Windows).
  • Basic familiarity with command-line tools (ls, cd, mkdir, rm, etc.).
  • A text editor (VS Code, Vim, Nano, or Sublime Text).

Your First Shell Script

Let’s write a simple “Hello World” script to get started. Follow these steps:

Step 1: Create the Script File

Open your text editor and create a new file named hello_world.sh.

Step 2: Add the Shebang Line

Every shell script should start with a shebang (#!), which tells the system which shell to use to execute the script. For Bash, use:

#!/bin/bash

Step 3: Add Commands

Below the shebang, add a comment (starts with #) and a command to print text:

#!/bin/bash

# My first shell script
echo "Hello, World!"

Step 4: Make It Executable

Scripts need execute permissions to run. Use chmod to grant this:

chmod +x hello_world.sh

Step 5: Run the Script

Execute the script with:

./hello_world.sh

Output:

Hello, World!

Congratulations! You’ve written and run your first shell script.

Fundamental Concepts

Variables

Variables store data for reuse. Use VAR_NAME=value to declare (no spaces around =), and $VAR_NAME to access.

Example:

#!/bin/bash

NAME="Alice"
AGE=30

echo "Name: $NAME"       # Output: Name: Alice
echo "Age: $AGE"         # Output: Age: 30

Quoting Variables: Always quote variables with "$VAR" to handle spaces in values:

FILE="my document.txt"
echo "Opening $FILE"     # Good: Handles spaces
# echo "Opening $FILE" → "Opening my document.txt"

# Bad: Without quotes, the shell splits "my" and "document.txt" into separate args
# echo Opening $FILE → "Opening my document.txt" (accidentally splits)

Input/Output

Reading Input

  • Command-Line Arguments: Access with $1, $2, …, $@ (all args), $# (number of args).
    Example: ./script.sh apple banana$1=apple, $2=banana.

  • User Input: Use read to capture input from the user:

    echo "Enter your name:"
    read -r NAME  # -r prevents backslash escapes
    echo "Hello, $NAME!"

Writing Output

  • echo: Simple text output (supports escape sequences with -e).
    Example: echo -e "Line 1\nLine 2" (newlines).
  • printf: More control over formatting (like C’s printf).
    Example: printf "Name: %s, Age: %d\n" "$NAME" "$AGE".

Control Structures

If-Else

Check conditions (e.g., file existence, numeric comparisons). Use [ condition ] (POSIX) or [[ condition ]] (Bash-specific, supports regex).

Example: Check if a file exists

FILE="data.txt"

if [ -f "$FILE" ]; then  # -f: regular file exists
    echo "$FILE exists."
elif [ -d "$FILE" ]; then  # -d: directory exists
    echo "$FILE is a directory."
else
    echo "$FILE does not exist."
fi

Loops

  • For Loop: Iterate over items (files, lists).
    Example: Loop through .txt files:

    for file in *.txt; do
        echo "Processing $file"
    done
  • While Loop: Run until a condition fails.
    Example: Read lines from a file:

    while IFS= read -r line; do  # IFS= preserves whitespace
        echo "Line: $line"
    done < "input.txt"  # Redirect file into loop

Functions

Reusable code blocks. Define with function_name() { ... } and call with function_name.

Example:

greet() {
    local name=$1  # Local variable (only in function)
    echo "Hello, $name!"
}

greet "Bob"  # Output: Hello, Bob!
greet "Charlie"  # Output: Hello, Charlie!

Usage Methods

Running Scripts

  • Direct Execution: ./script.sh (requires execute permission).
  • Via Shell: bash script.sh or sh script.sh (no execute permission needed).
  • Add to PATH: Move scripts to a directory in $PATH (e.g., ~/bin) to run from anywhere:
    mv script.sh ~/bin/  # ~/bin must be in PATH (check with `echo $PATH`)
    script.sh  # Now runs globally

Scheduling with Cron

Automate scripts to run at specific times using cron. Edit crontab with crontab -e and add a line like:

# Run backup script daily at 3 AM
0 3 * * * /home/user/scripts/backup.sh

Common Practices

  • Comments: Explain “why” not “what” (e.g., # Backup logs before rotation).
  • Naming: Use snake_case for scripts/functions (e.g., file_cleanup.sh).
  • Modularity: Split logic into functions (e.g., backup_files(), send_email()).
  • Error Handling: Check if commands succeed with if command; then ... or use set -e to exit on error:
    set -e  # Exit script if any command fails
    rm "important.txt"  # Script exits if this fails (e.g., file not found)

Best Practices

Use Explicit Shebangs

Always specify #!/bin/bash (not #!/bin/sh, which may use a minimal shell like Dash).

Strict Mode

Enable set -euo pipefail for robust scripts:

  • -e: Exit on any command failure.
  • -u: Treat undefined variables as errors.
  • -o pipefail: Exit if any command in a pipeline fails.
#!/bin/bash
set -euo pipefail  # Strict mode

Quote Variables

As mentioned earlier, quote variables to handle spaces: rm "$FILE" (not rm $FILE).

Avoid Hardcoded Paths

Use relative paths or environment variables (e.g., $HOME instead of /home/user).

Test with ShellCheck

Use ShellCheck to detect syntax errors and bad practices:

shellcheck script.sh  # Install with `sudo apt install shellcheck` (Linux)

Document with Help Messages

Add a --help flag to explain usage:

if [ "$1" = "--help" ]; then
    echo "Usage: $0 [name]"
    echo "Prints a greeting to [name]."
    exit 0
fi

Troubleshooting

  • Permission Denied: Run chmod +x script.sh to fix execute permissions.
  • Syntax Error: Check for missing spaces (e.g., if[ ... ]if [ ... ]) or use shellcheck.
  • Variable Not Expanding: Ensure variables are quoted ("$VAR") and not in single quotes ('$VAR').
  • Command Not Found: Check if dependencies are installed (e.g., jq, curl) or paths are correct.

Conclusion

Shell scripting is a cornerstone of Unix/Linux automation, enabling you to save time, reduce errors, and scale tasks. By mastering the fundamentals—variables, control structures, functions—and adopting best practices like strict mode and quoting, you can write robust, maintainable scripts.

Start small (e.g., a file cleanup script), test with shellcheck, and gradually tackle more complex projects (e.g., backups, log analysis). The Unix shell is a powerful ally—embrace it!

References