dotlinux guide

Mastering Shell Scripting: A Comprehensive Beginner's Guide

Table of Contents

  1. What is Shell Scripting?
    • 1.1 What is a Shell?
    • 1.2 What is a Shell Script?
    • 1.3 Why Learn Shell Scripting?
  2. Getting Started with Shell Scripting
    • 2.1 The Shebang Line
    • 2.2 Your First Shell Script
    • 2.3 Making Scripts Executable
    • 2.4 Running Shell Scripts
  3. Core Concepts
    • 3.1 Variables
    • 3.2 Control Structures (Conditionals and Loops)
    • 3.3 Functions
    • 3.4 Input/Output Handling
  4. Common Practices for Effective Scripting
    • 4.1 Commenting and Documentation
    • 4.2 Error Handling
    • 4.3 Logging
  5. Best Practices
    • 5.1 Quoting Variables to Avoid Pitfalls
    • 5.2 Using Modern Syntax ([[ ]] Over [ ])
    • 5.3 Avoiding Reserved Names and Uppercase Variables
    • 5.4 Validating Dependencies and Inputs
  6. Advanced Tips for Mastery
    • 6.1 Arrays
    • 6.2 Arithmetic Operations
    • 6.3 Debugging Techniques
    • 6.4 Sourcing Scripts
  7. Conclusion
  8. References

1. What is Shell Scripting?

1.1 What is a Shell?

A shell is a command-line interpreter that acts as an interface between the user and the operating system kernel. It reads commands from the user (or a script) and executes them. Popular shells include:

  • Bash (Bourne-Again Shell): The most common shell, default on Linux and macOS.
  • Zsh (Z Shell): Extends Bash with features like auto-completion and themes.
  • sh (Bourne Shell): The original Unix shell (less feature-rich than Bash).

This guide focuses on Bash, as it is widely adopted and compatible with most systems.

1.2 What is a Shell Script?

A shell script is a text file containing a sequence of shell commands. Instead of typing commands manually, you can write them in a script and execute the file to run all commands at once. Scripts support logic (e.g., if-else), loops, variables, and functions, making them ideal for automation.

1.3 Why Learn Shell Scripting?

  • Automation: Automate repetitive tasks (e.g., backups, log rotation, file cleanup).
  • System Administration: Manage users, services, and configurations at scale.
  • DevOps: Integrate with tools like git, docker, or CI/CD pipelines.
  • Productivity: Replace manual, error-prone workflows with reusable scripts.

2. Getting Started with Shell Scripting

2.1 The Shebang Line

