BASH (Bourne Again SHell) is the default shell on most Linux systems. It's both a command interpreter and a programming language.
bash --version # Or echo $BASH_VERSION # Common locations which bash # Usually: /bin/bash or /usr/bin/bash
#!/bin/bash # This is a comment # The first line is the "shebang" - tells the system which interpreter to use echo "Hello, World!" echo "This is my first BASH script"
# Create the script file cat > hello.sh <<'EOF' #!/bin/bash echo "Hello, World!" EOF # Make it executable chmod +x hello.sh # Run the script - three ways: ./hello.sh # Execute directly (requires chmod +x) bash hello.sh # Run with bash interpreter source hello.sh # Run in current shell (also: . hello.sh)
#!/bin/bash - Standard BASH#!/usr/bin/env bash - Portable (finds bash in PATH)#!/bin/sh - POSIX shell (more portable, fewer features)#!/bin/bash
#
# Script Name: example.sh
# Description: Brief description of what the script does
# Author: Your Name
# Date: 2025-10-31
# Version: 1.0
#
# Usage: ./example.sh [options] [arguments]
#
# Exit on error
set -e
# Variables
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR=$(dirname "$0")
# Functions go here
function main() {
echo "Main script logic"
}
# Main execution
main "$@"
#!/bin/bash
# Basic assignment (no spaces around =)
NAME="John"
AGE=30
PI=3.14159
# Using variables
echo "Name: $NAME"
echo "Age: $AGE"
echo "Pi: ${PI}"
# Command substitution
CURRENT_DATE=$(date)
USER_COUNT=`who | wc -l` # Old style backticks
echo "Date: $CURRENT_DATE"
echo "Users logged in: $USER_COUNT"
# Read-only variables (constants)
readonly DB_HOST="localhost"
declare -r MAX_CONNECTIONS=100
# Unsetting variables
unset NAME
#!/bin/bash
# Integer
declare -i NUMBER=42
NUMBER=NUMBER+10 # Arithmetic without $(())
echo $NUMBER # Prints: 52
# Array
declare -a FRUITS=("apple" "banana" "orange")
# Associative array (hash/dictionary)
declare -A CAPITALS
CAPITALS[USA]="Washington"
CAPITALS[UK]="London"
# Uppercase
declare -u UPPERCASE="hello"
echo $UPPERCASE # Prints: HELLO
# Lowercase
declare -l LOWERCASE="WORLD"
echo $LOWERCASE # Prints: world
# Read-only
declare -r CONSTANT="value"
# Export (make available to child processes)
declare -x EXPORTED_VAR="exported"
| Variable | Description | Example |
|---|---|---|
$0 |
Script name | ./script.sh |
$1, $2, ... |
Positional parameters | First, second argument, etc. |
$# |
Number of arguments | 3 |
$@ |
All arguments (as separate words) | "arg1" "arg2" "arg3" |
$* |
All arguments (as single word) | "arg1 arg2 arg3" |
$? |
Exit status of last command | 0 (success) or non-zero (error) |
$$ |
Process ID (PID) of script | 12345 |
$! |
PID of last background command | 12346 |
$_ |
Last argument of previous command | filename.txt |
$HOME |
Home directory | /home/user |
$USER |
Current username | john |
$HOSTNAME |
Computer name | server01 |
$RANDOM |
Random number (0-32767) | 15234 |
$LINENO |
Current line number in script | 42 |
#!/bin/bash # View all environment variables printenv # or env # Common environment variables echo "Path: $PATH" echo "Shell: $SHELL" echo "Home: $HOME" echo "User: $USER" echo "PWD: $PWD" # Set environment variable for child processes export MY_VAR="value" # Set for this script only MY_LOCAL_VAR="local value"
#!/bin/bash
# Simple argument access
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
# Check if arguments provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <arg1> <arg2>"
exit 1
fi
#!/bin/bash
# Shift moves arguments down
echo "First arg: $1"
shift
echo "Now first arg (was second): $1"
shift
echo "Now first arg (was third): $1"
# Process all arguments with shift
while [ $# -gt 0 ]; do
echo "Processing: $1"
shift
done
#!/bin/bash
# Parse command line options
VERBOSE=false
OUTPUT_FILE=""
while getopts "vo:h" opt; do
case $opt in
v)
VERBOSE=true
;;
o)
OUTPUT_FILE="$OPTARG"
;;
h)
echo "Usage: $0 [-v] [-o output_file]"
echo " -v Verbose mode"
echo " -o output_file Specify output file"
echo " -h Show this help"
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument" >&2
exit 1
;;
esac
done
# Shift to get non-option arguments
shift $((OPTIND-1))
if [ "$VERBOSE" = true ]; then
echo "Verbose mode enabled"
fi
if [ -n "$OUTPUT_FILE" ]; then
echo "Output file: $OUTPUT_FILE"
fi
echo "Remaining arguments: $@"
#!/bin/bash
# Long options support
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
echo "Help message"
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-c|--config)
CONFIG_FILE="$2"
shift 2
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
# Positional argument
POSITIONAL_ARGS+=("$1")
shift
;;
esac
done
# Restore positional parameters
set -- "${POSITIONAL_ARGS[@]}"
#!/bin/bash # Basic arithmetic - multiple methods A=10 B=3 # Method 1: $(( )) SUM=$((A + B)) DIFF=$((A - B)) PROD=$((A * B)) DIV=$((A / B)) MOD=$((A % B)) POWER=$((A ** 2)) echo "Sum: $SUM" echo "Difference: $DIFF" echo "Product: $PROD" echo "Division: $DIV" echo "Modulo: $MOD" echo "Power: $POWER" # Method 2: let let RESULT=A+B let A++ # Increment let B-- # Decrement let A+=5 # Add and assign # Method 3: expr (older, external command) RESULT=$(expr $A + $B) # Method 4: bc for floating point RESULT=$(echo "scale=2; 10/3" | bc) echo "10/3 = $RESULT"
| Numeric | String | Description |
|---|---|---|
-eq |
= or == |
Equal to |
-ne |
!= |
Not equal to |
-gt |
> |
Greater than |
-ge |
>= |
Greater than or equal |
-lt |
< |
Less than |
-le |
<= |
Less than or equal |
-z |
String is null (zero length) | |
-n |
String is not null |
#!/bin/bash
# Numeric comparisons
NUM1=10
NUM2=20
if [ $NUM1 -eq $NUM2 ]; then
echo "Equal"
fi
if [ $NUM1 -lt $NUM2 ]; then
echo "$NUM1 is less than $NUM2"
fi
# Modern syntax with (( ))
if (( NUM1 < NUM2 )); then
echo "$NUM1 is less than $NUM2"
fi
# String comparisons
STR1="hello"
STR2="world"
if [ "$STR1" = "$STR2" ]; then
echo "Strings are equal"
fi
if [ "$STR1" != "$STR2" ]; then
echo "Strings are different"
fi
# Check if string is empty
if [ -z "$STR1" ]; then
echo "String is empty"
fi
if [ -n "$STR1" ]; then
echo "String is not empty"
fi
#!/bin/bash
# AND operator: &&
if [ -f file.txt ] && [ -r file.txt ]; then
echo "File exists and is readable"
fi
# OR operator: ||
if [ "$USER" = "root" ] || [ "$UID" -eq 0 ]; then
echo "Running as root"
fi
# NOT operator: !
if [ ! -f file.txt ]; then
echo "File does not exist"
fi
# Combining with [[ ]]
if [[ -f file.txt && -r file.txt ]]; then
echo "File exists and is readable"
fi
if [[ "$USER" == "root" || "$UID" -eq 0 ]]; then
echo "Running as root"
fi
| Operator | Description |
|---|---|
-e file |
File exists |
-f file |
File exists and is a regular file |
-d file |
File exists and is a directory |
-L file |
File exists and is a symbolic link |
-r file |
File exists and is readable |
-w file |
File exists and is writable |
-x file |
File exists and is executable |
-s file |
File exists and has size > 0 |
-b file |
File is a block device |
-c file |
File is a character device |
file1 -nt file2 |
file1 is newer than file2 |
file1 -ot file2 |
file1 is older than file2 |
#!/bin/bash
# Basic if
if [ condition ]; then
echo "Condition is true"
fi
# If-else
if [ $# -eq 0 ]; then
echo "No arguments provided"
else
echo "Arguments provided: $@"
fi
# If-elif-else
HOUR=$(date +%H)
if [ $HOUR -lt 12 ]; then
echo "Good morning"
elif [ $HOUR -lt 18 ]; then
echo "Good afternoon"
else
echo "Good evening"
fi
# Nested if
if [ -f "$1" ]; then
if [ -r "$1" ]; then
echo "File is readable"
cat "$1"
else
echo "File exists but is not readable"
fi
else
echo "File does not exist"
fi
#!/bin/bash
# Single bracket (POSIX compliant)
if [ -f file.txt ]; then
echo "Using [ ]"
fi
# Double bracket (BASH extension - recommended)
if [[ -f file.txt ]]; then
echo "Using [[ ]]"
fi
# Double parentheses for arithmetic
if (( 5 > 3 )); then
echo "Using (( ))"
fi
# Test command (same as [ ])
if test -f file.txt; then
echo "Using test command"
fi
# Advantages of [[ ]]
# - No word splitting
# - Pattern matching
# - Regex support
# - Safer for string comparison
STRING="hello world"
if [[ $STRING == "hello"* ]]; then
echo "Pattern matches"
fi
if [[ $STRING =~ ^hello ]]; then
echo "Regex matches"
fi
#!/bin/bash
# Basic case statement
read -p "Enter a color (red/green/blue): " COLOR
case $COLOR in
red)
echo "You chose red"
;;
green)
echo "You chose green"
;;
blue)
echo "You chose blue"
;;
*)
echo "Unknown color"
;;
esac
# Multiple patterns
case $1 in
start|run|execute)
echo "Starting..."
;;
stop|halt|end)
echo "Stopping..."
;;
restart|reload)
echo "Restarting..."
;;
-h|--help)
echo "Help message"
;;
*)
echo "Unknown command: $1"
exit 1
;;
esac
# Pattern matching with case
FILE="$1"
case $FILE in
*.txt)
echo "Text file"
;;
*.jpg|*.png|*.gif)
echo "Image file"
;;
*.tar.gz|*.tgz)
echo "Compressed archive"
;;
*)
echo "Unknown file type"
;;
esac
#!/bin/bash
# Basic for loop
for i in 1 2 3 4 5; do
echo "Number: $i"
done
# Range with brace expansion
for i in {1..10}; do
echo "Number: $i"
done
# Range with step
for i in {0..20..2}; do
echo "Even number: $i"
done
# C-style for loop
for ((i=0; i<10; i++)); do
echo "Counter: $i"
done
# Iterate over files
for file in *.txt; do
echo "Processing: $file"
done
# Iterate over command output
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "User: $user"
done
# Iterate over array
FRUITS=("apple" "banana" "orange")
for fruit in "${FRUITS[@]}"; do
echo "Fruit: $fruit"
done
# Iterate over command line arguments
for arg in "$@"; do
echo "Argument: $arg"
done
#!/bin/bash
# Basic while loop
COUNTER=0
while [ $COUNTER -lt 5 ]; do
echo "Counter: $COUNTER"
((COUNTER++))
done
# Read file line by line
while IFS= read -r line; do
echo "Line: $line"
done < file.txt
# Infinite loop
while true; do
echo "Press Ctrl+C to stop"
sleep 1
done
# While with condition from command
while ps aux | grep -q "[m]yprocess"; do
echo "Process is running"
sleep 5
done
# Read from pipe
cat file.txt | while read line; do
echo "Processing: $line"
done
# While with multiple conditions
while [[ $COUNTER -lt 10 && $CONTINUE == "yes" ]]; do
echo "Counter: $COUNTER"
((COUNTER++))
read -p "Continue? (yes/no): " CONTINUE
done
#!/bin/bash
# Until loop (opposite of while)
COUNTER=0
until [ $COUNTER -ge 5 ]; do
echo "Counter: $COUNTER"
((COUNTER++))
done
# Wait until file exists
until [ -f /tmp/ready.flag ]; do
echo "Waiting for file..."
sleep 2
done
# Wait until service is ready
until curl -s http://localhost:8080 > /dev/null; do
echo "Waiting for service..."
sleep 5
done
#!/bin/bash
# Break - exit loop
for i in {1..10}; do
if [ $i -eq 5 ]; then
echo "Breaking at 5"
break
fi
echo "Number: $i"
done
# Continue - skip to next iteration
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue # Skip even numbers
fi
echo "Odd number: $i"
done
# Break out of nested loops
for i in {1..3}; do
for j in {1..3}; do
echo "$i,$j"
if [[ $i -eq 2 && $j -eq 2 ]]; then
break 2 # Break out of both loops
fi
done
done
#!/bin/bash
# Create a menu
PS3="Select an option: "
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"; do
case $opt in
"Option 1")
echo "You chose option 1"
;;
"Option 2")
echo "You chose option 2"
;;
"Option 3")
echo "You chose option 3"
;;
"Quit")
echo "Goodbye!"
break
;;
*)
echo "Invalid option"
;;
esac
done
#!/bin/bash
# Method 1: function keyword
function greet {
echo "Hello, World!"
}
# Method 2: Without function keyword (more portable)
greet2() {
echo "Hello again!"
}
# Call functions
greet
greet2
# Function with parameters
greet_user() {
local name=$1
local age=$2
echo "Hello, $name! You are $age years old."
}
greet_user "John" 30
# Function with return value
add_numbers() {
local sum=$(($1 + $2))
echo $sum # Output to stdout
}
result=$(add_numbers 5 3)
echo "Sum: $result"
# Return exit status
is_valid() {
if [ $1 -gt 0 ]; then
return 0 # Success
else
return 1 # Failure
fi
}
if is_valid 5; then
echo "Valid"
else
echo "Invalid"
fi
#!/bin/bash
# Global variable
GLOBAL_VAR="I am global"
my_function() {
# Local variable (only visible in function)
local LOCAL_VAR="I am local"
# Modify global variable
GLOBAL_VAR="Modified global"
echo "Inside function:"
echo " Local: $LOCAL_VAR"
echo " Global: $GLOBAL_VAR"
}
echo "Before function:"
echo " Global: $GLOBAL_VAR"
my_function
echo "After function:"
echo " Global: $GLOBAL_VAR"
# echo " Local: $LOCAL_VAR" # This would be empty
#!/bin/bash
print_args() {
echo "Function name: $FUNCNAME"
echo "Number of arguments: $#"
echo "All arguments: $@"
echo "All arguments (single string): $*"
echo "First argument: $1"
echo "Second argument: $2"
echo "PID of script: $$"
}
print_args one two three four
# Variable number of arguments
sum_all() {
local total=0
for num in "$@"; do
((total += num))
done
echo $total
}
result=$(sum_all 1 2 3 4 5)
echo "Sum of all: $result"
#!/bin/bash
# Factorial function
factorial() {
local n=$1
if [ $n -le 1 ]; then
echo 1
else
local prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
result=$(factorial 5)
echo "5! = $result"
# Fibonacci
fibonacci() {
local n=$1
if [ $n -le 1 ]; then
echo $n
else
local a=$(fibonacci $((n - 1)))
local b=$(fibonacci $((n - 2)))
echo $((a + b))
fi
}
echo "Fibonacci(10) = $(fibonacci 10)"
#!/bin/bash
# Declare array
declare -a FRUITS
# Initialize array - multiple ways
FRUITS=("apple" "banana" "orange")
COLORS=(red green blue yellow)
NUMBERS=(1 2 3 4 5)
# Add elements
FRUITS+=("grape")
FRUITS[4]="mango"
# Access elements
echo "First fruit: ${FRUITS[0]}"
echo "Third fruit: ${FRUITS[2]}"
echo "Last fruit: ${FRUITS[-1]}"
# All elements
echo "All fruits: ${FRUITS[@]}"
echo "All fruits: ${FRUITS[*]}"
# Number of elements
echo "Array length: ${#FRUITS[@]}"
# Length of specific element
echo "Length of first fruit: ${#FRUITS[0]}"
# Array indices
echo "Indices: ${!FRUITS[@]}"
# Loop through array
for fruit in "${FRUITS[@]}"; do
echo "Fruit: $fruit"
done
# Loop with indices
for i in "${!FRUITS[@]}"; do
echo "Index $i: ${FRUITS[$i]}"
done
# Slice array
echo "Elements 1-3: ${FRUITS[@]:1:3}"
# Remove element
unset FRUITS[2]
echo "After unset: ${FRUITS[@]}"
# Copy array
BACKUP=("${FRUITS[@]}")
# Clear array
unset FRUITS
#!/bin/bash
# Declare associative array
declare -A CAPITALS
# Initialize
CAPITALS=(
[USA]="Washington DC"
[UK]="London"
[France]="Paris"
[Japan]="Tokyo"
)
# Add elements
CAPITALS[Germany]="Berlin"
CAPITALS[Italy]="Rome"
# Access elements
echo "Capital of USA: ${CAPITALS[USA]}"
echo "Capital of Japan: ${CAPITALS[Japan]}"
# Check if key exists
if [[ -v CAPITALS[USA] ]]; then
echo "USA key exists"
fi
# All keys
echo "Countries: ${!CAPITALS[@]}"
# All values
echo "Capitals: ${CAPITALS[@]}"
# Number of elements
echo "Number of countries: ${#CAPITALS[@]}"
# Loop through associative array
for country in "${!CAPITALS[@]}"; do
echo "$country: ${CAPITALS[$country]}"
done
# Remove element
unset CAPITALS[UK]
#!/bin/bash
# Read file into array
mapfile -t LINES < file.txt
# or
readarray -t LINES < file.txt
# Split string into array
IFS=',' read -ra ITEMS <<< "apple,banana,orange"
echo "${ITEMS[@]}"
# Join array into string
FRUITS=("apple" "banana" "orange")
IFS=','
JOINED="${FRUITS[*]}"
echo "Joined: $JOINED"
# Sort array
NUMBERS=(5 2 8 1 9)
IFS=$'\n' SORTED=($(sort -n <<< "${NUMBERS[*]}"))
echo "Sorted: ${SORTED[@]}"
# Unique values
DUPLICATES=(1 2 2 3 3 3 4)
UNIQUE=($(printf "%s\n" "${DUPLICATES[@]}" | sort -u))
echo "Unique: ${UNIQUE[@]}"
# Search in array
FRUITS=("apple" "banana" "orange")
SEARCH="banana"
for fruit in "${FRUITS[@]}"; do
if [ "$fruit" = "$SEARCH" ]; then
echo "Found: $SEARCH"
break
fi
done
#!/bin/bash
STRING="Hello, World!"
echo "Length: ${#STRING}"
# Length of variable
NAME="John Doe"
echo "Name length: ${#NAME}"
#!/bin/bash
STRING="Hello, World!"
# Substring: ${string:position:length}
echo "${STRING:0:5}" # Hello
echo "${STRING:7}" # World!
echo "${STRING:7:5}" # World
echo "${STRING: -6}" # World! (space before minus is important)
echo "${STRING: -6:5}" # World
#!/bin/bash
STRING="Hello World, Hello Universe"
# Replace first occurrence
echo "${STRING/Hello/Hi}" # Hi World, Hello Universe
# Replace all occurrences
echo "${STRING//Hello/Hi}" # Hi World, Hi Universe
# Replace at beginning
echo "${STRING/#Hello/Hi}" # Hi World, Hello Universe
# Replace at end
echo "${STRING/%Universe/Galaxy}" # Hello World, Hello Galaxy
# Delete pattern
echo "${STRING//Hello/}" # World, Universe
#!/bin/bash
STRING="Hello World"
# To uppercase
echo "${STRING^^}" # HELLO WORLD
echo "${STRING^^[aeiou]}" # HEllO WOrld (specific chars)
# To lowercase
echo "${STRING,,}" # hello world
echo "${STRING,,[HW]}" # hello world (specific chars)
# Capitalize first letter
echo "${STRING^}" # Hello World
# Uncapitalize first letter
echo "${STRING,}" # hello World
#!/bin/bash
STRING=" Hello World "
# Trim leading whitespace
STRING="${STRING#"${STRING%%[![:space:]]*}"}"
# Trim trailing whitespace
STRING="${STRING%"${STRING##*[![:space:]]}"}"
# Or use external tools
STRING=$(echo " Hello World " | xargs)
echo "Trimmed: '$STRING'"
#!/bin/bash
FILENAME="example.tar.gz"
# Remove shortest match from beginning
echo "${FILENAME#*.}" # tar.gz
# Remove longest match from beginning
echo "${FILENAME##*.}" # gz
# Remove shortest match from end
echo "${FILENAME%.*}" # example.tar
# Remove longest match from end
echo "${FILENAME%%.*}" # example
# Extract file extension
EXT="${FILENAME##*.}"
echo "Extension: $EXT"
# Extract filename without extension
NAME="${FILENAME%.*}"
echo "Name: $NAME"
# Extract path
FULLPATH="/path/to/file.txt"
DIR="${FULLPATH%/*}"
FILE="${FULLPATH##*/}"
echo "Directory: $DIR"
echo "File: $FILE"
#!/bin/bash
# Concatenation
FIRST="Hello"
LAST="World"
FULL="$FIRST $LAST"
echo $FULL
# Append to string
STRING="Hello"
STRING="${STRING} World"
STRING+=" Again"
echo $STRING
# Split string
IFS=',' read -ra PARTS <<< "apple,banana,orange"
for part in "${PARTS[@]}"; do
echo "Part: $part"
done
# Join strings
PARTS=("apple" "banana" "orange")
IFS=',' JOINED="${PARTS[*]}"
echo "Joined: $JOINED"
#!/bin/bash
STR1="hello"
STR2="world"
# Equality
if [ "$STR1" = "$STR2" ]; then
echo "Strings are equal"
fi
# Inequality
if [ "$STR1" != "$STR2" ]; then
echo "Strings are different"
fi
# Lexicographic comparison
if [[[ "$STR1" < "$STR2" ]]; then
echo "$STR1 comes before $STR2"
fi
# Check if string contains substring
if [[ "$STR1" == *"ell"* ]]; then
echo "Contains 'ell'"
fi
# Check if string starts with
if [[ "$STR1" == "hel"* ]]; then
echo "Starts with 'hel'"
fi
# Check if string ends with
if [[ "$STR1" == *"lo" ]]; then
echo "Ends with 'lo'"
fi
#!/bin/bash # Print to stdout echo "Hello World" echo -n "No newline" echo -e "Escape sequences:\n\tTab and newline" # Printf (more control) printf "Name: %s, Age: %d\n" "John" 30 printf "%.2f\n" 3.14159 # Print to stderr echo "Error message" >&2 printf "Error: %s\n" "Something went wrong" >&2
#!/bin/bash
# Simple read
read -p "Enter your name: " NAME
echo "Hello, $NAME"
# Read with timeout
read -t 5 -p "Enter something (5 seconds): " INPUT
# Read password (no echo)
read -sp "Enter password: " PASSWORD
echo # New line after password
# Read into array
read -a WORDS <<< "word1 word2 word3"
echo "${WORDS[@]}"
# Read line by line
while IFS= read -r line; do
echo "Line: $line"
done < file.txt
# Read with custom delimiter
IFS=',' read -r NAME AGE CITY <<< "John,30,New York"
echo "Name: $NAME, Age: $AGE, City: $CITY"
#!/bin/bash
# Redirect stdout to file (overwrite)
echo "Hello" > output.txt
# Redirect stdout to file (append)
echo "World" >> output.txt
# Redirect stderr to file
command 2> error.log
# Redirect both stdout and stderr
command > output.txt 2>&1
# or (BASH 4+)
command &> output.txt
# Redirect stdout and stderr separately
command > output.txt 2> error.txt
# Discard output
command > /dev/null
command 2> /dev/null
command &> /dev/null
# Here document
cat << EOF > file.txt
Line 1
Line 2
Line 3
EOF
# Here document with variable expansion disabled
cat << 'EOF' > file.txt
$HOME will not be expanded
EOF
# Here string
grep "pattern" <<< "string to search"
# Read from file
while read line; do
echo $line
done < input.txt
# Pipe
cat file.txt | grep "pattern" | sort | uniq
#!/bin/bash # Standard file descriptors: # 0 = stdin # 1 = stdout # 2 = stderr # Redirect file descriptor exec 3> output.txt echo "Goes to fd 3" >&3 exec 3>&- # Close fd 3 # Save and restore stdout exec 3>&1 # Save stdout exec > log.txt # Redirect stdout echo "To log" exec 1>&3 # Restore stdout exec 3>&- # Close fd 3 # Swap stdout and stderr command 3>&1 1>&2 2>&3 # Copy file descriptor exec 3>&1 # fd 3 copies stdout
#!/bin/bash
# Compare output of two commands
diff <(ls dir1) <(ls dir2)
# Use command output as input file
while read line; do
echo "Line: $line"
done < <(cat file1 file2)
# Multiple inputs
paste <(seq 1 5) <(seq 6 10)
#!/bin/bash
FILE="/path/to/file.txt"
# Check if file exists
if [ -e "$FILE" ]; then
echo "File exists"
fi
# Check if regular file
if [ -f "$FILE" ]; then
echo "Is a regular file"
fi
# Check if directory
if [ -d "$FILE" ]; then
echo "Is a directory"
fi
# Check if readable
if [ -r "$FILE" ]; then
echo "File is readable"
fi
# Check if writable
if [ -w "$FILE" ]; then
echo "File is writable"
fi
# Check if executable
if [ -x "$FILE" ]; then
echo "File is executable"
fi
# Check if not empty
if [ -s "$FILE" ]; then
echo "File is not empty"
fi
# Compare file modification times
if [ file1.txt -nt file2.txt ]; then
echo "file1.txt is newer than file2.txt"
fi
#!/bin/bash
# Create file
touch newfile.txt
# Create multiple files
touch file{1..5}.txt
# Copy file
cp source.txt destination.txt
# Copy with backup
cp -b source.txt destination.txt
# Move/rename file
mv oldname.txt newname.txt
# Remove file
rm file.txt
# Remove multiple files
rm file1.txt file2.txt file3.txt
# Remove with pattern
rm *.tmp
# Remove directory and contents
rm -rf directory/
# Create directory
mkdir newdir
# Create nested directories
mkdir -p path/to/nested/dir
# Remove empty directory
rmdir emptydir
# Find files
find /path -name "*.txt"
find . -type f -mtime -7 # Modified in last 7 days
find . -type f -size +10M # Larger than 10MB
# Check file size
SIZE=$(stat -f%z "$FILE") # macOS
SIZE=$(stat -c%s "$FILE") # Linux
echo "File size: $SIZE bytes"
# Get file modification time
MTIME=$(stat -f%m "$FILE") # macOS
MTIME=$(stat -c%Y "$FILE") # Linux
#!/bin/bash
# Write to file
cat > file.txt << EOF
Line 1
Line 2
Line 3
EOF
# Append to file
cat >> file.txt << EOF
Line 4
Line 5
EOF
# Read entire file into variable
CONTENT=$(<file.txt)
echo "$CONTENT"
# Read file line by line
while IFS= read -r line; do
echo "Processing: $line"
done < file.txt
# Read file into array
mapfile -t LINES < file.txt
# Count lines
LINE_COUNT=$(wc -l < file.txt)
# Process CSV
while IFS=',' read -r col1 col2 col3; do
echo "Col1: $col1, Col2: $col2, Col3: $col3"
done < data.csv
#!/bin/bash # Change permissions (symbolic) chmod u+x script.sh # Add execute for user chmod g-w file.txt # Remove write for group chmod o+r file.txt # Add read for others chmod a+x script.sh # Add execute for all # Change permissions (numeric) chmod 755 script.sh # rwxr-xr-x chmod 644 file.txt # rw-r--r-- chmod 600 private.txt # rw------- # Change ownership chown user:group file.txt chown -R user:group directory/ # Get file permissions PERMS=$(stat -f%p file.txt) # macOS PERMS=$(stat -c%a file.txt) # Linux
#!/bin/bash # Create temporary file TMPFILE=$(mktemp) echo "data" > "$TMPFILE" # Use the file rm "$TMPFILE" # Create temporary directory TMPDIR=$(mktemp -d) # Use the directory rm -rf "$TMPDIR" # Automatic cleanup with trap TMPFILE=$(mktemp) trap "rm -f $TMPFILE" EXIT # File will be deleted when script exits # Create temp file in specific directory TMPFILE=$(mktemp /tmp/myapp.XXXXXX)
#!/bin/bash
# Run command and wait for completion
sleep 5
# Run command in background
sleep 10 &
BG_PID=$!
echo "Background process PID: $BG_PID"
# Wait for background process
wait $BG_PID
echo "Process completed with status: $?"
# Run multiple commands in background
command1 &
command2 &
command3 &
wait # Wait for all background jobs
# Check if process is running
if ps -p $BG_PID > /dev/null; then
echo "Process is running"
fi
# Kill process
kill $BG_PID
# or force kill
kill -9 $BG_PID
#!/bin/bash # Get current PID echo "My PID: $$" # Get parent PID echo "Parent PID: $PPID" # List all processes ps aux # Find process by name pgrep httpd pidof httpd # Get process info ps -p $$ -o pid,ppid,cmd,%cpu,%mem # Process tree pstree $$
#!/bin/bash # Start job in background sleep 100 & # List background jobs jobs # Bring job to foreground # fg %1 # Send job to background # bg %1 # Kill job # kill %1 # Disown job (detach from shell) # disown %1
#!/bin/bash
# Trap signals
trap "echo 'Caught SIGINT'; exit" SIGINT SIGTERM
# Trap exit
trap "echo 'Script exiting'; cleanup" EXIT
# Function to cleanup
cleanup() {
echo "Cleaning up..."
rm -f /tmp/myapp.*
kill $BG_PID 2>/dev/null
}
# Ignore signal
trap '' SIGINT
# Reset trap
trap - SIGINT
# Common signals:
# SIGINT (2) - Ctrl+C
# SIGTERM (15) - Termination request
# SIGKILL (9) - Force kill (cannot be trapped)
# SIGHUP (1) - Hangup
# SIGQUIT (3) - Ctrl+\
# Example: Prevent Ctrl+C during critical section
trap '' SIGINT
echo "Critical section - Ctrl+C disabled"
sleep 5
trap - SIGINT
echo "Ctrl+C enabled again"
#!/bin/bash
# Execute command and capture output
OUTPUT=$(ls -l)
echo "$OUTPUT"
# Execute command and capture exit status
ls /nonexistent > /dev/null 2>&1
STATUS=$?
echo "Exit status: $STATUS"
# Execute command only if previous succeeded
cd /tmp && ls -l
# Execute command only if previous failed
cd /nonexistent || echo "Failed to change directory"
# Always execute second command
cd /tmp ; ls -l
# Conditional execution chain
command1 && command2 && command3 || command4
# Subshell
(cd /tmp; ls -l) # Directory change doesn't affect parent shell
# Command group
{ cd /tmp; ls -l; } # Runs in current shell
#!/bin/bash
STRING="hello123world"
# Simple pattern matching
if [[ $STRING == *"123"* ]]; then
echo "Contains 123"
fi
# Regex matching
if [[ $STRING =~ [0-9]+ ]]; then
echo "Contains numbers"
echo "Matched: ${BASH_REMATCH[0]}"
fi
# Extract groups
EMAIL="user@example.com"
if [[ $EMAIL =~ ^([a-zA-Z0-9._-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})$ ]]; then
echo "Valid email"
echo "Username: ${BASH_REMATCH[1]}"
echo "Domain: ${BASH_REMATCH[2]}"
echo "TLD: ${BASH_REMATCH[3]}"
fi
#!/bin/bash # Basic grep grep "pattern" file.txt # Case-insensitive grep -i "pattern" file.txt # Invert match grep -v "pattern" file.txt # Count matches grep -c "pattern" file.txt # Show line numbers grep -n "pattern" file.txt # Recursive search grep -r "pattern" directory/ # Extended regex grep -E "pattern1|pattern2" file.txt # or egrep "pattern1|pattern2" file.txt # Perl regex grep -P "(?<=abc)def" file.txt # Multiple patterns grep -e "pattern1" -e "pattern2" file.txt # Match whole word grep -w "word" file.txt # Files with matches grep -l "pattern" *.txt # Context lines grep -A 2 "pattern" file.txt # 2 lines after grep -B 2 "pattern" file.txt # 2 lines before grep -C 2 "pattern" file.txt # 2 lines before and after
#!/bin/bash # Substitute sed 's/old/new/' file.txt # First occurrence sed 's/old/new/g' file.txt # All occurrences sed 's/old/new/2' file.txt # Second occurrence # Case-insensitive substitute sed 's/old/new/gi' file.txt # Delete lines sed '/pattern/d' file.txt # Delete matching lines sed '1d' file.txt # Delete first line sed '$d' file.txt # Delete last line sed '1,5d' file.txt # Delete lines 1-5 # Print specific lines sed -n '10p' file.txt # Print line 10 sed -n '10,20p' file.txt # Print lines 10-20 sed -n '/pattern/p' file.txt # Print matching lines # Multiple commands sed -e 's/old/new/g' -e 's/foo/bar/g' file.txt # or sed 's/old/new/g; s/foo/bar/g' file.txt # In-place editing sed -i 's/old/new/g' file.txt # Linux sed -i '' 's/old/new/g' file.txt # macOS # Insert/append lines sed '5i\New line before line 5' file.txt sed '5a\New line after line 5' file.txt # Change line sed '5c\Replacement for line 5' file.txt
#!/bin/bash
# Print specific columns
awk '{print $1, $3}' file.txt
# With custom delimiter
awk -F: '{print $1, $3}' /etc/passwd
# Pattern matching
awk '/pattern/ {print}' file.txt
awk '$3 > 100 {print $1, $3}' file.txt
# BEGIN and END blocks
awk 'BEGIN {print "Start"} {print} END {print "End"}' file.txt
# Sum column
awk '{sum += $3} END {print sum}' file.txt
# Calculate average
awk '{sum += $3; count++} END {print sum/count}' file.txt
# Multiple conditions
awk '$1 == "John" && $3 > 100 {print}' file.txt
# Print line numbers
awk '{print NR, $0}' file.txt
# Print number of fields
awk '{print NF}' file.txt
# Format output
awk '{printf "%-20s %10s\n", $1, $2}' file.txt
#!/bin/bash # Exit on error set -e # or set -o errexit # Exit on undefined variable set -u # or set -o nounset # Fail on pipe errors set -o pipefail # Print commands before execution set -x # or set -o xtrace # Combine options set -euo pipefail # Turn off set +e set +x
#!/bin/bash
# Check exit status
if command; then
echo "Success"
else
echo "Failed with status: $?"
fi
# Command || fallback
cd /nonexistent || { echo "Failed to cd"; exit 1; }
# Try-catch style
{
command1
command2
command3
} || {
echo "One of the commands failed"
exit 1
}
# Custom error function
error_exit() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Usage
[ -f file.txt ] || error_exit "File not found" 2
# Function with error handling
safe_command() {
if ! command "$@"; then
echo "Command failed: $*" >&2
return 1
fi
}
# Error on undefined variable access
set -u
# This will cause error:
# echo $UNDEFINED_VAR
#!/bin/bash
# Debug mode - entire script
bash -x script.sh
# Debug specific section
set -x
# code to debug
set +x
# Verbose mode
bash -v script.sh
# Check syntax without execution
bash -n script.sh
# Custom debug function
DEBUG=true
debug() {
if [ "$DEBUG" = true ]; then
echo "[DEBUG] $*" >&2
fi
}
debug "Variable value: $VAR"
# Print line numbers
PS4='Line $LINENO: '
set -x
# Trace function calls
set -o functrace
trap 'echo "Calling ${FUNCNAME[0]}"' DEBUG
#!/bin/bash
LOGFILE="/var/log/myscript.log"
log() {
local level=$1
shift
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOGFILE"
}
log "INFO" "Script started"
log "ERROR" "Something went wrong"
log "DEBUG" "Variable: $VAR"
# Log everything
exec > >(tee -a "$LOGFILE")
exec 2>&1
# Syslog
logger -t myscript "Log message"
# Log with levels
log_info() { log "INFO" "$@"; }
log_error() { log "ERROR" "$@"; }
log_debug() { log "DEBUG" "$@"; }
#!/usr/bin/env bash
#
# Script: example.sh
# Description: Brief description
# Author: Your Name
# Date: 2025-10-31
# Version: 1.0
#
# Usage: ./example.sh [options] arguments
#
set -euo pipefail
# Constants
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly VERSION="1.0.0"
# Default values
VERBOSE=false
DRY_RUN=false
# Functions
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] ARGUMENTS
Description of what this script does.
OPTIONS:
-h, --help Show this help message
-v, --verbose Verbose output
-n, --dry-run Dry run mode
-V, --version Show version
EXAMPLES:
$SCRIPT_NAME -v input.txt
$SCRIPT_NAME --dry-run file1 file2
EOF
exit 0
}
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}
error() {
log "ERROR: $*"
exit 1
}
# Parse arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-v|--verbose)
VERBOSE=true
shift
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-V|--version)
echo "$SCRIPT_NAME version $VERSION"
exit 0
;;
-*)
error "Unknown option: $1"
;;
*)
# Positional argument
break
;;
esac
done
}
# Main logic
main() {
parse_args "$@"
# Your code here
log "Script started"
if [ "$VERBOSE" = true ]; then
log "Verbose mode enabled"
fi
if [ "$DRY_RUN" = true ]; then
log "Dry run mode enabled"
fi
log "Script completed"
}
# Run main function
main "$@"
#!/usr/bin/env bash for portabilityset -euo pipefail"$VAR" not $VAR[[ ]] instead of [ ]$() instead of backticks#!/bin/bash
# ❌ BAD: Unquoted variables
rm -rf $DIR/* # Dangerous if DIR is empty
# ✓ GOOD: Quoted variables
rm -rf "${DIR:?DIR not set}"/*
# ❌ BAD: Not checking command existence
grep pattern file
# ✓ GOOD: Check command exists
if command -v grep >/dev/null 2>&1; then
grep pattern file
else
echo "grep not found" >&2
exit 1
fi
# ❌ BAD: Parsing ls output
for file in $(ls *.txt); do
echo "$file"
done
# ✓ GOOD: Use globbing
for file in *.txt; do
[ -f "$file" ] || continue
echo "$file"
done
# ❌ BAD: Using 'cd' without checking
cd /some/directory
ls -l
# ✓ GOOD: Check 'cd' success
cd /some/directory || exit 1
ls -l
# ❌ BAD: Word splitting issues
VAR="hello world"
echo $VAR # Prints: hello world (two arguments)
# ✓ GOOD: Preserve spaces
echo "$VAR" # Prints: hello world (one argument)
# ❌ BAD: Ignoring exit status
command
echo "Done"
# ✓ GOOD: Check exit status
if command; then
echo "Done"
else
echo "Failed" >&2
exit 1
fi
#!/bin/bash
# Use absolute paths or validate PATH
PATH="/usr/local/bin:/usr/bin:/bin"
# Validate input
validate_input() {
local input=$1
if [[ ! $input =~ ^[a-zA-Z0-9_-]+$ ]]; then
error "Invalid input: $input"
fi
}
# Don't trust user input in commands
# ❌ BAD
eval "echo $USER_INPUT"
# ✓ GOOD
echo "$USER_INPUT"
# Use temporary files safely
TMPFILE=$(mktemp) || error "Cannot create temp file"
trap "rm -f $TMPFILE" EXIT
# Check file before operations
if [ -f "$FILE" ] && [ -r "$FILE" ]; then
cat "$FILE"
fi
# Avoid running as root when possible
if [ "$EUID" -eq 0 ]; then
error "Do not run this script as root"
fi
# Sanitize filenames
sanitize_filename() {
echo "$1" | tr -cd '[:alnum:]._-'
}
#!/bin/bash
# Create named pipe
PIPE="/tmp/mypipe"
mkfifo "$PIPE"
# Writer (in background)
(
for i in {1..5}; do
echo "Message $i" > "$PIPE"
sleep 1
done
) &
# Reader
while read line; do
echo "Received: $line"
done < "$PIPE"
# Cleanup
rm "$PIPE"
#!/bin/bash
# Compare two command outputs
diff <(ls dir1) <(ls dir2)
# Read from multiple sources
while read line; do
echo "Line: $line"
done < <(cat file1 file2 file3)
# Tee to multiple files
echo "data" | tee >(process1) >(process2) > /dev/null
#!/bin/bash
# Start co-process
coproc bc -l
# Send commands
echo "10 + 20" >&${COPROC[1]}
read result <&${COPROC[0]}
echo "Result: $result"
# Close co-process
echo "quit" >&${COPROC[1]}
#!/bin/bash
# Using xargs
cat files.txt | xargs -P 4 -I {} process_file {}
# Using GNU parallel
parallel -j 4 process_file ::: file1 file2 file3 file4
# Manual parallel with background jobs
MAX_JOBS=4
job_count=0
for file in *.txt; do
process_file "$file" &
((job_count++))
if (( job_count >= MAX_JOBS )); then
wait -n # Wait for any job
((job_count--))
fi
done
wait # Wait for remaining jobs
#!/bin/bash
# Default values
echo "${VAR:-default}" # Use default if VAR is unset
echo "${VAR:=default}" # Assign default if VAR is unset
echo "${VAR:?error}" # Error if VAR is unset
echo "${VAR:+alternate}" # Use alternate if VAR is set
# Array operations
ARRAY=(one two three)
echo "${ARRAY[@]^}" # Capitalize first letter
echo "${ARRAY[@]^^}" # Uppercase all
echo "${ARRAY[@]/o/0}" # Replace o with 0
# Indirect expansion
VAR="VALUE"
REF="VAR"
echo "${!REF}" # Prints: VALUE
# Variable name list
declare -A HASH
HASH[key1]="value1"
echo "${!HASH[@]}" # Prints keys
#!/bin/bash
# Use built-ins instead of external commands
# ❌ Slow
LENGTH=$(echo "$STRING" | wc -c)
# ✓ Fast
LENGTH=${#STRING}
# Avoid unnecessary subshells
# ❌ Slow
result=$(cat file | grep pattern | wc -l)
# ✓ Fast
result=$(grep -c pattern file)
# Use [[ ]] instead of [ ]
# [[ ]] is a built-in and faster
# Read files efficiently
# ❌ Slow - creates subshell per line
cat file | while read line; do
echo "$line"
done
# ✓ Fast
while read line; do
echo "$line"
done < file
# Batch operations
# ❌ Slow
for file in *.txt; do
mv "$file" "$file.bak"
done
# ✓ Fast (if rename available)
rename 's/\.txt$/.txt.bak/' *.txt
#!/bin/bash
set -euo pipefail
# Configuration
SOURCE_DIR="/var/www"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
RETENTION_DAYS=7
# Create backup
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}
log "Starting backup of $SOURCE_DIR"
# Create backup directory if needed
mkdir -p "$BACKUP_DIR"
# Create archive
tar czf "${BACKUP_DIR}/${BACKUP_FILE}" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
# Verify backup
if [ -f "${BACKUP_DIR}/${BACKUP_FILE}" ]; then
SIZE=$(du -h "${BACKUP_DIR}/${BACKUP_FILE}" | cut -f1)
log "Backup completed: ${BACKUP_FILE} (${SIZE})"
else
log "ERROR: Backup failed!"
exit 1
fi
# Remove old backups
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "Removed backups older than $RETENTION_DAYS days"
log "Backup process completed"
#!/bin/bash
set -euo pipefail
LOGFILE="${1:-/var/log/apache2/access.log}"
echo "=== Log Analysis for $LOGFILE ==="
echo
# Total requests
TOTAL=$(wc -l < "$LOGFILE")
echo "Total requests: $TOTAL"
# Unique visitors (IP addresses)
UNIQUE_IPS=$(awk '{print $1}' "$LOGFILE" | sort -u | wc -l)
echo "Unique IPs: $UNIQUE_IPS"
# Top 10 IP addresses
echo
echo "Top 10 IP addresses:"
awk '{print $1}' "$LOGFILE" | sort | uniq -c | sort -rn | head -10
# Top 10 requested pages
echo
echo "Top 10 requested pages:"
awk '{print $7}' "$LOGFILE" | sort | uniq -c | sort -rn | head -10
# HTTP status codes
echo
echo "HTTP status codes:"
awk '{print $9}' "$LOGFILE" | sort | uniq -c | sort -rn
# Requests by hour
echo
echo "Requests by hour:"
awk '{print $4}' "$LOGFILE" | cut -d: -f2 | sort | uniq -c
# 404 errors
echo
echo "404 errors:"
awk '$9 == 404 {print $7}' "$LOGFILE" | sort | uniq -c | sort -rn | head -10
#!/bin/bash
set -euo pipefail
ALERT_THRESHOLD=80
check_disk_space() {
echo "=== Disk Space ==="
df -h | grep -v "tmpfs"
# Alert if over threshold
df -h | grep -v "tmpfs" | awk '{print $5}' | sed 's/%//' | while read usage; do
if [ "$usage" -gt "$ALERT_THRESHOLD" ]; then
echo "WARNING: Disk usage over ${ALERT_THRESHOLD}%"
fi
done
echo
}
check_memory() {
echo "=== Memory Usage ==="
free -h
echo
}
check_cpu() {
echo "=== CPU Load ==="
uptime
echo
}
check_services() {
echo "=== Service Status ==="
for service in httpd mysql sshd; do
if systemctl is-active --quiet "$service"; then
echo "$service: Running"
else
echo "$service: NOT RUNNING"
fi
done
echo
}
check_failed_logins() {
echo "=== Failed Login Attempts (last 10) ==="
grep "Failed password" /var/log/secure 2>/dev/null | tail -10 || echo "No failed logins found"
echo
}
# Run all checks
check_disk_space
check_memory
check_cpu
check_services
check_failed_logins
echo "Health check completed at $(date)"
#!/bin/bash
set -euo pipefail
# Configuration
APP_NAME="myapp"
APP_DIR="/var/www/${APP_NAME}"
REPO_URL="git@github.com:user/myapp.git"
BRANCH="${1:-main}"
BACKUP_DIR="/backup/${APP_NAME}"
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}
error() {
log "ERROR: $*"
exit 1
}
# Check if running as correct user
[ "$(whoami)" = "www-data" ] || error "Must run as www-data user"
log "Starting deployment of $APP_NAME (branch: $BRANCH)"
# Backup current version
log "Creating backup"
mkdir -p "$BACKUP_DIR"
tar czf "${BACKUP_DIR}/backup_$(date +%Y%m%d_%H%M%S).tar.gz" -C "$(dirname "$APP_DIR")" "$(basename "$APP_DIR")"
# Pull latest code
log "Pulling latest code"
cd "$APP_DIR"
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"
# Install dependencies
log "Installing dependencies"
if [ -f "package.json" ]; then
npm install --production
fi
if [ -f "requirements.txt" ]; then
pip install -r requirements.txt
fi
# Run migrations
log "Running migrations"
if [ -f "manage.py" ]; then
python manage.py migrate
fi
# Restart services
log "Restarting services"
systemctl restart "$APP_NAME"
# Health check
log "Performing health check"
sleep 5
if curl -sf http://localhost:8000/health > /dev/null; then
log "Deployment successful!"
else
error "Health check failed!"
fi
log "Deployment completed"
You now have a comprehensive reference for BASH shell scripting covering:
man bash - BASH manualhelp - Built-in commands help
Comprehensive Guide to BASH Shell Script Programming
From basics to advanced techniques