dotlinux guide

Introduction to Shell Scripting for Beginners: Getting Started

Table of Contents

  1. What is a Shell Script?
  2. Setting Up Your Environment
  3. Basic Shell Script Structure
  4. Variables and Data Types
  5. Control Structures
  6. Input/Output Handling
  7. Functions
  8. Common Practices
  9. Best Practices
  10. Sample Script: Putting It All Together
  11. Conclusion
  12. References

What is a Shell Script?

A shell script is a plain text file that contains a series of commands interpreted and executed by a shell. The shell acts as an intermediary between the user and the operating system, translating commands into actions.

Common shells include:

  • Bash (Bourne Again SHell): The most popular shell, default on Linux and macOS.
  • Zsh (Z Shell): An extended version of Bash with more features.
  • Sh (Bourne Shell): The original Unix shell (less common today).

Shell scripts are used for:

  • Automating repetitive tasks (e.g., renaming files, backups).
  • System administration (e.g., monitoring logs, managing users).
  • Running sequences of commands with conditional logic.

Setting Up Your Environment

Before writing scripts, ensure your environment is ready:

1. Check Your Shell

Most systems use Bash by default. Verify with:

echo $SHELL  # Output: /bin/bash (or /bin/zsh, etc.)

2. Text Editor

Use a simple editor like nano, vim, or VS Code. For beginners, nano is user-friendly:

nano my_script.sh  # Creates/opens "my_script.sh"

3. Make Scripts Executable

Scripts are not executable by default. Use chmod to set permissions:

chmod +x my_script.sh  # Grants execute permission

4. Run the Script

Execute with:

./my_script.sh  # Runs the script (if in current directory)
# Or specify the shell explicitly:
bash my_script.sh

Basic Shell Script Structure

A minimal shell script has two key components: the shebang line and commands.

Shebang Line

The first line of a script, #!/bin/bash, tells the system which shell to use. Always include this to avoid ambiguity:

#!/bin/bash

Comments

Use # for comments (ignored by the shell). Explain why (not what) the code does:

#!/bin/bash

# This script prints a greeting
echo "Hello, World!"  # Output: Hello, World!

Example: “Hello World” Script

Create hello_world.sh:

#!/bin/bash
# My first shell script

echo "Hello, World!"

Run it:

chmod +x hello_world.sh
./hello_world.sh  # Output: Hello, World!

Variables and Data Types

Variables store data for reuse. Shell scripting has loose typing (no need to declare types).

Declaring Variables

Use VAR_NAME=value (no spaces around =):

name="Alice"
age=30

Accessing Variables

Prefix with $ or ${} (curly braces avoid ambiguity):

echo "Name: $name"       # Output: Name: Alice
echo "Age: ${age} years" # Output: Age: 30 years

Special Variables

Shells provide built-in variables for common tasks:

  • $0: Script name (e.g., my_script.sh).
  • $1, $2...: Command-line arguments (e.g., ./script.sh arg1 arg2$1=arg1).
  • $#: Number of arguments.
  • $?: Exit status of the last command (0 = success, non-zero = error).
  • $HOME: User’s home directory (e.g., /home/alice).

Example with command-line arguments:

#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Number of arguments: $#"

Run with:

./script.sh apple banana
# Output:
# Script name: ./script.sh
# First argument: apple
# Number of arguments: 2

Environment Variables

Set variables globally with export:

export PATH="$PATH:/new/directory"  # Adds a directory to PATH

Control Structures

Control structures (conditionals, loops) add logic to scripts.

If-Else Statements

Check conditions and execute code accordingly. Syntax:

if [ condition ]; then
  # Code if true
elif [ another_condition ]; then
  # Code if first condition false, second true
else
  # Code if all conditions false
fi

Common Conditions:

  • -eq: Equal (numbers, e.g., 5 -eq 5).
  • -ne: Not equal.
  • -gt: Greater than.
  • -lt: Less than.
  • -f "file": File exists.
  • -z "string": String is empty.

Example: Check if a number is positive:

#!/bin/bash
read -p "Enter a number: " num  # Read user input

if [ $num -gt 0 ]; then
  echo "$num is positive"
elif [ $num -lt 0 ]; then
  echo "$num is negative"
else
  echo "$num is zero"
fi

Loops (For and While)

For Loops

Iterate over a list of items:

