RED HAT ENTERPRISE LINUX

Shell Scripting Basics

Writing Scripts and Using Shell Features Efficiently

CIS238RH | RHEL System Administration 2
Mesa Community College

Learning Objectives

1
Write and execute shell scripts

Create scripts with proper structure, shebang, and execute permissions

2
Use variables and input

Define variables, use special parameters, and read user input

3
Implement loops

Use for and while loops to repeat commands efficiently

4
Apply conditional logic and shell features

Use if statements, test conditions, and shell expansions

What is a Shell Script?

A shell script is a text file containing a sequence of commands that the shell executes. Scripts automate tasks you would otherwise type manually.

Automation

Run multiple commands with a single execution

Reusability

Write once, run many times on different systems

Consistency

Same steps every time, reducing human error

Documentation

Script itself documents the procedure

Real-World Uses: System backups, log rotation, user management, software deployment, monitoring, report generation.

Your First Shell Script

# Create a script file
[student@server ~]$ vim hello.sh
#!/bin/bash
# My first shell script

echo "Hello, World!"
echo "Today is $(date)"
echo "You are logged in as: $USER"
echo "Your home directory is: $HOME"
# Make executable and run
[student@server ~]$ chmod +x hello.sh
[student@server ~]$ ./hello.sh
Hello, World!
Today is Mon Jan 15 10:30:45 EST 2024
You are logged in as: student
Your home directory is: /home/student

The Shebang Line

