Complete Guide to Creating Simple Shell Scripts
Table of Contents
1. [Introduction to Shell Scripting](#introduction) 2. [Shell Script Basics](#basics) 3. [Creating Your First Script](#first-script) 4. [Variables and Data Types](#variables) 5. [Input/Output Operations](#input-output) 6. [Control Structures](#control-structures) 7. [Functions](#functions) 8. [File Operations](#file-operations) 9. [Error Handling](#error-handling) 10. [Advanced Topics](#advanced-topics) 11. [Best Practices](#best-practices) 12. [Common Examples](#examples)Introduction to Shell Scripting {#introduction}
Shell scripting is a powerful way to automate tasks in Unix-like operating systems. A shell script is a text file containing a series of commands that are executed sequentially by the shell interpreter.
What is a Shell Script?
A shell script is essentially a program written for the Unix shell. It allows you to: - Automate repetitive tasks - Combine multiple commands into a single executable file - Create custom utilities and tools - Perform system administration tasks - Process files and data
Types of Shells
| Shell Type | Description | Command | Features |
|------------|-------------|---------|----------|
| Bash | Bourne Again Shell | /bin/bash | Most common, feature-rich |
| Sh | Bourne Shell | /bin/sh | POSIX compliant, basic |
| Zsh | Z Shell | /bin/zsh | Advanced features, customizable |
| Fish | Friendly Interactive Shell | /bin/fish | User-friendly, syntax highlighting |
| Dash | Debian Almquist Shell | /bin/dash | Lightweight, fast |
Shell Script Basics {#basics}
Script Structure
Every shell script follows a basic structure:
`bash
#!/bin/bash
This is a comment
Script description and author information
Variable declarations
variable_name="value"Main script logic
command1 command2 command3Exit status
exit 0`Shebang Line
The shebang (#!) line tells the system which interpreter to use:
| Shebang | Interpreter | Usage |
|---------|-------------|-------|
| #!/bin/bash | Bash shell | Most common for Linux |
| #!/bin/sh | Bourne shell | POSIX compatibility |
| #!/usr/bin/env bash | Bash via env | Portable across systems |
| #!/bin/zsh | Z shell | Advanced features |
Comments
Comments are essential for documentation:
`bash
Single line comment
echo "Hello World" # Inline commentMulti-line comment block
: ' This is a multi-line comment Everything between the quotes is ignored Useful for documentation blocks '`Creating Your First Script {#first-script}
Step-by-Step Process
1. Create the script file:
`bash
touch myscript.sh
`
2. Edit the script:
`bash
nano myscript.sh
or
vim myscript.sh`3. Add content:
`bash
#!/bin/bash
echo "Hello, World!"
echo "This is my first shell script"
`
4. Make it executable:
`bash
chmod +x myscript.sh
`
5. Run the script:
`bash
./myscript.sh
or
bash myscript.sh`File Permissions
Understanding file permissions is crucial:
| Permission | Numeric | Symbolic | Description | |------------|---------|----------|-------------| | Read | 4 | r | Can read file content | | Write | 2 | w | Can modify file | | Execute | 1 | x | Can run as program |
Common permission combinations:
- 755 (rwxr-xr-x): Owner can read/write/execute, others can read/execute
- 644 (rw-r--r--): Owner can read/write, others can read only
- 700 (rwx------): Only owner has full access
Variables and Data Types {#variables}
Variable Declaration and Usage
`bash
#!/bin/bash
Variable assignment (no spaces around =)
name="John Doe" age=25 pi=3.14159Using variables
echo "Name: $name" echo "Age: ${age}" echo "Pi value: $pi"Read-only variables
readonly PI=3.14159 declare -r CONSTANT="Cannot change"Environment variables
export MY_VAR="Available to child processes"`Variable Types
| Type | Declaration | Example | Description |
|------|-------------|---------|-------------|
| String | var="text" | name="Alice" | Text data |
| Integer | var=123 | count=10 | Numeric data |
| Array | var=(a b c) | fruits=(apple banana) | Multiple values |
| Readonly | readonly var=value | readonly PI=3.14 | Immutable |
| Environment | export var=value | export PATH=/usr/bin | Global scope |
Special Variables
`bash
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Process ID: $"
echo "Exit status of last command: $?"
`
| Variable | Description | Example |
|----------|-------------|---------|
| $0 | Script name | ./script.sh |
| $1, $2, ... | Positional parameters | First, second argument |
| $@ | All arguments as separate words | "arg1" "arg2" "arg3" |
| $* | All arguments as single word | "arg1 arg2 arg3" |
| $# | Number of arguments | 3 |
| $ | Current process ID | 1234 |
| $? | Exit status of last command | 0 for success |
| $! | Process ID of last background job | 5678 |
Input/Output Operations {#input-output}
Reading User Input
`bash
#!/bin/bash
Simple input
echo "Enter your name:" read name echo "Hello, $name!"Input with prompt
read -p "Enter your age: " age echo "You are $age years old"Silent input (for passwords)
read -s -p "Enter password: " password echo echo "Password entered"Reading multiple values
echo "Enter first and last name:" read first last echo "First: $first, Last: $last"Reading with timeout
if read -t 10 -p "Enter something (10 seconds): " input; then echo "You entered: $input" else echo "Timeout occurred" fi`Output Commands
| Command | Purpose | Example | Notes |
|---------|---------|---------|-------|
| echo | Display text | echo "Hello" | Adds newline by default |
| printf | Formatted output | printf "%s\n" "Hello" | More control over formatting |
| cat | Display file content | cat file.txt | Concatenate and display |
| tee | Write to file and stdout | echo "text" \| tee file.txt | Useful for logging |
Printf Formatting
`bash
#!/bin/bash
name="Alice" age=25 height=5.8
String formatting
printf "Name: %s\n" "$name"Integer formatting
printf "Age: %d years\n" "$age"Float formatting
printf "Height: %.1f feet\n" "$height"Padding and alignment
printf "%-10s %5d\n" "$name" "$age"Multiple values
printf "%s is %d years old and %.1f feet tall\n" "$name" "$age" "$height"`Control Structures {#control-structures}
Conditional Statements
#### If-Then-Else
`bash
#!/bin/bash
age=18
if [ $age -ge 18 ]; then echo "You are an adult" elif [ $age -ge 13 ]; then echo "You are a teenager" else echo "You are a child" fi
File testing
file="test.txt" if [ -f "$file" ]; then echo "File exists" else echo "File does not exist" fiString comparison
name="Alice" if [ "$name" = "Alice" ]; then echo "Hello Alice!" fi`#### Test Conditions
| Test | Description | Example |
|------|-------------|---------|
| -eq | Equal (numeric) | [ $a -eq $b ] |
| -ne | Not equal (numeric) | [ $a -ne $b ] |
| -lt | Less than | [ $a -lt $b ] |
| -le | Less than or equal | [ $a -le $b ] |
| -gt | Greater than | [ $a -gt $b ] |
| -ge | Greater than or equal | [ $a -ge $b ] |
| = | String equal | [ "$a" = "$b" ] |
| != | String not equal | [ "$a" != "$b" ] |
| -z | String is empty | [ -z "$string" ] |
| -n | String is not empty | [ -n "$string" ] |
#### File Test Operators
| Test | Description | Example |
|------|-------------|---------|
| -f | Regular file | [ -f "file.txt" ] |
| -d | Directory | [ -d "mydir" ] |
| -e | File exists | [ -e "path" ] |
| -r | Readable | [ -r "file.txt" ] |
| -w | Writable | [ -w "file.txt" ] |
| -x | Executable | [ -x "script.sh" ] |
| -s | File not empty | [ -s "file.txt" ] |
Case Statements
`bash
#!/bin/bash
read -p "Enter a grade (A-F): " grade
case $grade in
A|a)
echo "Excellent!"
;;
B|b)
echo "Good job!"
;;
C|c)
echo "Average"
;;
D|d)
echo "Below average"
;;
F|f)
echo "Failed"
;;
*)
echo "Invalid grade"
;;
esac
`
Loops
#### For Loops
`bash
#!/bin/bash
Simple for loop
for i in 1 2 3 4 5; do echo "Number: $i" doneRange-based for loop
for i in {1..10}; do echo "Count: $i" doneStep increment
for i in {0..20..2}; do echo "Even number: $i" doneC-style for loop
for ((i=1; i<=5; i++)); do echo "Iteration: $i" doneIterating over files
for file in *.txt; do echo "Processing: $file" doneIterating over arrays
fruits=("apple" "banana" "orange") for fruit in "${fruits[@]}"; do echo "Fruit: $fruit" done`#### While Loops
`bash
#!/bin/bash
Simple while loop
counter=1 while [ $counter -le 5 ]; do echo "Counter: $counter" ((counter++)) doneReading file line by line
while IFS= read -r line; do echo "Line: $line" done < "input.txt"Infinite loop with break
while true; do read -p "Enter 'quit' to exit: " input if [ "$input" = "quit" ]; then break fi echo "You entered: $input" done`#### Until Loops
`bash
#!/bin/bash
counter=1
until [ $counter -gt 5 ]; do
echo "Counter: $counter"
((counter++))
done
`
Functions {#functions}
Function Declaration and Usage
`bash
#!/bin/bash
Function definition
greet() { echo "Hello, $1!" }Function with multiple parameters
calculate_area() { local length=$1 local width=$2 local area=$((length * width)) echo $area }Function with return value
is_even() { local number=$1 if [ $((number % 2)) -eq 0 ]; then return 0 # true else return 1 # false fi }Using functions
greet "Alice" area=$(calculate_area 5 3) echo "Area: $area"if is_even 4; then
echo "4 is even"
fi
`
Function Features
| Feature | Syntax | Example | Description |
|---------|--------|---------|-------------|
| Local variables | local var=value | local name="John" | Variable scope within function |
| Parameters | $1, $2, ... | function_name param1 param2 | Access function arguments |
| Return value | return n | return 0 | Exit status (0-255) |
| Function call | function_name args | greet "Alice" | Execute function |
Advanced Function Example
`bash
#!/bin/bash
Function with error handling and validation
backup_file() { local source_file=$1 local backup_dir=${2:-"./backup"} # Validate input if [ $# -lt 1 ]; then echo "Usage: backup_fileUsage
backup_file "important.txt" "/home/user/backups"`File Operations {#file-operations}
Basic File Operations
`bash
#!/bin/bash
File creation and manipulation
touch "newfile.txt" echo "Hello World" > "newfile.txt" echo "Second line" >> "newfile.txt"File information
if [ -f "newfile.txt" ]; then echo "File size: $(stat -f%z "newfile.txt" 2>/dev/null || stat -c%s "newfile.txt")" echo "Last modified: $(stat -f%Sm "newfile.txt" 2>/dev/null || stat -c%y "newfile.txt")" fiDirectory operations
mkdir -p "test/subdirectory" cd "test" pwd ls -la cd ..File copying and moving
cp "newfile.txt" "backup.txt" mv "backup.txt" "test/"File deletion
rm "newfile.txt" rmdir "test/subdirectory" rm -rf "test"`File Processing Examples
`bash
#!/bin/bash
Count lines in a file
count_lines() { local file=$1 if [ -f "$file" ]; then wc -l < "$file" else echo "File not found: $file" >&2 return 1 fi }Search and replace in file
search_replace() { local file=$1 local search=$2 local replace=$3 if [ -f "$file" ]; then sed -i.bak "s/$search/$replace/g" "$file" echo "Replaced '$search' with '$replace' in $file" else echo "File not found: $file" >&2 return 1 fi }Extract file extension
get_extension() { local filename=$1 echo "${filename##*.}" }Usage examples
echo "Lines in file: $(count_lines "data.txt")" search_replace "config.txt" "old_value" "new_value" echo "Extension: $(get_extension "document.pdf")"`File Processing Commands
| Command | Purpose | Example | Description |
|---------|---------|---------|-------------|
| cat | Display file | cat file.txt | Show entire file |
| head | Show first lines | head -n 10 file.txt | Display first 10 lines |
| tail | Show last lines | tail -n 5 file.txt | Display last 5 lines |
| grep | Search text | grep "pattern" file.txt | Find matching lines |
| sed | Stream editor | sed 's/old/new/g' file.txt | Replace text |
| awk | Text processing | awk '{print $1}' file.txt | Extract columns |
| sort | Sort lines | sort file.txt | Alphabetical sorting |
| uniq | Remove duplicates | uniq file.txt | Unique lines only |
| wc | Word count | wc -l file.txt | Count lines/words |
| find | Find files | find . -name "*.txt" | Locate files |
Error Handling {#error-handling}
Exit Status and Error Checking
`bash
#!/bin/bash
Function to check command success
check_command() { if [ $? -eq 0 ]; then echo "Command succeeded" else echo "Command failed with exit code $?" exit 1 fi }Error handling with conditional execution
mkdir test_directory && echo "Directory created successfully" || echo "Failed to create directory"Comprehensive error handling
safe_copy() { local source=$1 local destination=$2 # Check if source exists if [ ! -f "$source" ]; then echo "Error: Source file '$source' does not exist" >&2 return 1 fi # Check if destination directory exists local dest_dir=$(dirname "$destination") if [ ! -d "$dest_dir" ]; then echo "Error: Destination directory '$dest_dir' does not exist" >&2 return 1 fi # Perform copy with error checking if cp "$source" "$destination"; then echo "Successfully copied '$source' to '$destination'" return 0 else echo "Error: Failed to copy '$source' to '$destination'" >&2 return 1 fi }`Trap and Signal Handling
`bash
#!/bin/bash
Cleanup function
cleanup() { echo "Cleaning up temporary files..." rm -f /tmp/script_temp_* echo "Cleanup completed" exit 0 }Set trap for various signals
trap cleanup EXIT INT TERMCreate temporary file
temp_file="/tmp/script_temp_$" touch "$temp_file"echo "Script running... Press Ctrl+C to test cleanup" echo "Temporary file created: $temp_file"
Simulate some work
for i in {1..10}; do echo "Working... $i" sleep 1 doneecho "Script completed normally"
`
Error Logging
`bash
#!/bin/bash
Logging configuration
LOG_FILE="/var/log/myscript.log" ERROR_LOG="/var/log/myscript_error.log"Logging functions
log_info() { echo "$(date '+%Y-%m-%d %H:%M:%S') INFO: $1" | tee -a "$LOG_FILE" }log_error() { echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: $1" | tee -a "$ERROR_LOG" >&2 }
log_warning() { echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING: $1" | tee -a "$LOG_FILE" }
Usage example
process_file() { local file=$1 log_info "Starting to process file: $file" if [ ! -f "$file" ]; then log_error "File not found: $file" return 1 fi if [ ! -r "$file" ]; then log_warning "File is not readable: $file" return 1 fi # Process file local line_count=$(wc -l < "$file") log_info "File processed successfully. Lines: $line_count" return 0 }`Advanced Topics {#advanced-topics}
Arrays
`bash
#!/bin/bash
Indexed arrays
fruits=("apple" "banana" "orange" "grape")Accessing array elements
echo "First fruit: ${fruits[0]}" echo "All fruits: ${fruits[@]}" echo "Number of fruits: ${#fruits[@]}"Adding elements
fruits+=("mango") fruits[5]="pineapple"Iterating over array
for i in "${!fruits[@]}"; do echo "Index $i: ${fruits[i]}" doneAssociative arrays (Bash 4+)
declare -A person person["name"]="John Doe" person["age"]="30" person["city"]="New York"Accessing associative array
echo "Name: ${person["name"]}" echo "Age: ${person["age"]}"Iterating over associative array
for key in "${!person[@]}"; do echo "$key: ${person[$key]}" done`String Manipulation
`bash
#!/bin/bash
text="Hello World Programming"
String length
echo "Length: ${#text}"Substring extraction
echo "Substring: ${text:0:5}" # Hello echo "Substring: ${text:6:5}" # WorldPattern matching and replacement
echo "Replace first: ${text/o/0}" # Hell0 World Programming echo "Replace all: ${text//o/0}" # Hell0 W0rld Pr0gramming echo "Remove from beginning: ${text#Hello }" # World Programming echo "Remove from end: ${text% Programming}" # Hello WorldCase conversion (Bash 4+)
echo "Uppercase: ${text^^}" echo "Lowercase: ${text,,}" echo "Title case: ${text^}"`Command Line Arguments Processing
`bash
#!/bin/bash
Advanced argument processing
usage() { echo "Usage: $0 [-h] [-v] [-f file] [-o output] [input_files...]" echo " -h Show this help message" echo " -v Verbose mode" echo " -f file Input file" echo " -o output Output file" exit 1 }Default values
VERBOSE=false INPUT_FILE="" OUTPUT_FILE=""Process command line options
while getopts "hvf:o:" opt; do case $opt in h) usage ;; v) VERBOSE=true ;; f) INPUT_FILE="$OPTARG" ;; o) OUTPUT_FILE="$OPTARG" ;; \?) echo "Invalid option: -$OPTARG" >&2 usage ;; :) echo "Option -$OPTARG requires an argument" >&2 usage ;; esac doneShift processed options
shift $((OPTIND-1))Remaining arguments
REMAINING_ARGS=("$@")Display configuration
if [ "$VERBOSE" = true ]; then echo "Verbose mode enabled" echo "Input file: ${INPUT_FILE:-"None"}" echo "Output file: ${OUTPUT_FILE:-"None"}" echo "Remaining arguments: ${REMAINING_ARGS[*]}" fi`Best Practices {#best-practices}
Script Template
`bash
#!/bin/bash
#===============================================================================
Script Name: script_template.sh
Description: Template for shell scripts with best practices
Author: Your Name
Version: 1.0
Date: $(date +%Y-%m-%d)
#===============================================================================Exit on any error
set -eExit on undefined variable
set -uExit on pipe failure
set -o pipefailGlobal variables
readonly SCRIPT_NAME=$(basename "$0") readonly SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) readonly LOG_FILE="/tmp/${SCRIPT_NAME%.sh}.log"Color codes for output
readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly NC='\033[0m' # No ColorLogging functions
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE" }error() { echo -e "${RED}ERROR: $*${NC}" >&2 log "ERROR: $*" }
warning() { echo -e "${YELLOW}WARNING: $*${NC}" >&2 log "WARNING: $*" }
info() { echo -e "${GREEN}INFO: $*${NC}" log "INFO: $*" }
Cleanup function
cleanup() { log "Script execution completed" # Add cleanup tasks here }Set trap for cleanup
trap cleanup EXITMain function
main() { info "Starting script execution" # Script logic here info "Script completed successfully" }Run main function with all arguments
main "$@"`Code Style Guidelines
| Practice | Good Example | Bad Example | Reason |
|----------|--------------|-------------|---------|
| Variable naming | user_name="john" | un="john" | Descriptive names |
| Quoting variables | echo "$variable" | echo $variable | Prevents word splitting |
| Function naming | calculate_total() | calc() | Clear purpose |
| Error checking | if command; then... | command | Handle failures |
| Constants | readonly PI=3.14 | PI=3.14 | Prevent modification |
Security Considerations
`bash
#!/bin/bash