Skip to main content

Command Palette

Search for a command to run...

Shell Scripting for DevOps: A Complete Practical Guide

Updated
10 min read
Shell Scripting for DevOps: A Complete Practical Guide

Shell scripting is one of the most important skills for any DevOps engineer. Whether you're automating server setup, deploying applications, or managing infrastructure, bash scripts are everywhere. This guide covers everything from basics to real-world automation patterns.


1. Script Structure & Shebang

Every shell script starts with a shebang line that tells the OS which interpreter to use.

#!/bin/bash
VAR_NAME=VALUE

Run a script and pass arguments:

sh script-name.sh first-arg second-arg
  • $1 → first argument (first-arg)

  • $2 → second argument (second-arg)

  • ... and so on for \(3, \)4, etc.


2. Reading Input & Command Substitution

read VAR_NAME
VAR_NAME=$(command)

read takes user input interactively. Command substitution $(command) captures the output of a command into a variable — extremely useful for storing results like IP addresses, hostnames, or version numbers.


3. Built-in Shell Variables

Bash automatically provides several useful variables:

echo "All variables passed to script: $@"
echo "Number of variables passed: $#"
echo "First variable: $1"
echo "Script name: $0"
echo "Who is running this: $USER"
echo "Which directory: $PWD"
echo "Home directory: $HOME"
echo "PID of the current script: $$"

sleep 5 &
echo "PID of the background command running just now: $!"
wait $!

echo "Line number: $LINENO"
echo "Script executed in $SECONDS seconds"
echo "Random number: $RANDOM"
Variable Meaning
$@ All arguments passed to script
$# Number of arguments
\(1, \)2... Positional arguments
$0 Script name
$USER Current logged-in user
$PWD Present working directory
$HOME Home directory of the user
$$ PID of current script
$! PID of the last background process
$LINENO Current line number in script
$SECONDS Time elapsed since the script started
$RANDOM Random integer generator

4. Exit Codes

$?

Every command returns an exit code after execution:

  • 0 → success

  • 1–127 → failure (different numbers represent different failure reasons)

Best practice: It is the script writer's responsibility to check the exit code of every critical command and handle failures gracefully — never assume success.

command
if [ $? -eq 0 ]; then
    echo "Success"
else
    echo "Failed with exit code $?"
    exit 1
fi

5. Variables & Data Types

Shell, by default, treats every variable as a string. There's no strict type system like in programming languages (Java/Python), where you'd declare:

int i = 10;
String name = "siva123";

Common data types across languages for reference: int, float, long, decimal, boolean, string, char, double, array, arraylist, map, set.

Example — type matters in real systems:

Rocket Speed Data Type
Ariane 4 9 km/sec Integer
Ariane 5 8.2 km/sec Float
integer speed = 8.2   # wrong - would cause data loss/precision issue
float speed = 8.2     # correct

This is a famous real-world failure case — Ariane 5's flight software reused Ariane 4's integer-based code for a value that needed to be a float, leading to an overflow and the rocket's destruction shortly after launch.


6. Conditional Statements

General if-else (language agnostic pseudocode)

if (expression) {
    execute these lines if expression is true
}

if (expression) {
    execute if true
} else {
    execute if false
}

if (expression) {
    ...
} else if (expression) {
    ...
} else if (expression) {
    ...
} else {
    ...
}

Example logic

number = 20
if (number > 10) {
    print "20 is greater than 10"
} else {
    print "20 is less than 10"
}

Bash equivalent

NUMBER=20
if [ $NUMBER -gt 20 ]; then
    echo "number is greater than 20"
else
    echo "number is less than 20"
fi

Common comparison operators

Operator Meaning
-eq equal to
-ne not equal to
-gt greater than
-lt less than
-ge greater than or equal
-le less than or equal to
-z string is empty
-n string is not empty
-f file exists
-d directory exists

7. Real Use Case: Install MySQL via Shell Script

Requirements:

  1. The user should have root access or run the script with root access.

  2. If not root, exit the script with a proper reason.

  3. Check if the package is already installed.

    • If installed → print "already installed".

    • If not installed → install it.

  4. Install the package.

  5. Check exit status:

    • 0 → show success

    • non-zero → show failure

Sample script:

#!/bin/bash

# Step 1: Check root access
if [ $USERID -ne 0 ]; then
    echo "ERROR: Please run this script with root access"
    exit 1
fi

# Step 2: Check if mysql is already installed
dnf list installed mysql &>/dev/null

if [ $? -eq 0 ]; then
    echo "MySQL is already installed"
else
    dnf install mysql -y &>/dev/null

    if [ $? -eq 0 ]; then
        echo "MySQL installed successfully"
    else
        echo "MySQL installation failed"
        exit 1
    fi
fi

Always validate: (1) permissions, (2) idempotency (don't reinstall what's already there), and (3) exit status of every critical operation.


8. Functions

Functions help you avoid repeating code. Write the logic once, call it multiple times with different arguments.

Benefits:

  • Fewer lines of code

  • Less time to write/maintain

  • Less resource usage / better readability

FUNC_NAME(){
    \(1 \)2
    # keep the repeated logic here
}

FUNC_NAME arg-1 arg-2

Practical example — logging function:

LOG(){
    echo "\((date +%F-%T) - \)1"
}

LOG "Starting installation"
LOG "Installation completed"