The shebang (#!) on the first line specifies which interpreter executes the script. Must be line 1, column 1.

#!/bin/bash          # Use Bash (most common)
#!/bin/sh            # POSIX shell (portable)
#!/usr/bin/python3   # Python script
#!/usr/bin/env bash  # Find bash in PATH

With Shebang

$ ./script.sh

Without Shebang

$ bash script.sh
Important: The shebang must be on line 1, column 1. No blank lines or spaces before it!

Running Scripts

# Method 1: Make executable and run
[student@server ~]$ chmod +x script.sh
[student@server ~]$ ./script.sh

# Method 2: Call interpreter explicitly
[student@server ~]$ bash script.sh
[student@server ~]$ bash -x script.sh    # Debug mode

# Method 3: Source (runs in current shell)
[student@server ~]$ source script.sh
[student@server ~]$ . script.sh
MethodExecute PermissionNew Shell?Use Case
./script.shRequiredYesNormal execution
bash script.shNot neededYesTesting
source script.shNot neededNoSet environment

Variables

Variables store data for use in scripts. No spaces around =, case-sensitive names.

# Defining variables (NO spaces around =)
NAME="John"
AGE=25
FILE_PATH="/var/log/messages"

# Using variables
echo "Hello, $NAME"
echo "Log file: $FILE_PATH"

# Curly braces for clarity
echo "Files: ${FILE_PATH}s"
echo "Name length: ${#NAME}"

# Command output in a variable
TODAY=$(date +%Y-%m-%d)
FILES=$(ls /etc/*.conf | wc -l)
echo "Today: $TODAY, Config files: $FILES"
Common Mistake: Spaces around = cause errors! NAME = "John" won't work.

Quoting

# Double quotes - variables ARE expanded
NAME="World"
echo "Hello, $NAME"           # Output: Hello, World

# Single quotes - literal (NO expansion)
echo 'Hello, $NAME'           # Output: Hello, $NAME

# Escape special characters
echo "The price is \$5.00"
echo 'The price is $5.00'

# Spaces in values - quotes required
GREETING="Hello World"        # Correct

# Files with spaces
FILE="my document.txt"
cat "$FILE"                   # Quotes preserve the space
Rule of Thumb: Use double quotes for strings with variables. Use single quotes for literal text. Always quote variables that might contain spaces.

Special Variables

$0
Script name
$1-$9
Positional parameters
$#
Number of arguments
$@
All arguments (separate)
$?
Exit status of last cmd
$$
Current process ID
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Number of arguments: $#"
echo "All arguments: $@"

[student@server ~]$ ./args.sh hello world
Script name: ./args.sh
First argument: hello
Number of arguments: 2
All arguments: hello world

Reading User Input

#!/bin/bash
# Simple read
echo "What is your name?"
read NAME
echo "Hello, $NAME!"

# Read with prompt (-p)
read -p "Enter your age: " AGE

# Silent read for passwords (-s)
read -sp "Enter password: " PASSWORD
echo

# Read with timeout (-t)
read -t 5 -p "Quick! Enter something: " QUICK

# Default value pattern
read -p "Enter directory [/tmp]: " DIR
DIR=${DIR:-/tmp}       # Use /tmp if empty

Exit Status

Every command returns an exit status (0-255). 0 = success, non-zero = failure.

[student@server ~]$ ls /etc/passwd
[student@server ~]$ echo $?
0                           # Success

[student@server ~]$ ls /nonexistent
[student@server ~]$ echo $?
2                           # Failure

# Setting exit status in scripts
#!/bin/bash
if [ ! -f "$1" ]; then
    echo "Error: File not found" >&2
    exit 1
fi
echo "Processing $1..."
exit 0

Conditional Execution

# && (AND) - run second only if first succeeds
$ mkdir /tmp/test && echo "Created"

# || (OR) - run second only if first fails
$ mkdir /tmp/test || echo "Failed"

# Combining && and ||
$ ping -c 1 server && echo "Up" || echo "Down"

# Semicolon ; runs regardless of status
$ echo "Start"; sleep 2; echo "Done"

# Practical examples
cd /var/log && tail messages
[ -d /backup ] || mkdir /backup
Memory Aid: && = "AND then" (if OK). || = "OR else" (if failed).

The if Statement

#!/bin/bash
# Basic if
if [ "$USER" = "root" ]; then
    echo "You are root"
fi

# if-else
if [ -f "/etc/passwd" ]; then
    echo "File exists"
else
    echo "File not found"
fi

# if-elif-else
if [ "$1" = "start" ]; then
    echo "Starting..."
elif [ "$1" = "stop" ]; then
    echo "Stopping..."
else
    echo "Usage: $0 {start|stop}"
    exit 1
fi
Syntax: Spaces inside [ ] are required! ["$USER"="root"] won't work.

Test Conditions

-f FILE
File exists
-d DIR
Directory exists
-e PATH
Path exists
-r FILE
Readable
-w FILE
Writable
-x FILE
Executable
-z STR
String empty
-n STR
String not empty
S1 = S2
Strings equal
-eq
Numbers equal
-lt
Less than
-gt
Greater than
[ -f "/etc/passwd" ] && echo "File exists"
[ -z "$VAR" ] && echo "VAR is empty"
[ "$COUNT" -gt 10 ] && echo "More than 10"

The for Loop

#!/bin/bash
# Loop through a list
for NAME in Alice Bob Charlie; do
    echo "Hello, $NAME"
done

# Loop through files
for FILE in /etc/*.conf; do
    echo "Config: $FILE"
done

# Loop through command output
for USER in $(cut -d: -f1 /etc/passwd); do
    echo "User: $USER"
done

# C-style for loop
for ((i=1; i<=5; i++)); do
    echo "Count: $i"
done
Common Use: Processing files, iterating through lists, batch operations.

The while Loop

#!/bin/bash
# Basic while loop
COUNT=1
while [ $COUNT -le 5 ]; do
    echo "Count: $COUNT"
    ((COUNT++))
done

# Read file line by line
while read LINE; do
    echo "Line: $LINE"
done < /etc/hosts

# Infinite loop with break
while true; do
    read -p "Command (quit to exit): " CMD
    [ "$CMD" = "quit" ] && break
    echo "You entered: $CMD"
done

Brace Expansion

Brace expansion generates multiple strings from a pattern before other expansions.

# List expansion
$ echo {a,b,c}
a b c
$ echo file{1,2,3}.txt
file1.txt file2.txt file3.txt

# Sequence expansion
$ echo {1..5}
1 2 3 4 5
$ echo {a..e}
a b c d e

# Practical uses
mkdir -p project/{src,bin,doc}
cp config.conf{,.backup}
touch file{1..10}.txt
mv report.{txt,md}
Note: Brace expansion happens before variable expansion, so {$a..$b} won't work as expected.

Command Substitution

# Modern syntax: $(command)
$ echo "Today is $(date +%A)"
Today is Monday

$ FILES=$(ls *.txt | wc -l)
$ echo "Found $FILES text files"

# Nested command substitution
$ echo "Home: $(dirname $(readlink -f ~/.bashrc))"

# In conditions
if [ $(id -u) -eq 0 ]; then
    echo "Running as root"
fi

# In loops
for PKG in $(rpm -qa | grep httpd); do
    echo "Installed: $PKG"
done

Arithmetic

# Arithmetic expansion $(( ))
$ echo $((5 + 3))
8
$ echo $((10 / 3))    # Integer division
3
$ echo $((10 % 3))    # Modulo
1

# Variables in arithmetic
A=5; B=3
echo $((A + B))       # 8
echo $((A ** 2))      # 25 (exponent)

# Increment/decrement
((COUNT++))
((COUNT += 5))

# In scripts
TOTAL=0
for NUM in 1 2 3 4 5; do
    ((TOTAL += NUM))
done
echo "Sum: $TOTAL"

Script Best Practices

Good Practices

  • Start with #!/bin/bash
  • Add comments
  • Quote all variables
  • Check required arguments
  • Use meaningful names
  • Set exit status
  • Handle errors

Avoid

  • Unquoted variables
  • No error checking
  • Hardcoded paths
  • Cryptic names
  • No usage message
  • Silent failures
  • Ignoring exit status
#!/bin/bash
# backup.sh - Backup home directory
set -e                     # Exit on error
DEST="${1:-/backup}"
[ -d "$DEST" ] || { echo "Error: $DEST not found" >&2; exit 1; }
tar -czf "$DEST/home-$(date +%Y%m%d).tar.gz" "$HOME"

Key Takeaways

1

Scripts: Start with #!/bin/bash, chmod +x, run with ./script.sh

2

Variables: NAME=value (no spaces), use "$NAME", special: $1 $# $?

3

Loops: for VAR in LIST; do...done, while COND; do...done

4

Conditionals: if [ test ]; then...fi, tests: -f -d -eq -lt, && ||

Graded Lab

  • Create a for loop to iterate through a list of items from the command line and in a shell script.
  • Create an if/then/else statement that executes different commands based on a condition being true or false.

Next: Using Regular Expressions for Practical Applications