dotlinux guide

Linux Scripting Made Easy: Writing Your First Bash Script

In the world of Linux, automation is key to efficiency. Whether you’re a system administrator, developer, or casual user, repetitive tasks like file backups, log analysis, or software installations can eat up valuable time. This is where Bash scripting shines. Bash (Bourne Again SHell) is the default shell for most Linux distributions, and scripting with Bash allows you to automate these tasks with simple, readable code. This guide is designed for beginners and intermediate Linux users looking to dive into scripting. We’ll start with the basics,逐步 build up to more advanced concepts, and equip you with the tools to write robust, maintainable scripts. By the end, you’ll be able to create your own Bash scripts to streamline your workflow.

Table of Contents

  1. What is a Bash Script?
  2. Fundamental Concepts
  3. How to Write and Run a Bash Script
  4. Common Practices
  5. Best Practices for Robust Scripts
  6. Real-World Example: A Simple Backup Script
  7. Conclusion
  8. References

What is a Bash Script?

A Bash script is a plain text file containing a sequence of commands that the Bash shell can execute. Think of it as a “recipe” for the shell: instead of typing commands one by one in the terminal, you write them down in a script and run the script to execute all commands at once.

Scripts are used for:

  • Automating repetitive tasks (e.g., backups, log rotation).
  • Automating system administration (e.g., user management, package updates).
  • Creating custom tools or utilities.

Fundamental Concepts

The Shebang Line

Every Bash script should start with the shebang line, which tells the system which interpreter to use to run the script. For Bash scripts, this is:

#!/bin/bash
  • #! (pronounced “shebang”) signals the start of the interpreter path.
  • /bin/bash is the path to the Bash interpreter.

Why not #!/bin/sh? sh is a POSIX-compliant shell, but Bash has additional features (e.g., arrays, string manipulation). Use #!/bin/bash for full Bash compatibility.

Variables

Variables store data for later use. In Bash, you declare a variable without spaces around the = sign:

name="Alice"
age=30

To access a variable, prefix it with $:

echo "Hello, $name! You are $age years old."

Output:
Hello, Alice! You are 30 years old.

Environment Variables

Some variables are predefined by the system (e.g., $HOME for your home directory, $PATH for executable paths). You can use them in scripts:

echo "Your home directory is: $HOME"
echo "Your PATH is: $PATH"

Input and Output

Bash scripts interact with the user and system through input/output (I/O) operations.

Output with echo

The echo command prints text to the terminal:

echo "This is a message."
# Print variables
echo "Today is $(date)"  # $(command) runs "date" and inserts its output

Input with read

The read command captures user input and stores it in a variable:

echo "Enter your name:"
read username  # Waits for user input and stores it in $username
echo "Hello, $username!"

Shortcut: Use -p to prompt and read in one line:

read -p "Enter your age: " age
echo "You are $age years old."

Control Structures

Control structures let you add logic to scripts (e.g., conditionals, loops).

If-Else Statements

Check conditions and run code accordingly. Use [ ] (POSIX syntax) or [[ ]] (Bash-specific, more powerful):

read -p "Enter a number: " num

if [[ $num -gt 10 ]]; then  # -gt = "greater than"
  echo "$num is greater than 10."
elif [[ $num -eq 10 ]]; then  # -eq = "equal to"
  echo "$num is equal to 10."
else
  echo "$num is less than 10."
fi

Common condition checks:

  • -eq: Equal to (numbers)
  • -ne: Not equal to (numbers)
  • -lt: Less than (numbers)
  • -gt: Greater than (numbers)
  • -z "$var": Check if var is empty
  • -f "file": Check if file exists and is a regular file

Loops

Repeat commands multiple times.

For Loop (iterate over a list):

# Loop over numbers 1-5
for i in {1..5}; do
  echo "Number: $i"
done