9. Redirections

Redirection controls where command output and errors go.

Symbol Meaning
< input redirection
> output redirection (overwrite)
>> append to output
1 stdout (success/normal output)
2 stderr (error output)
& both stdout and stderr

Examples:

ls -l 1> output.log          # redirect success output
ls -l 2> error.log           # redirect error output
ls -l 1> output.log 2> error.log
ls -l &> all_output.log      # both stdout and stderr to same file
ls -l >> output.log          # append instead of overwrite

10. Loops

General pseudocode (for loop)

for (int i = 0; i < 5; i++) {
    print i;
}

Bash for loop — iterating over script arguments

#!/bin/bash
# usage: sh loop.sh mysql nginx

for SERVICE in "$@"
do
    echo "Processing: $SERVICE"
done
for <expression>
do
    statements
done

11. Colored Output in Terminal (ANSI Escape Codes)

Bash supports ANSI color codes to make log output more readable.

Color Code
Red 31
Green 32
Yellow 33

Format: \e[<code>m

R="\e[31m"
G="\e[32m"
Y="\e[33m"
N="\e[0m"   # reset to normal

echo -e "\({G}2026-05-25 08:34:44 INFO Installing Mysql\){N}"
echo -e "\({R}2026-05-25 08:34:50 ERROR Installation failed\){N}"
echo -e "\({Y}2026-05-25 08:35:00 WARNING Service not started\){N}"

12. Error Handling

There are two broad categories of errors:

  1. Expected errors — anticipated failure points (e.g., package not found, service not running, permission denied). You write conditional checks for these.

  2. Unexpected errors — failures you didn't predict (e.g., disk full, network timeout). Use set -e, trap, and logging to catch these gracefully.

set -e   # exit immediately if any command fails
trap 'echo "Error occurred on line $LINENO"; exit 1' ERR

13. Real-World Example: AWS Security Group via Script

Goal: Create a security group named roboshop-common for roboshop-mongodb.

Requirements:

  1. Script should run with root access (or have correctly configured AWS credentials).
#!/bin/bash

if [ $USERID -ne 0 ]; then
    echo "Please run with root access"
    exit 1
fi

aws ec2 create-security-group \
    --group-name roboshop-common \
    --description "Common SG for Roboshop services" \
    --vpc-id <vpc-id>

if [ $? -eq 0 ]; then
    echo "Security group created successfully"
else
    echo "Failed to create security group"
    exit 1
fi

14. The sed Editor (Stream Line Editor)

sed is used for searching, replacing, and editing text directly from the command line — without opening a file in an editor.

Command Action
i insert text before a line
a insert text after a line
s substitute / find & replace
d delete a line
c change/replace a line
/pattern/d delete lines matching a pattern

Examples:

sed '3i\New line before line 3' file.txt
sed '3a\New line after line 3' file.txt
sed 's/old/new/g' file.txt          # replace all occurrences
sed '5d' file.txt                   # delete line 5
sed '/error/d' file.txt             # delete all lines containing "error"
sed '3c\Replaced line 3' file.txt

15. Putting It All Together: Roboshop Multi-Service Installer

#!/bin/bash
# usage: sh roboshop.sh mongodb redis mysql rabbitmq

USERID=$(id -u)
R="\e[31m"
G="\e[32m"
Y="\e[33m"
N="\e[0m"

LOG_FOLDER="/var/log/roboshop-logs"
SCRIPT_NAME=\((basename "\)0" | cut -d "." -f1)
LOGFILE="\(LOG_FOLDER/\)SCRIPT_NAME.log"

mkdir -p "$LOG_FOLDER"

VALIDATE(){
    if [ $1 -eq 0 ]; then
        echo -e "\(2 ... \){G}SUCCESS${N}"
    else
        echo -e "\(2 ... \){R}FAILED${N}"
        exit 1
    fi
}

if [ $USERID -ne 0 ]; then
    echo -e "\({R}ERROR:: Please run this script with root access\){N}"
    exit 1
fi

for SERVICE in "$@"
do
    echo "Installing \(SERVICE" | tee -a "\)LOGFILE"

    dnf list installed "\(SERVICE" &>>"\)LOGFILE"

    if [ $? -eq 0 ]; then
        echo -e "\(SERVICE is already installed ... \){Y}SKIPPING\({N}" | tee -a "\)LOGFILE"
        continue
    fi

    dnf install "\(SERVICE" -y &>>"\)LOGFILE"
    VALIDATE \(? "Installing \)SERVICE"
done

What this script demonstrates:

  • Root access validation

  • Logging with timestamps and color-coded output

  • Idempotency checks (skip if already installed)

  • Reusable VALIDATE function for exit status checking

  • Looping over multiple services passed as arguments

  • Centralized logging to a log file using tee -a


Key Takeaways

  • Always validate permissions before performing privileged operations.

  • Always check exit status ($?) after critical commands — never assume success.

  • Build idempotent scripts — re-running them shouldn't break or duplicate work.

  • Use functions to reduce repetition and improve maintainability.

  • Use logging and color codes for better visibility into script execution.

  • Handle both expected and unexpected errors gracefully.

  • Master sed for quick, scriptable text manipulation — a huge time-saver in automation pipelines.


If you found this useful, follow for more DevOps automation content covering AWS, Docker, Kubernetes, and CI/CD pipelines.