Unix Shell Scripting Tutorial: Beginner to Advanced
| | | | | | | | | | | | | | | | | | | | | | | | | | | |
| |_| | |_| | |_| | |_| | |_| | |_| | |_| | |_| | |_| |
| _ | _ | _ | _ | _ | _ | _ | _ | _ |
|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|
H I M A N S H U
Created by funoracleapps.com
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |_| | |_| | |_| | |_| | |_| | |_| | |_| | |_| | |_| | | _ | _ | _ | _ | _ | _ | _ | _ | _ | |_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_| H I M A N S H U
Welcome to this comprehensive tutorial on Unix Shell Scripting! Whether you're a complete novice looking to automate repetitive tasks or an experienced user aiming to deepen your understanding and write more robust scripts, this guide will take you from the basics to advanced concepts.
Shell scripting is a powerful skill that allows you to automate tasks, manage files, process data, and interact with your operating system efficiently. It's an essential tool for system administrators, developers, and anyone who regularly works with Unix-like systems (Linux, macOS, etc.).
Let's dive in and unlock the power of the command line!
Part 1: Beginner Level - Getting Started with Shell Scripting
1. What is a Shell?
A shell is a command-line interpreter that provides a user interface for accessing an operating system's services. It's the program where you type commands, and it executes them. Common shells include Bash (Bourne Again SHell), Zsh, Ksh, and Csh. Bash is the most widely used and will be the primary focus of this tutorial.
2. Basic Shell Commands (Quick Review)
Before diving into scripting, it's beneficial to be familiar with some fundamental commands you'll often use:
ls
: List directory contentscd
: Change directorypwd
: Print working directorymkdir
: Make directoryrmdir
: Remove empty directorycp
: Copy files or directoriesmv
: Move or rename files or directoriesrm
: Remove files or directoriescat
: Catenate and display file contentecho
: Display a line of textman <command>
: Get manual pages for a command (e.g.,man ls
forls
command details)
3. Your First Script: "Hello World"
A shell script is simply a text file containing a series of shell commands that are executed sequentially.
#!/bin/bash
# This is a comment: The line above is called a 'shebang'.
# It tells the system which interpreter to use for this script.
echo "Hello, World from funoracleapps.com!" # This command prints the string to the terminal
Understanding the Shebang Line (#!
):
#!
is famously known as the "shebang" or "hash-bang."- It must be the very first line of your script.
- Its purpose is to tell the operating system which interpreter program to use to execute the script.
#!/bin/bash
specifically means "use the Bash interpreter located at/bin/bash
."
4. Executing Your Script
To run your newly created script:
- Save the file: Save the "Hello World" script as
hello.sh
(the.sh
extension is a common convention but not strictly mandatory for execution). - Give execute permissions: Before running, you need to make the script executable.
chmod +x hello.sh
This command adds execute (
x
) permission for the owner, group, and others. - Run the script:
./hello.sh
The
./
prefix tells the shell to look for and execute the script in the current directory.
5. Variables
Variables are fundamental for storing data within your scripts, allowing for dynamic behavior.
User-Defined Variables
- Syntax:
VARIABLE_NAME=value
- Important: There must be no spaces around the
=
sign.
- Naming Convention: Variable names are typically written in
UPPERCASE
by convention for shell scripts. - Accessing Values: To retrieve the value stored in a variable, use a
$
prefix: $VARIABLE_NAME
or ${VARIABLE_NAME}
. The curly braces are optional but recommended for clarity, especially when concatenating with other strings.
#!/bin/bash
# Define some variables
NAME="Alice"
AGE=30
CITY="New York"
echo "Hello, $NAME from $CITY!"
echo "$NAME is $AGE years old."
# Changing a variable's value
NAME="Bob"
echo "Now, hello, $NAME! Age is still $AGE."
VARIABLE_NAME=value
- Important: There must be no spaces around the
=
sign.
UPPERCASE
by convention for shell scripts.$
prefix: $VARIABLE_NAME
or ${VARIABLE_NAME}
. The curly braces are optional but recommended for clarity, especially when concatenating with other strings.#!/bin/bash
# Define some variables
NAME="Alice"
AGE=30
CITY="New York"
echo "Hello, $NAME from $CITY!"
echo "$NAME is $AGE years old."
# Changing a variable's value
NAME="Bob"
echo "Now, hello, $NAME! Age is still $AGE."
System-Defined Variables (Environment Variables)
These are special, predefined variables that the shell and operating system automatically set and use. They provide information about the current environment.
#!/bin/bash
echo "Your current user is: $USER"
echo "Your home directory is: $HOME"
echo "The shell you are using is: $SHELL"
echo "Your current working directory is: $PWD"
echo "The path for executable commands is: $PATH"
echo "The last command's exit status was: $?" # Special variable for exit status
6. Input and Output
echo
: As seen, echo
is used for displaying text or variable values to the standard output (your terminal).read
: The read
command is used to take input from the user (from standard input) and store it into a specified variable.
#!/bin/bash
echo "What is your name?"
read USER_NAME # Reads input from the user and stores it in the USER_NAME variable
echo "Hello, $USER_NAME! How are you today?"
echo "Enter your favorite number:"
read FAV_NUMBER
echo "You entered: $FAV_NUMBER. That's a great choice!"
echo
: As seen, echo
is used for displaying text or variable values to the standard output (your terminal).read
: The read
command is used to take input from the user (from standard input) and store it into a specified variable.#!/bin/bash
echo "What is your name?"
read USER_NAME # Reads input from the user and stores it in the USER_NAME variable
echo "Hello, $USER_NAME! How are you today?"
echo "Enter your favorite number:"
read FAV_NUMBER
echo "You entered: $FAV_NUMBER. That's a great choice!"
7. Basic Operators
Shell scripting supports various types of operations, including arithmetic calculations and comparisons.
Arithmetic Operations
For arithmetic calculations in Bash, the ((...))
construct is generally preferred. It's more intuitive and powerful than the older expr
command.
#!/bin/bash
NUM1=10
NUM2=5
echo "--- Arithmetic Operations ---"
# Addition
SUM=$((NUM1 + NUM2))
echo "Sum of $NUM1 and $NUM2: $SUM" # Output: Sum: 15
# Subtraction
DIFFERENCE=$((NUM1 - NUM2))
echo "Difference of $NUM1 and $NUM2: $DIFFERENCE" # Output: Difference: 5
# Multiplication
PRODUCT=$((NUM1 * NUM2))
echo "Product of $NUM1 and $NUM2: $PRODUCT" # Output: Product: 50
# Division
QUOTIENT=$((NUM1 / NUM2))
echo "Quotient of $NUM1 and $NUM2: $QUOTIENT" # Output: Quotient: 2 (Integer division)
# Modulo (remainder)
REMAINDER=$((NUM1 % NUM2))
echo "Remainder of $NUM1 divided by $NUM2: $REMAINDER" # Output: Remainder: 0
# Increment/Decrement (can be used directly within ((...)))
COUNT=1
((COUNT++)) # Increment COUNT by 1
echo "Count after increment: $COUNT" # Output: Count after increment: 2
((COUNT--)) # Decrement COUNT by 1
echo "Count after decrement: $COUNT" # Output: Count after decrement: 1
Comparison Operators (for `if` statements)
These operators are used to compare values, typically within conditional expressions like if
statements. They are often used within [[ ... ]]
or [ ... ]
.
Numerical Comparisons:
-eq
: equal to-ne
: not equal to-gt
: greater than-ge
: greater than or equal to-lt
: less than-le
: less than or equal to
#!/bin/bash
A=10
B=20
echo "--- Numerical Comparisons ---"
if (( A < B )); then # Using arithmetic context for numerical comparison
echo "$A is less than $B"
fi
if [[ "$A" -eq "$B" ]]; then # Using -eq for numerical comparison
echo "$A is equal to $B"
else
echo "$A is not equal to $B"
fi
echo "Enter two numbers for comparison:"
read NUM_X
read NUM_Y
if [[ "$NUM_X" -gt "$NUM_Y" ]]; then
echo "$NUM_X is greater than $NUM_Y"
elif [[ "$NUM_X" -lt "$NUM_Y" ]]; then
echo "$NUM_X is less than $NUM_Y"
else
echo "$NUM_X is equal to $NUM_Y"
fi
Part 2: Intermediate Level - Adding Logic and Structure
This section delves into control flow, functions, and argument handling, allowing your scripts to perform more complex tasks.
1. Control Flow
Control flow statements enable your script to make decisions, repeat actions, and execute commands conditionally.
`if-else` Statements
The if-else
construct allows your script to execute different blocks of code based on whether a condition is true or false.
#!/bin/bash
echo "Enter a number:"
read NUMBER
if (( NUMBER > 0 )); then
echo "$NUMBER is positive."
elif (( NUMBER < 0 )); then # 'elif' is short for 'else if'
echo "$NUMBER is negative."
else
echo "$NUMBER is zero."
fi
echo "--- String Comparison Example ---"
echo "Enter your favorite color:"
read COLOR
if [[ "$COLOR" == "blue" ]]; then # String equality check using ==
echo "Blue is a great color!"
elif [[ "$COLOR" == "green" ]]; then
echo "Green is nice too."
else
echo "Interesting color choice: $COLOR."
fi
Note on [[ ... ]]
vs. [ ... ]
:
[[ ... ]]
is a Bash-specific extended test command. It's generally preferred because it offers more robust string comparisons (e.g., without needing quotes for variables in some cases, though quoting is still a good habit) and supports regular expression matching (=~
).[ ... ]
is the traditionaltest
command, available in all POSIX-compliant shells. It's more portable but less flexible. Always quote variables within[ ... ]
to prevent issues with empty or multi-word strings.
`case` Statements
The case
statement is useful for handling multiple choice scenarios, providing a cleaner alternative to nested if-elif-else
structures when dealing with a single variable's value.
#!/bin/bash
echo "--- Menu Options ---"
echo "1. List files"
echo "2. Create a new directory"
echo "3. Display current date"
echo "4. Exit"
echo "Enter your choice (1-4):"
read CHOICE
case $CHOICE in
1)
echo "Listing files in current directory:"
ls -lh
;; # Double semicolon (;;) terminates a case block
2)
echo "Enter directory name to create:"
read DIR_NAME
mkdir -p "$DIR_NAME" # -p creates parent directories if they don't exist
echo "Directory '$DIR_NAME' created."
;;
3)
echo "Current date and time: $(date)"
;;
4)
echo "Exiting script. Goodbye!"
exit 0 # Exit successfully
;;
*) # Default case: acts like an 'else' for any other input
echo "Invalid option. Please choose a number between 1 and 4."
;;
esac
`for` Loops
for
loops allow you to iterate over a list of items or a defined range of numbers, executing a block of commands for each item.
#!/bin/bash
echo "--- Looping through a list of strings ---"
for FRUIT in "Apple" "Banana" "Cherry"; do
echo "I really enjoy eating $FRUIT."
done
echo "--- Counting from 1 to 5 ---"
for i in {1..5}; do # Brace expansion for number ranges
echo "Current number: $i"
done
echo "--- Processing files in a directory ---"
# Create some dummy files for demonstration
touch file1.txt report.pdf image.jpg data.txt
for FILE in *.txt; do # Loops through all files ending with .txt in the current directory
if [[ -f "$FILE" ]]; then # Check if it's a regular file (not a directory or broken link)
echo "Processing text file: $FILE"
cat "$FILE" # Example: display content of the file
fi
done
# Clean up dummy files
rm -f file1.txt report.pdf image.jpg data.txt
`while` Loops
while
loops execute a block of commands repeatedly as long as a specified condition remains true.
#!/bin/bash
COUNT=1
echo "--- Counting up to 3 ---"
while (( COUNT <= 3 )); do
echo "Count: $COUNT"
((COUNT++)) # Increment COUNT
done
echo "--- User Input Loop ---"
echo "Type 'quit' to exit the loop."
while true; do # Loop indefinitely until 'break' is encountered
read -p "Your input: " INPUT # -p for prompt
if [[ "$INPUT" == "quit" ]]; then
echo "Exiting loop as requested."
break # Exit the loop
else
echo "You typed: '$INPUT'"
fi
done
2. Functions
Functions allow you to group commands into reusable blocks of code. This improves script organization, readability, and maintainability.
#!/bin/bash
# Define a simple function without arguments
greet_user() {
echo "Hello there! This is a function call."
}
# Define a function with arguments
# Arguments are accessed using $1, $2, etc.
greet_name() {
echo "Hello, $1! Welcome to the script from funoracleapps.com."
}
# Define a function with multiple arguments and demonstrating return value (exit status)
add_numbers() {
local NUM1=$1 # 'local' keyword makes variable local to the function, preventing global conflicts
local NUM2=$2
local SUM=$((NUM1 + NUM2))
echo "The sum of $NUM1 and $NUM2 is $SUM."
return $SUM # Functions return an exit status (0 for success, non-zero for error).
# To return a data value, print it and capture with command substitution.
}
# --- Calling the functions ---
greet_user
greet_name "Charlie"
# Call add_numbers and capture its output (the sum)
# The `result=$(...)` syntax is called command substitution.
calculated_sum=$(add_numbers 15 25)
echo "Function output (captured sum): $calculated_sum"
# Call add_numbers again and check its *exit status*
add_numbers 5 7
echo "Function exited with status: $?" # The 'return $SUM' sets the exit status.
# Note: If SUM > 255, it will wrap around.
# For returning data, echoing and capturing is the standard.
3. Command Line Arguments
Your scripts can accept input directly when they are executed, known as command-line arguments.
$0
: The name of the script itself.$1
,$2
,$3
, ...: Individual arguments, positional parameters.$#
: The number of arguments passed to the script.$@
: All arguments as separate strings. This is generally the best choice when iterating through arguments.$*
: All arguments as a single string.
#!/bin/bash
echo "--- Command Line Arguments ---"
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Number of arguments received: $#"
echo "All arguments (using \$@, iterated):"
for ARG in "$@"; do
echo "- Argument: '$ARG'"
done
echo "All arguments (using \$*): '$*'"
# Example: Check if enough arguments are provided
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <arg1> <arg2>"
exit 1 # Exit with error
fi
echo "Processing arguments: $1 and $2"
How to run this script:
./my_script.sh first_value "second value with spaces" 123
4. Exit Status (`$?`)
Every command executed in Unix (including your own script commands) returns an exit status (also known as a return code or exit code). This is a crucial mechanism for error checking.
0
: Indicates success (conventionally).Non-zero
(1-255): Indicates an error or failure. Different non-zero codes can signify different types of errors.
#!/bin/bash
echo "--- Checking Exit Status ---"
# This command should succeed
ls /etc/passwd
echo "Exit status of 'ls /etc/passwd': $?" # Will typically be 0
echo ""
# This command should fail (assuming /nonexistent_file doesn't exist)
ls /nonexistent_file
echo "Exit status of 'ls /nonexistent_file': $?" # Will typically be non-zero (e.g., 1 or 2)
echo ""
# You can use exit status directly in 'if' statements
if cp /etc/hosts /tmp/hosts_copy; then
echo "Successfully copied /etc/hosts to /tmp/hosts_copy."
else
echo "Failed to copy /etc/hosts."
fi
# Clean up
rm -f /tmp/hosts_copy
5. Redirection
Redirection allows you to change the default input and output streams of commands.
>
: Redirects standard output (stdout) to a file. Overwrites the file if it already exists.echo "Hello from funoracleapps.com" > welcome.txt # Creates/overwrites welcome.txt
>>
: Redirects standard output (stdout) to a file. Appends to the file if it already exists.echo "This is a new line." >> welcome.txt # Appends to welcome.txt
<
: Redirects standard input (stdin) from a file.cat < welcome.txt # Reads from welcome.txt and displays its content
2>
: Redirects standard error (stderr) to a file.ls /nonexistent_dir 2> error.log # Error messages go to error.log, not the terminal
&>
: Redirects both stdout and stderr to a file. (Bash-specific, often preferred for simplicity).ls /etc/passwd /nonexistent_dir &> combined.log # Both output and errors go to combined.log
2>&1
: Redirects stderr to the same location as stdout.ls /etc/passwd /nonexistent_dir > output_and_error.log 2>&1 # Both go to output_and_error.log
6. Pipes (`|`)
Pipes are incredibly powerful, allowing you to connect the standard output of one command to the standard input of another command. This creates command "pipelines."
#!/bin/bash
echo "--- Using Pipes ---"
# List files in long format, then sort them by file size (5th column, numeric)
echo "Files sorted by size:"
ls -l | sort -k 5n
echo ""
# Find all running processes, then filter for those containing 'bash'
echo "Bash processes:"
ps aux | grep bash
echo ""
# Get the content of a file, then count the number of lines
echo "Number of lines in /etc/passwd: $(cat /etc/passwd | wc -l)"
Part 3: Advanced Level - Robustness and Efficiency
This section focuses on making your shell scripts more robust, efficient, and professional by handling errors, using advanced text processing, and understanding system interactions.
1. Error Handling and Script Robustness
Professional scripts should be resilient to unexpected situations. Bash offers built-in options to help with this.
set -e
(Exit on Error):- If any command in the script fails (returns a non-zero exit status), the script will immediately terminate. This prevents the script from continuing with potentially invalid data or state after a critical step has failed.
#!/bin/bash set -e # Exit immediately if a command exits with a non-zero status. echo "Starting robust script..." mkdir my_temp_safe_dir echo "Directory 'my_temp_safe_dir' created." # The next command will fail if 'non_existent_file.txt' doesn't exist cp non_existent_file.txt my_temp_safe_dir/ # Script will exit here if this fails. echo "This line will NOT be executed if 'cp' fails above." rmdir my_temp_safe_dir # This cleanup will only run if cp succeeded. echo "Script finished successfully."
set -u
(Treat Unset Variables as Error):- If you try to use a variable that has not been defined or set, the script will exit with an error. This is excellent for catching typos in variable names.
#!/bin/bash set -u # Exit if an unset variable is used. echo "Hello, $MY_UNDEFINED_NAME!" # MY_UNDEFINED_NAME is not set, script will exit. echo "This line will NOT be executed."
set -o pipefail
(Fail on Pipe Error):- Normally, in a pipeline (
cmd1 | cmd2 | cmd3
), the exit status of the last command in the pipe is returned. This means ifcmd1
fails butcmd2
andcmd3
succeed, the overall pipeline might still report success, hiding the initial failure.pipefail
ensures that the pipeline's exit status is the rightmost non-zero exit status; if all commands succeed, it returns zero.
#!/bin/bash set -e set -o pipefail # Fail if any command in a pipe fails. echo "--- Testing pipefail ---" # 'false' command returns 1 (failure), 'cat' returns 0 (success). # Without pipefail, this pipeline would succeed because 'cat' is last. # With pipefail, it will fail because 'false' failed. false | cat # This command will now cause the script to exit due to 'false' failing. echo "This line will NOT be executed if pipefail is active and 'false' fails."
- Normally, in a pipeline (
- Combining them (Common Robust Script Header):
- For most professional scripts, it's a best practice to start with these three options combined.
#!/bin/bash set -euo pipefail # e: exit on error, u: unset variables are errors, o pipefail: fail on pipe errors echo "Robust script execution started." # ... your robust commands ... echo "Robust script execution finished successfully."
2. Regular Expressions (RegEx)
Regular expressions are powerful patterns used for searching, matching, and manipulating text. They are commonly used with text processing commands like grep
, sed
, and awk
.
grep
(Global Regular Expression Print): Used to search for lines containing a specific pattern in files.# Find lines containing 'error' (case-insensitive) in the system log grep -i "error" /var/log/syslog # Find lines that start with 'WARN' grep "^WARN" /var/log/messages # Find lines that end with a number (0-9) grep "[0-9]$" my_data.txt
sed
(Stream Editor): A non-interactive text editor used for transforming text, often for substitutions.# Replace the first occurrence of 'old_text' with 'new_text' on each line sed 's/old_text/new_text/' my_file.txt # Replace ALL occurrences of 'apple' with 'orange' on each line (g = global) sed 's/apple/orange/g' fruits.txt # Replace 'foo' with 'bar' and save changes *back to the original file* (-i = in-place) sed -i 's/foo/bar/g' config.ini # Delete lines containing the word 'temp' sed '/temp/d' logfile.txt
awk
(Pattern Scanning and Processing Language): A powerful tool for processing text files based on patterns, especially useful for columnar data.# Print the first and third columns of a CSV file (default delimiter is space/tab) awk '{print $1, $3}' data.csv # Sum the values in the second column and print the total at the end awk '{sum += $2} END {print "Total sum:", sum}' numbers.txt # Print lines where the first column is exactly 'User' awk '$1 == "User" {print}' users.txt # Use a different field separator (e.g., comma for CSV) awk -F',' '{print "Item:", $1, "Price:", $2}' products.csv
3. String Manipulation
Bash provides a rich set of built-in features for manipulating strings directly within your scripts, often more efficiently than external commands for simple tasks.
#!/bin/bash
MY_STRING=" Hello World Bash Scripting by funoracleapps.com "
echo "Original string: \"$MY_STRING\""
# Get string length
echo "Length of string: ${#MY_STRING}" # Output: 46 (includes leading/trailing spaces)
# Remove leading/trailing whitespace (using sed for robustness)
CLEAN_STRING=$(echo "$MY_STRING" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo "Cleaned string: \"$CLEAN_STRING\"" # Output: Hello World Bash Scripting by funoracleapps.com
# Extract substring (offset:length)
echo "Substring (6 chars from index 6): ${MY_STRING:6:6}" # Output: World
# Remove shortest matching prefix (#)
echo "Remove ' Hello ': ${MY_STRING# Hello }" # Output: World Bash Scripting by funoracleapps.com
# Remove longest matching prefix (##)
echo "Remove everything up to last space: ${MY_STRING##* }" # Output: funoracleapps.com
# Remove shortest matching suffix (%)
echo "Remove ' ': ${MY_STRING% }" # Output: Hello World Bash Scripting by funoracleapps.com
# Remove longest matching suffix (%%)
echo "Remove everything from first space: ${MY_STRING%% *}" # Output: Hello
# Replace first occurrence of a pattern
echo "Replace 'World' with 'Universe': ${MY_STRING/World/Universe}" # Output: Hello Universe Bash Scripting by funoracleapps.com
# Replace ALL occurrences of a pattern (//)
REPEATED_STRING="apple,banana,apple,orange"
echo "Replace all 'apple' with 'grape': ${REPEATED_STRING//apple/grape}" # Output: grape,banana,grape,orange
4. Arrays
Arrays allow you to store multiple values in a single variable, accessed by an index. Bash supports indexed arrays.
#!/bin/bash
# Define an indexed array
FRUITS=("Apple" "Banana" "Cherry" "Date")
echo "--- Array Operations ---"
# Access elements (arrays are 0-indexed)
echo "First fruit: ${FRUITS[0]}" # Output: Apple
echo "Third fruit: ${FRUITS[2]}" # Output: Cherry
# Get all elements
echo "All fruits: ${FRUITS[@]}" # Output: Apple Banana Cherry Date
# Get the number of elements
echo "Number of fruits: ${#FRUITS[@]}" # Output: 4
# Loop through array elements (always quote "${ARRAY_NAME[@]}" for safety)
echo "Listing fruits from array:"
for FRUIT in "${FRUITS[@]}"; do
echo "- $FRUIT"
done
# Add an element to the end of the array
FRUITS+=("Elderberry")
echo "After adding 'Elderberry': ${FRUITS[@]}"
# Remove an element by index
unset FRUITS[1] # Removes "Banana"
echo "After removing 'Banana': ${FRUITS[@]}" # Note: Index 1 is now empty/skipped, not re-indexed
# Accessing a non-existent element (will be empty)
echo "Element at index 1 (after unset): '${FRUITS[1]}'" # Output: '' (empty)
5. Debugging Scripts
When your scripts don't behave as expected, Bash provides useful options to help you debug them.
set -x
(Trace Execution):- This option prints each command and its arguments to standard error after they have been expanded (variables substituted, etc.) but before execution. It's incredibly useful for understanding the flow of your script and seeing exactly what commands are being run.
#!/bin/bash set -x # Enable tracing for the entire script VAR="test_value" echo "The variable VAR is: $VAR" ls -l /tmp/
- You can also enable tracing from the command line:
bash -x your_script.sh
set -n
(No Execution / Syntax Check):- This option reads commands but does not execute them. It's primarily used for syntax checking. If there's a syntax error, the script will report it without running any commands.
#!/bin/bash set -n # Read commands but do not execute them # This line has a syntax error (missing 'fi') if true; then echo "This won't run" # fi
- Run with
bash -n your_script.sh
6. Trapping Signals (`trap`)
The trap
command allows your script to catch and handle system signals (like Ctrl+C, script termination, etc.) or specific events (like exiting). This is crucial for performing cleanup operations.
#!/bin/bash
# Function to run on script exit (normal or abnormal)
cleanup_resources() {
echo "--- Initiating cleanup ---"
if [[ -f "/tmp/my_temp_file_created_by_script.txt" ]]; then
rm -f /tmp/my_temp_file_created_by_script.txt
echo "Removed temporary file: /tmp/my_temp_file_created_by_script.txt"
fi
echo "Cleanup complete."
}
# Trap the EXIT signal: 'cleanup_resources' function will run whenever the script exits.
trap cleanup_resources EXIT
# Trap the INT signal (Interrupt, typically from Ctrl+C):
# This will execute the specified command string, then exit with status 1.
trap 'echo "Ctrl+C detected! Performing graceful exit..."; cleanup_resources; exit 1' INT
echo "Script started. Creating a temporary file..."
touch /tmp/my_temp_file_created_by_script.txt
echo "Temporary file created. Waiting for 10 seconds (or Ctrl+C)..."
sleep 10 # Wait for 10 seconds
echo "Script finished normally."
To test:
- Run the script:
./your_script.sh
- While it's sleeping, press
Ctrl+C
. You should see the "Ctrl+C detected!" message and the cleanup function run. - Run it again and let it finish naturally. You'll see the "Script finished normally." message, and the cleanup function will still execute.
7. Here Documents (<<EOF
)
A "here document" (often called a "heredoc") is a way to provide multi-line input to a command directly within your script, without needing to create a separate temporary file.
#!/bin/bash
echo "--- Creating a multi-line file using a here document ---"
# The text between `EOF` and the closing `EOF` (can be any unique marker)
# will be redirected as standard input to the `cat` command,
# which then redirects its output to `my_report.txt`.
cat <<EOF > my_report.txt
This is an automated report generated by funoracleapps.com.
Current user: $USER
Current date: $(date)
This report contains important information.
EOF
echo "Content of 'my_report.txt':"
cat my_report.txt
echo ""
echo "--- Passing multi-line input directly to 'sort' command ---"
# The text between `END_LIST` and the closing `END_LIST` will be
# piped as input to the `sort -r` command (reverse sort).
sort -r <<END_LIST
banana
apple
cherry
date
grape
END_LIST
8. File Descriptors
In Unix, everything is treated as a file. Standard input, output, and error are special "file descriptors." You can manipulate these and even create your own.
0
: stdin (Standard Input) - where a command reads its input from (default: keyboard).1
: stdout (Standard Output) - where a command writes its normal output (default: terminal).2
: stderr (Standard Error) - where a command writes its error messages (default: terminal).
#!/bin/bash
echo "--- Custom File Descriptor Example ---"
# Open file descriptor 3 for writing to 'custom_log.txt'
exec 3> custom_log.txt
echo "This message goes to standard output (terminal)."
echo "This message goes to the custom log file." >&3 # Redirects this specific echo's output to FD 3
echo "This message also goes to standard output."
# Run a command and redirect its output to FD 3
echo "Listing /tmp directory to custom log:" >&3
ls -l /tmp >&3
# Close file descriptor 3
exec 3>&- # The '&-' closes the file descriptor
echo "After closing FD 3, this message goes to standard output again."
# Verify content of custom_log.txt
echo ""
echo "Content of 'custom_log.txt':"
cat custom_log.txt
# Clean up
rm -f custom_log.txt
9. Working with Dates and Times
The date
command is incredibly versatile for displaying and manipulating date and time information.
#!/bin/bash
echo "--- Date and Time Operations ---"
# Print current date and time (default format)
echo "Current date and time: $(date)"
# Format date as %Y-%m-%d_%H-%M-%S
echo "Formatted date: $(date +%Y-%m-%d_%H-%M-%S)"
# Get current Unix timestamp (seconds since epoch)
echo "Unix Timestamp: $(date +%s)"
# Calculate dates in the past/future (using GNU date, common on Linux)
echo "Yesterday's date: $(date -d "yesterday" +%Y-%m-%d)"
echo "Date next week: $(date -d "+1 week" +%Y-%m-%d)"
echo "Date 3 days ago: $(date -d "3 days ago" +%Y-%m-%d)"
# Get only the current year
echo "Current year: $(date +%Y)"
10. Basic System Administration Examples
Shell scripts are indispensable for automating routine system administration tasks.
- Check Disk Space for Root Partition:
#!/bin/bash echo "--- Disk Usage for Root Partition ---" df -h /
- List Top Processes by CPU Usage:
#!/bin/bash echo "--- Top 5 CPU Consuming Processes ---" # ps aux: lists all processes # --sort=-%cpu: sorts by CPU usage in descending order # head -n 6: takes the header line and the top 5 processes ps aux --sort=-%cpu | head -n 6
- Monitor Log File for Specific Errors:
#!/bin/bash LOG_FILE="/var/log/syslog" # Adjust this to your specific log file (e.g., /var/log/auth.log, /var/log/nginx/error.log) echo "--- Monitoring '$LOG_FILE' for 'error' messages (Press Ctrl+C to stop) ---" # tail -f: continuously outputs new lines added to the file # grep -i "error": filters for lines containing 'error' (case-insensitive) tail -f "$LOG_FILE" | grep -i "error"
Part 4: Best Practices for Professional Shell Scripting
Writing good scripts goes beyond just making them work. Professional scripts are readable, maintainable, and robust.
- Add Comprehensive Comments:
- Explain the script's purpose at the beginning.
- Comment on complex logic, non-obvious commands, and variable usage.
- Use
#
for single-line comments.
- Use Meaningful Variable Names:
- Choose names that clearly indicate the variable's purpose (e.g.,
USER_NAME
instead ofu
). - Stick to a consistent naming convention (e.g.,
UPPER_SNAKE_CASE
for global variables).
- Choose names that clearly indicate the variable's purpose (e.g.,
- Quote Variables and Command Substitutions:
- Always enclose variables and command substitutions in double quotes (e.g.,
"$MY_VAR"
,"$(date)"
). This prevents issues with word splitting (when values contain spaces) and pathname expansion (globbing).
- Always enclose variables and command substitutions in double quotes (e.g.,
- Use
local
for Function Variables:- Declare variables inside functions with
local
(e.g.,local my_temp_var="value"
). This prevents them from accidentally interfering with global variables or variables in the calling scope, reducing bugs.
- Declare variables inside functions with
- Implement Robust Error Handling:
- Start your scripts with
set -euo pipefail
to automatically exit on errors, unset variables, or failed commands in pipelines. - Use
if
statements to explicitly check the exit status ($?
) of critical commands and provide informative error messages.
- Start your scripts with
- Validate Input and Arguments:
- If your script expects user input or command-line arguments, validate them (e.g., check if arguments are provided, if numbers are indeed numbers, if files exist). Provide clear usage instructions if validation fails.
- Keep it Readable and Maintainable:
- Use consistent indentation (e.g., 4 spaces or tabs).
- Add blank lines to logically group commands.
- Break down complex logic into smaller, well-named functions.
- Avoid overly long lines.
- Test Thoroughly:
- Test your scripts with various inputs, including valid, invalid, and edge cases.
- Test error conditions to ensure your error handling works as expected.
- Use the Correct Shebang (
#!/bin/bash
):- Always ensure the first line points to the correct interpreter for your script.
- Minimize
sudo
Usage:- If a script needs root privileges, it's generally safer and clearer to run the entire script with
sudo
(e.g.,sudo ./my_admin_script.sh
) rather than sprinklingsudo
throughout the script. This makes it explicit that the script requires elevated permissions.
- If a script needs root privileges, it's generally safer and clearer to run the entire script with
Conclusion
Congratulations! You've navigated a comprehensive journey through Unix shell scripting, from understanding the basics of shell interaction and writing your first "Hello World" script, to mastering control flow, functions, and advanced techniques for error handling and text manipulation. You've also learned crucial best practices to make your scripts professional, reliable, and easy to maintain.
Shell scripting is a skill best honed through practice. Start with small, everyday tasks that you find repetitive, and try to automate them. Experiment with the commands and concepts you've learned, and don't hesitate to consult man
pages, online documentation, or communities when you encounter challenges. The more you script, the more intuitive it becomes.
Embrace the power of automation and streamline your workflow!
Happy scripting from funoracleapps.com!
Post a Comment
Post a Comment