# Syntax: for item in list; do ... done
for fruit in apple banana cherry; do
  echo "I like $fruit"
done

Output:

I like apple
I like banana
I like cherry

Loop through numbers with {start..end}:

for i in {1..5}; do
  echo "Count: $i"
done

While Loops

Repeat while a condition is true:

# Syntax: while [ condition ]; do ... done
count=1
while [ $count -le 5 ]; do
  echo "Count: $count"
  count=$((count + 1))  # Increment count (arithmetic expansion)
done

Input/Output Handling

Read User Input

Use read to capture input from the user:

#!/bin/bash
read -p "Enter your name: " name  # -p adds a prompt
echo "Hello, $name!"

Redirect Output

  • >: Overwrite a file (e.g., echo "Hi" > output.txt).
  • >>: Append to a file (e.g., echo "Again" >> output.txt).
  • 2>: Redirect errors (e.g., ls non_existent_file 2> error.log).

Pipes (|)

Pass output of one command to another:

ls -l | grep ".txt"  # List files, then filter for .txt

Functions

Functions group reusable code. Syntax:

function_name() {
  # Code here
  # Use $1, $2... for parameters
}

Example: Greet a user:

#!/bin/bash

greet() {
  local name=$1  # "local" limits variable scope to the function
  echo "Hello, $name!"
}

greet "Bob"  # Output: Hello, Bob!

Return values: Use echo to return a value and capture with $(function):

add() {
  echo $(( $1 + $2 ))
}

sum=$(add 3 5)
echo "Sum: $sum"  # Output: Sum: 8

Common Practices

Test Scripts First

  • Use bash -n script.sh to check for syntax errors (dry run).
  • Use bash -x script.sh to debug (prints each command as it runs).

Handle Errors Gracefully

Check if commands succeed before proceeding:

if ! cp file.txt backup/; then  # "!" inverts success status
  echo "Error: Failed to copy file"
  exit 1  # Exit with non-zero code to indicate failure
fi

Best Practices

1. Use the Shebang Line

Always start with #!/bin/bash (or your shell) to avoid relying on the system default.

2. Quote Variables

Quoting prevents issues with spaces in filenames/strings:

filename="my file.txt"
echo "$filename"  # Correct: preserves spaces
# echo $filename  # Incorrect: splits into "my" and "file.txt"

3. Use set -euo pipefail

Add this at the top to make scripts robust:

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

4. Limit Scope with local Variables

In functions, use local to avoid polluting the global namespace:

my_func() {
  local var="local only"  # Not accessible outside the function
}

5. Avoid Hard-Coded Paths

Use variables for paths to make scripts portable:

BACKUP_DIR="$HOME/backups"  # Instead of "/home/alice/backups"
mkdir -p "$BACKUP_DIR"      # "-p" creates parent dirs if needed

Sample Script: Putting It All Together

Let’s write a script that backs up .txt files from a directory to a backup folder, logs the action, and counts the files backed up.

#!/bin/bash
set -euo pipefail

# Configuration
SOURCE_DIR="$1"          # First command-line argument: source directory
BACKUP_DIR="$HOME/backups"
LOG_FILE="$BACKUP_DIR/backup_log.txt"

# Check if source directory is provided
if [ $# -eq 0 ]; then
  echo "Error: Please provide a source directory."
  echo "Usage: $0 <source_directory>"
  exit 1
fi

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

# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"

# Count .txt files
FILE_COUNT=$(find "$SOURCE_DIR" -maxdepth 1 -type f -name "*.txt" | wc -l)

# Backup .txt files
echo "Backing up $FILE_COUNT .txt files from $SOURCE_DIR to $BACKUP_DIR..."
cp "$SOURCE_DIR"/*.txt "$BACKUP_DIR"/

# Log the action
timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] Backed up $FILE_COUNT files from $SOURCE_DIR" >> "$LOG_FILE"

echo "Backup completed successfully. Logged to $LOG_FILE"
exit 0

Run it with:

./backup_script.sh /path/to/txt_files

Conclusion

Shell scripting is a powerful tool for automating tasks and simplifying system administration. By mastering the basics—variables, control structures, input/output, and functions—you can write scripts to save time and reduce errors.

Start small: automate a daily task (e.g., organizing downloads), then gradually add complexity. Remember to follow best practices like quoting variables and handling errors to write robust scripts.

Happy scripting!

References