# Loop over files in a directory
for file in /home/user/documents/*.txt; do
  echo "Found text file: $file"
done

While Loop (run until a condition is false):

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

Functions

Functions group reusable code into named blocks. Define them with function name() { ... } or name() { ... }:

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

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

Output:
Hello, Bob!

How to Write and Run a Bash Script

Step 1: Create the Script File

Use a text editor like nano, vim, or VS Code to create a .sh file (convention, not required):

nano my_script.sh

Step 2: Add Code

Paste the following example (a “Hello World” script):

#!/bin/bash

# My first Bash script
echo "Hello, World!"
read -p "What's your name? " name
echo "Welcome, $name! Today is $(date +%A)."  # %A = day of the week

Step 3: Make It Executable

By default, text files aren’t executable. Use chmod to add execute permission:

chmod +x my_script.sh  # +x = "add execute"

Step 4: Run the Script

Execute it with ./script.sh (if in the same directory) or the full path:

./my_script.sh
# Or
/home/user/scripts/my_script.sh

Pro Tip: Add the script’s directory to your $PATH to run it from anywhere:
export PATH="$HOME/scripts:$PATH" (add to ~/.bashrc to make permanent).

Common Practices

Using Command-Line Arguments

Pass inputs to scripts when running them (instead of using read). Arguments are stored in $1, $2, etc. ($0 is the script name):

#!/bin/bash
# script: greet.sh
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"

Run it:
./greet.sh Alice 30

Output:

Script name: ./greet.sh
First argument: Alice
Second argument: 30

Use $# to check the number of arguments:

if [[ $# -ne 2 ]]; then  # -ne = "not equal to"
  echo "Usage: $0 <name> <age>"
  exit 1  # Exit with error code (non-zero = failure)
fi

Commenting Your Code

Comments (#) explain what the script does—critical for readability:

#!/bin/bash
# Purpose: Backup a directory to a timestamped zip file
# Usage: ./backup.sh <directory>
# Example: ./backup.sh ~/documents

Error Handling

Prevent scripts from running on errors with set -e (exit on error) and set -u (exit on undefined variable):

#!/bin/bash
set -euo pipefail  # Exit on error, undefined var, or pipeline failure

# If "bad_command" doesn't exist, script exits immediately
bad_command  # This will fail, and the script stops here
echo "This line will NOT run."

Use trap to run commands on script exit (e.g., clean up temporary files):

trap 'echo "Script interrupted!"; exit 1' SIGINT  # Catch Ctrl+C

Best Practices

Quote Variables to Avoid Spaces

Unquoted variables break if they contain spaces. Always quote them:

filename="my file.txt"
# Bad: cp $filename /tmp  # Breaks (treats "my" and "file.txt" as separate args)
# Good:
cp "$filename" /tmp  # Quoting ensures it’s treated as one argument

Check for Dependencies

Ensure required tools (e.g., tar, curl) exist before running:

#!/bin/bash
if ! command -v tar &> /dev/null; then  # &> /dev/null suppresses output
  echo "Error: 'tar' is not installed."
  exit 1
fi

Test Scripts Thoroughly

  • Use set -x to debug (prints each command before running):
    bash -x my_script.sh
  • Test edge cases (e.g., empty inputs, missing files).
  • Run with a non-root user to avoid accidental damage.

Use Version Control

Store scripts in Git (or another VCS) to track changes and revert if needed:

git init  # Initialize repo (once)
git add my_script.sh
git commit -m "Add backup script with error handling"

Real-World Example: A Simple Backup Script

Let’s combine everything into a practical script that backs up a directory to a timestamped .tar.gz file:

#!/bin/bash
set -euo pipefail

# Backup Script
# Usage: ./backup.sh <source_dir> <dest_dir>

# Check for correct number of arguments
if [[ $# -ne 2 ]]; then
  echo "Usage: $0 <source_dir> <dest_dir>"
  exit 1
fi

source_dir="$1"
dest_dir="$2"

# Check if source directory exists
if [[ ! -d "$source_dir" ]]; then
  echo "Error: Source directory '$source_dir' does not exist."
  exit 1
fi

# Create destination directory if it doesn't exist
mkdir -p "$dest_dir"  # -p = "create parent dirs if needed"

# Generate timestamp (e.g., 20240520_1430)
timestamp=$(date +%Y%m%d_%H%M)
backup_file="$dest_dir/backup_$timestamp.tar.gz"

# Backup the directory
echo "Backing up '$source_dir' to '$backup_file'..."
tar -czf "$backup_file" -C "$source_dir" .  # -czf = "compress, zip, file"

echo "Backup completed successfully!"

How to Use:
./backup.sh ~/photos ~/backups
Creates ~/backups/backup_20240520_1430.tar.gz.

Conclusion

Bash scripting is a powerful tool for automating Linux tasks. By mastering variables, control structures, and best practices like error handling and quoting, you can write scripts that save time and reduce manual work.

Start small (e.g., a script to organize files) and gradually tackle more complex projects. The key is practice—experiment, break things, and learn from mistakes!

References