Every shell script starts with a shebang line (#!), which tells the system which interpreter to use. For Bash scripts, the shebang line is:

#!/bin/bash

This ensures the script runs with Bash, even if another shell is default.

2.2 Your First Shell Script

Let’s create a simple “Hello World” script. Open a text editor (e.g., nano, vim) and paste:

#!/bin/bash

# This is a comment (ignored by the shell)
echo "Hello, World!"  # Print text to the terminal

Save the file as hello.sh.

2.3 Making Scripts Executable

By default, text files are not executable. Use chmod to grant execution permissions:

chmod +x hello.sh

2.4 Running Shell Scripts

Execute the script with:

./hello.sh  # Runs the script in the current directory

Output:

Hello, World!

3. Core Concepts

3.1 Variables

Variables store data for reuse. In Bash, declare variables without spaces around =:

#!/bin/bash

name="Alice"  # Local variable (no $ when assigning)
echo "Hello, $name!"  # Use $ to access the variable

Key Variable Types:

  • Local Variables: Defined in the script (e.g., name="Alice").
  • Environment Variables: Global variables (e.g., $HOME, $PATH; avoid overwriting these).
  • Special Variables: Predefined by Bash for script metadata:
    • $0: Script name.
    • $1, $2, …: Positional arguments (e.g., ./script.sh arg1 arg2$1=arg1).
    • $#: Number of arguments.
    • $?: Exit code of the last command (0 = success, non-zero = error).

3.2 Control Structures

Control structures add logic to scripts.

If-Else Statements

Check conditions with if-else. Use [[ ]] (modern) or [ ] (POSIX-compliant) for comparisons:

#!/bin/bash

age=20

if [[ $age -ge 18 ]]; then  # -ge = greater than or equal
  echo "Adult"
elif [[ $age -ge 13 ]]; then
  echo "Teenager"
else
  echo "Child"
fi

Loops

Repeat commands with for, while, or until loops.

For Loop (Iterate Over Items):

#!/bin/bash

fruits=("apple" "banana" "cherry")

for fruit in "${fruits[@]}"; do  # Loop through array elements
  echo "I like $fruit"
done

While Loop (Run Until Condition Fails):

#!/bin/bash

count=1

while [[ $count -le 5 ]]; do
  echo "Count: $count"
  ((count++))  # Increment count
done

3.3 Functions

Reuse code with functions. Define them with function name() { ... } or name() { ... }:

#!/bin/bash

greet() {
  local name=$1  # $1 = first argument to the function
  echo "Hello, $name!"
}

greet "Bob"  # Call the function with "Bob" as an argument

Output:

Hello, Bob!

3.4 Input/Output Handling

Read User Input

Use read to capture input from the user:

#!/bin/bash

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

Redirect Output

  • >: Overwrite a file (e.g., echo "Hi" > output.txt).
  • >>: Append to a file (e.g., echo "Again" >> output.txt).
  • |: Pipe output of one command to another (e.g., ls -l | grep ".txt").

4. Common Practices for Effective Scripting

4.1 Commenting and Documentation

Add comments to explain why (not just what) the code does:

#!/bin/bash

# Backup log files to /tmp/backups (runs daily at 2 AM via cron)
LOG_DIR="/var/log"
BACKUP_DIR="/tmp/backups"

mkdir -p "$BACKUP_DIR"  # Create backup dir if it doesn't exist
cp "$LOG_DIR"/*.log "$BACKUP_DIR"  # Copy logs to backup

4.2 Error Handling

Prevent silent failures with:

  • set -e: Exit the script if any command fails (non-zero exit code).
  • set -u: Treat unset variables as errors.
  • trap: Run commands on script exit (e.g., clean up temporary files).

Example:

#!/bin/bash

set -euo pipefail  # Exit on error, unset var, or pipe failure

# Cleanup temporary files on exit
cleanup() {
  rm -f /tmp/tempfile.txt
  echo "Cleanup done."
}
trap cleanup EXIT  # Run cleanup when script exits

# Simulate an error (script will exit here)
false  # This command fails (exit code 1)
echo "This line will never run."

4.3 Logging

Log messages to a file for debugging and auditing:

#!/bin/bash

LOG_FILE="/var/log/myscript.log"

log() {
  local message="$1"
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $message" >> "$LOG_FILE"
}

log "Script started"
log "User $USER ran the script"

5. Best Practices

5.1 Quote Variables to Avoid Word Splitting

Unquoted variables with spaces cause “word splitting.” Always quote variables with "$var" to preserve spaces:

#!/bin/bash

name="Alice Smith"

echo "Hello, $name"       # Good: Outputs "Hello, Alice Smith"
echo "Hello, " $name      # Bad: Outputs "Hello, Alice Smith" (accidental, but risky)
echo "Files: $HOME/*"     # Bad: Expands to filenames; use "$HOME"/* instead

5.2 Use [[ ]] Instead of [ ]

[[ ]] (Bash-specific) supports regex, globbing, and avoids edge cases (e.g., empty variables):

#!/bin/bash

name=""

# Bad: [ ] fails if variable is empty (needs quotes)
if [ "$name" = "Alice" ]; then echo "Match"; fi

# Good: [[ ]] handles empty variables safely
if [[ $name = "Alice" ]]; then echo "Match"; fi

5.3 Avoid Reserved Names and Uppercase Variables

  • Reserved Names: Don’t name variables/functions after commands (e.g., ls, cd).
  • Uppercase Variables: Reserve for environment variables (e.g., PATH). Use lowercase for local variables.

5.4 Validate Dependencies and Inputs

Check if required commands or files exist before running:

#!/bin/bash

# Check if "git" is installed
if ! command -v git &> /dev/null; then
  echo "Error: git is not installed."
  exit 1
fi

# Check if input file exists
input_file="data.txt"
if [[ ! -f "$input_file" ]]; then
  echo "Error: $input_file not found."
  exit 1
fi

6. Advanced Tips for Mastery

6.1 Arrays

Store lists of data with arrays:

#!/bin/bash

colors=("red" "green" "blue")
echo "First color: ${colors[0]}"  # Access by index (0-based)
echo "All colors: ${colors[@]}"   # Expand all elements

6.2 Arithmetic Operations

Use $(( ... )) for arithmetic:

#!/bin/bash

a=5
b=3
sum=$((a + b))
echo "5 + 3 = $sum"  # Output: 5 + 3 = 8

6.3 Debugging Techniques

  • set -x: Print commands as they run (debug mode).
  • bash -x script.sh: Run the script with debugging without editing it.

Example:

#!/bin/bash

set -x  # Enable debugging
name="Alice"
echo "Hello, $name"
set +x  # Disable debugging

6.4 Sourcing Scripts

Use source script.sh or . script.sh to run a script in the current shell (import variables/functions):

# In utils.sh
greet() { echo "Hello, $1"; }

# In main.sh
source ./utils.sh  # Import greet()
greet "Charlie"  # Output: Hello, Charlie

7. Conclusion

Shell scripting is a foundational skill for automation and system management. By mastering variables, control structures, and best practices like error handling and quoting, you can write scripts that are robust, maintainable, and efficient.

Start small (e.g., automate file backups), then gradually tackle complex tasks. Refer to the Bash manual and experiment—practice is key to mastery!

8. References