← Back to list

Advanced Shell Scripting (Part 2)

Real-World Automation, DevOps & Production-Grade Bash

DevOps voice in DevOps.dev · 2026-07-01 08:58 · 170 claps · 12.1 min read paywalled
#bash-scripting #automation #scripting #linux-tutorial #devops-practice
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🔓 · Open Source

Advanced Shell Scripting (Part 2)

Real-World Automation, DevOps & Production-Grade Bash

▶️ **YouTube , [📸 Instagram](https://www.instagram.com/devops_voice) , [💼 LinkedIn](https://www.linkedin.com/in/tushar-jadhav29/) , [✍️ Medium](https://medium.com/@tushar.jadhav29)**

Non-Member= Click HERE!

This guide is designed for engineers who already understand:

  • Variables
  • Loops
  • Functions
  • Arrays
  • Conditions
  • File Handling
  • Cron Jobs

Now we’ll focus on production-grade automation used by:

  • Linux Administrators
  • DevOps Engineers
  • Cloud Engineers
  • SREs
  • Platform Engineers

1. Strict Mode (Production Scripts)

Most production-grade Bash scripts start with:

#!/bin/bash

set -euo pipefail

This enables Strict Mode, helping prevent common scripting mistakes and making scripts safer and more predictable.

Why Use Strict Mode?

Without strict mode:

  • Scripts may continue after failures.
  • Undefined variables may go unnoticed.
  • Pipeline errors may be hidden.
  • Production incidents become harder to troubleshoot.

With strict mode:

  • Fail fast.
  • Detect bugs early.
  • Improve reliability and maintainability.

Exit on Error (set -e)

Purpose : Stops script execution immediately when a command exits with a non-zero status.

#!/bin/bash

set -e

cp file.txt backup/
rm file.txt

echo "Completed"

Problem Without set -e

If:

cp file.txt backup/   ### fails because the file does not exist:

cp: cannot stat 'file.txt' ## the script still executes:

rm file.txt  ## which may lead to data loss or unexpected behavior.

With set -e
cp: cannot stat 'file.txt'  ## Script stops immediately and nothing else runs.

Undefined Variable Check ( set -u )

Purpose : Treats undefined variables as errors.

## Example: 

#!/bin/bash

set -u

echo "$USERNAME"

## If USERNAME is not defined:

## Script exits immediately.
./script.sh: line 5: USERNAME: unbound variable

## Without set -u

echo "$USERNAME"

Output: 
(empty value)

This can cause:

  • Incorrect paths
  • Empty filenames
  • Accidental deletions
## DevOPs Example
rm -rf "$APP_HOME/logs" ## If APP_HOME is empty:

rm -rf /logs     ### Potentially dangerous.

Pipeline Validation (set -o pipefail)

Purpose : Makes a pipeline fail if any command in the pipeline fails.

set -o pipefail

Example:

cat file.txt | grep test | sort

Without pipefail:

## Example

cat file.txt | grep test | sort

file.txt    ## does not exist.

## Without pipefail
cat file.txt | grep test | sort

Output:
cat: file.txt: No such file or directory

## But pipeline exit code:
echo $?
0

# Because sort succeeded.
# The script incorrectly believes everything worked.

Combined Example

#!/bin/bash

set -euo pipefail

BACKUP_DIR="/backup"

cp data.txt "$BACKUP_DIR/"

cat data.txt | grep ERROR > errors.log

echo "Backup completed successfully"

Behavior:

| ---------------------- | ------------ |
| Situation              | Result       |
| ---------------------- | ------------ |
| Copy fails             | Script exits |
| Variable missing       | Script exits |
| grep fails in pipeline | Script exits |
| Everything successful  | Continues    |
| ---------------------- | ------------ |

Template

#!/bin/bash

set -euo pipefail

main() {
    echo "Starting script..."

    # Script logic here

    echo "Completed successfully."
}

main "$@"

2. Advanced Error Handling

Production scripts should not only fail safely but also provide meaningful error messages that help quickly identify and troubleshoot issues.

Error Function

A common pattern is creating a reusable error handling function.

#!/bin/bash

set -euo pipefail

error_exit() {
    echo "[ERROR] $1"
    exit 1
}

Usage:

[ -f config.txt ] || error_exit "Config Missing"

## Output
[ERROR] Config file missing

Benefits

  • Consistent error messages
  • Cleaner code
  • Easier maintenance
  • Reusable across scripts

Enhanced Error Function

Include timestamp and script name.

error_exit() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] [$0] $1"
    exit 1
}

# Example:
[ -d /backup ] || error_exit "Backup directory not found"

# Output
2026-06-23 10:30:12 [ERROR] [backup.sh] Backup directory not found

Centralized Error Handler Using trap

Instead of manually checking every command, use Bash traps.

#!/bin/bash

set -euo pipefail

handle_error() {
    echo "Error occurred on line $1"
}

trap 'handle_error $LINENO' ERR

Production-level debugging.

#!/bin/bash

set -euo pipefail

handle_error() {
    echo "Error occurred on line $1"
}

trap 'handle_error $LINENO' ERR

cp missing.txt /backup/

echo "Completed"

## Output:

cp: cannot stat 'missing.txt': No such file or directory
Error occurred on line 10

Production-Grade Error Handler

Capture:

  • Line number
  • Failed command
  • Exit code
  • Script name
  • Timestamp
#!/bin/bash

set -euo pipefail

handle_error() {
    local line_no=$1
    local exit_code=$2

    echo "================================="
    echo "Timestamp : $(date)"
    echo "Script    : $0"
    echo "Line      : $line_no"
    echo "Command   : $BASH_COMMAND"
    echo "Exit Code : $exit_code"
    echo "================================="
}

trap 'handle_error ${LINENO} $?' ERR

##  Example Output:

=================================
Timestamp : Tue Jun 23 10:35:12 UTC 2026
Script    : backup.sh
Line      : 22
Command   : cp missing.txt /backup/
Exit Code : 1
=================================

Logging Errors to a File

LOGFILE="/var/log/app.log"

handle_error() {
    echo "$(date) ERROR at line $1: $BASH_COMMAND" >> "$LOGFILE"
}

trap 'handle_error $LINENO' ERR

## Output in log:

2026-06-23 10:40:12 ERROR at line 18: cp missing.txt /backup/

Cleanup Before Exit

Use trap EXIT for cleanup tasks.

cleanup() {
    rm -f /tmp/mytempfile
}

trap cleanup EXIT

Multiple Traps

trap cleanup EXIT
trap 'handle_error $LINENO' ERR
trap 'echo "Interrupted"; exit 130' INT
trap 'echo "Terminated"; exit 143' TERM

Signals

| ------ | --------------- |
| Signal | Description     |
| ------ | --------------- |
| EXIT   | Script exits    |
| ERR    | Command failure |
| INT    | Ctrl+C          |
| TERM   | Kill request    |
| HUP    | Terminal closed |
| ------ | --------------- |

Production Error Handling Template

#!/bin/bash

set -euo pipefail

LOGFILE="/var/log/script.log"

log() {
    echo "$(date '+%F %T') [INFO] $1" | tee -a "$LOGFILE"
}

error_exit() {
    echo "$(date '+%F %T') [ERROR] $1" | tee -a "$LOGFILE"
    exit 1
}

handle_error() {
    local line_no=$1
    local exit_code=$2

    echo "$(date '+%F %T') [ERROR] Script failed at line ${line_no}" \
        | tee -a "$LOGFILE"

    echo "$(date '+%F %T') [ERROR] Command: ${BASH_COMMAND}" \
        | tee -a "$LOGFILE"

    echo "$(date '+%F %T') [ERROR] Exit Code: ${exit_code}" \
        | tee -a "$LOGFILE"
}

cleanup() {
    log "Cleanup completed"
}

trap 'handle_error ${LINENO} $?' ERR
trap cleanup EXIT

log "Script started"

[ -f config.txt ] || error_exit "Config file missing"

log "Processing completed"

3. Logging Framework

Production scripts should use structured logging instead of plain echo statements. A logging framework makes it easier to troubleshoot issues, monitor deployments, and integrate with log management tools.

Production scripts should use structured logging instead of plain echo statements. A logging framework makes it easier to troubleshoot issues, monitor deployments, and integrate with log management tools.

Why Use a Logging Framework?

Instead of:

echo "Starting deployment..."
echo "Database backup completed"
echo "Deployment failed"

Create logger:

LOGFILE=/var/log/deploy.log

log() {
    echo "$(date '+%F %T') [$1] $2" | tee -a $LOGFILE
}

Use structured logs:

2026-01-01 10:00:00 [INFO] Deployment Started
2026-01-01 10:01:15 [INFO] Database Backup Completed
2026-01-01 10:03:42 [ERROR] Deployment Failed

Benefits

  • Consistent log format
  • Easy debugging
  • Timestamped events
  • Log file persistence
  • Compatible with monitoring tools (Grafana, ELK, Splunk, Loki, etc.)

Basic Logging Function

#!/bin/bash

LOGFILE="/var/log/deploy.log"

log() {
    echo "$(date '+%F %T') [$1] $2" | tee -a "$LOGFILE"
}

## Usage
log INFO "Deployment Started"
log WARNING "Disk Usage High"
log ERROR "Database Connection Failed"

## Output
2026-01-01 10:00:00 [INFO] Deployment Started
2026-01-01 10:05:12 [WARNING] Disk Usage High
2026-01-01 10:07:45 [ERROR] Database Connection Failed

Log Levels

| -------- | ----------------- | -------------------------- |
| Level    | Purpose           | Example                    |
| -------- | ----------------- | -------------------------- |
| INFO     | Normal operations | Service started            |
| WARNING  | Potential issue   | Disk usage above 80%       |
| ERROR    | Operation failed  | Database connection failed |
| DEBUG    | Troubleshooting   | Variable values            |
| CRITICAL | Severe failure    | System unavailable         |
| -------- | ----------------- | -------------------------- |
## Example:
log INFO "Backup started"
log DEBUG "Server IP: $HOSTNAME"
log WARNING "Memory usage is above 90%"
log ERROR "Unable to connect to database"
log CRITICAL "Deployment aborted"

## Improved Logger with Script Name
#!/bin/bash

LOGFILE="/var/log/deploy.log"

log() {
    local level="$1"
    shift
    echo "$(date '+%F %T') [$level] [$0] $*" | tee -a "$LOGFILE"
}

## Output:
2026-01-01 10:15:32 [INFO] [deploy.sh] Deployment Started

Logging to File Only

This writes only to the log file without displaying output on the terminal.

log() {
    echo "$(date '+%F %T') [$1] $2" >> "$LOGFILE"
}

Logging to Console and File

This is the most common approach in production because logs are visible in real time and stored for later review.

log() {
    local level="$1"
    shift
    echo "$(date '+%F %T') [$level] $*" | tee -a "$LOGFILE"
}
-------------------------------------------------------
## Add Process ID (PID)
log() {
    local level="$1"
    shift
    echo "$(date '+%F %T') [$level] [PID:$$] $*" | tee -a "$LOGFILE"
}

## Output:
2026-01-01 10:20:11 [INFO] [PID:4521] Deployment Started
-------------------------------------------------------
### Add Hostname
log() {
    local level="$1"
    shift
    echo "$(date '+%F %T') [$(hostname)] [$level] $*" | tee -a "$LOGFILE"
}

## Output:
2026-01-01 10:25:00 [web01] [INFO] Service Started

DevOPs Logging Framework

#!/bin/bash

set -euo pipefail

LOGFILE="/var/log/deploy.log"

log() {
    local level="$1"
    shift

    printf "%s [%s] [%s] [PID:%s] %s\n" \
        "$(date '+%F %T')" \
        "$level" \
        "$(hostname)" \
        "$$" \
        "$*" | tee -a "$LOGFILE"
}

## Usage:
log INFO "Deployment Started"
log INFO "Creating backup"
log WARNING "Low Disk Space"
log ERROR "Application failed to start"
log CRITICAL "Rolling back deployment"

## Sample output:
2026-01-01 10:00:00 [INFO] [web01] [PID:4521] Deployment Started
2026-01-01 10:01:20 [INFO] [web01] [PID:4521] Creating backup
2026-01-01 10:02:30 [WARNING] [web01] [PID:4521] Low Disk Space
2026-01-01 10:03:15 [ERROR] [web01] [PID:4521] Application failed to start
2026-01-01 10:03:18 [CRITICAL] [web01] [PID:4521] Rolling back deployment
---------------------------------------------
## Integrating with Error Handling
error_exit() {
    log ERROR "$1"
    exit 1
}

handle_error() {
    log ERROR "Error on line $1: ${BASH_COMMAND}"
}

trap 'handle_error ${LINENO}' ERR

## Example:
cp config.txt /backup || error_exit "Backup failed"

## Log output:
2026-01-01 10:30:15 [ERROR] Backup failed

4. Configuration Files

Production scripts should never hardcode values such as ports, hostnames, usernames, or file paths. Instead, store configuration in a separate file so the script can be reused across different environments (Development, Testing, Staging, Produc

Why Use Configuration Files?

❌ Never hardcode values.

## Hardcoded values:
# config.conf
APP_NAME=myapp
PORT=8080
DB_HOST=localhost

**Problems:

  • **Difficult to maintain
  • Requires script changes for every environment
  • Increases risk of errors
  • Hard to reuse

Use a Configuration File

Create a file named config.conf:

APP_NAME=myapp
PORT=8080
DB_HOST=localhost
DB_PORT=3306
LOG_DIR=/var/log/myapp
BACKUP_DIR=/backup

Loading the Configuration

#!/bin/bash

set -euo pipefail

source config.conf

## OR try with below example 

#!/bin/bash

set -euo pipefail

CONFIG_FILE="./config.conf"

if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "Configuration file not found: $CONFIG_FILE"
    exit 1
fi

source "$CONFIG_FILE"

Using Configuration Values

echo "$APP_NAME"
echo "$PORT"
echo "$DB_HOST"

## Output:
myapp
8080
localhost

Example Script

config.conf

APP_NAME=InventoryApp
PORT=8080
DB_HOST=db01.example.com
LOG_DIR=/var/log/inventory

deploy.sh

#!/bin/bash

set -euo pipefail

source config.conf

echo "Application : $APP_NAME"
echo "Port        : $PORT"
echo "Database    : $DB_HOST"
echo "Logs        : $LOG_DIR"

## Output:
Application : InventoryApp
Port        : 8080
Database    : db01.example.com
Logs        : /var/log/inventory

---------------------------------------
#!/bin/bash

set -euo pipefail

CONFIG_FILE="./config.conf"

[[ -f "$CONFIG_FILE" ]] || {
    echo "Configuration file missing"
    exit 1
}

source "$CONFIG_FILE"

echo "Deploying $APP_NAME..."
echo "Connecting to $DB_HOST:$DB_PORT"
echo "Application will listen on port $PORT"

5. Parsing YAML

Modern DevOps tools such as Kubernetes, Ansible, GitHub Actions, Argo CD, Helm, and Docker Compose use YAML for configuration. Instead of manually parsing YAML with grep or awk, use **yq**, a command-line YAML processor.

Why Use yq?

❌ Avoid parsing YAML like this:

grep environment config.yaml | awk '{print $2}'

Problems:

  • Breaks with nested YAML
  • Doesn’t handle arrays or complex structures
  • Difficult to maintain

✅ Use yq, which understands YAML syntax.

Sample YAML File

environment: production
app:
  name: myapp
  port: 8080

database:
  host: db.example.com
  port: 3306
servers:
  - web01
  - web02

Install yq

## Ubuntu/Debian (via Snap)
sudo snap install yq

## download the binary
sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \
    -O /usr/local/bin/yq

sudo chmod +x /usr/local/bin/yq

## macOS (Homebrew)
brew install yq

## Verify installation:
yq --version

Read a Value

yq '.environment' config.yaml

## Output:
production

## Read Nested Values
yq '.app.name' config.yaml

## Output:
myapp

## Read Array Elements
yq '.servers[0]' config.yaml

## Output:
web01

## Display all servers:
yq '.servers[]' config.yaml

# Output:
web01
web02

Store Values in Variables

APP_NAME=$(yq '.app.name' config.yaml)
DB_HOST=$(yq '.database.host' config.yaml)
PORT=$(yq '.app.port' config.yaml)

echo "$APP_NAME"
echo "$DB_HOST"
echo "$PORT"

Loop Through an Array

for server in $(yq '.servers[]' config.yaml); do
    echo "Deploying to $server"
done

## Output:
Deploying to web01
Deploying to web02

Update a YAML Value

Modify a field in place:

yq -i '.environment = "staging"' config.yaml

# Updated file:
environment: staging

Production Example

config.yaml

application:
  name: inventory-service
  version: "1.2.0"

database:
  host: mysql-prod
  port: 3306

deployment:
  replicas: 3

deploy.sh

#!/bin/bash

set -euo pipefail

APP_NAME=$(yq '.application.name' config.yaml)
VERSION=$(yq '.application.version' config.yaml)
REPLICAS=$(yq '.deployment.replicas' config.yaml)

echo "Deploying $APP_NAME"
echo "Version: $VERSION"
echo "Replicas: $REPLICAS"

## Output:
Deploying inventory-service
Version: 1.2.0
Replicas: 3

Common yq Commands

| Task               | Command                                     |
| ------------------ | ------------------------------------------- |
| Read a value       | `yq '.environment' config.yaml`             |
| Read nested value  | `yq '.database.host' config.yaml`           |
| Read array         | `yq '.servers[]' config.yaml`               |
| Read first element | `yq '.servers[0]' config.yaml`              |
| Update value       | `yq -i '.environment = "prod"' config.yaml` |
| Pretty-print YAML  | `yq '.' config.yaml`                        |

6. JSON Automation with jq

Most REST APIs return JSON. Use **jq** to parse it reliably.

Sample JSON

{
  "status": "success",
  "count": 25,
  "users": [
    {
      "id": 1,
      "name": "John"
    }
  ]
}

##  Read Values
jq -r '.status' response.json

## Output
success
jq '.count' response.json

## Output
25

Read Nested Value

jq -r '.users[0].name' response.json

## Output
John

Parse API Response

response=$(curl -s https://api.example.com/users)

status=$(jq -r '.status' <<< "$response")

echo "$status"

7. Advanced Arrays

Indexed Arrays

servers=(
    web01
    web02
    web03
)

Loop:

for server in "${servers[@]}"; do
    echo "$server"
done

Associative Arrays

declare -A server_ip

server_ip[web01]="10.0.0.1"
server_ip[web02]="10.0.0.2"

Access:

echo "${server_ip[web01]}"

Loop:

for host in "${!server_ip[@]}"; do
    echo "$host -> ${server_ip[$host]}"
done

8. Parallel Processing

Sequential:

backup_server server1
backup_server server2
backup_server server3

Parallel:

backup_server server1 &
backup_server server2 &
backup_server server3 &

wait

Production pattern:

for server in "${servers[@]}"; do
    backup_server "$server" &
done

wait

echo "All backups completed"

9. Multi-Server SSH Automation

while read -r server; do
    ssh "$server" uptime
done < servers.txt

Use read -r to preserve backslashes and always quote variables.

10. Parallel SSH

while read -r server; do
    ssh "$server" uptime &
done < servers.txt

wait

Ideal for:

  • Health checks
  • Configuration validation
  • Patch verification
  • Log collection
  • Rolling deployments

For large fleets, consider tools like pssh, pdsh, or Ansible.

11. Automated Package Deployment

packages=(
    nginx
    git
    curl
    docker.io
)

for pkg in "${packages[@]}"; do
    sudo apt-get install -y "$pkg"
done

Production improvement:

sudo apt-get update

for pkg in "${packages[@]}"; do
    if ! dpkg -s "$pkg" >/dev/null 2>&1; then
        sudo apt-get install -y "$pkg"
    fi
done

12. Service Monitoring

Check service:

systemctl is-active nginx

Restart if stopped:

if ! systemctl is-active --quiet nginx; then
    systemctl restart nginx
fi

Verify restart:

systemctl is-active --quiet nginx \
    && echo "Service healthy"

13. Database Backup Automation

Avoid passwords on the command line.

Instead of:

mysqldump -u root -pPassword appdb

## Use:
mysqldump --defaults-extra-file=/root/.my.cnf appdb \
    > backup.sql

## Compress:
gzip backup.sql

## Add timestamp:
mysqldump appdb \
> backup_$(date +%F).sql

14. Disk Monitoring

usage=$(df -P / | awk 'NR==2 {gsub("%","",$5); print $5}')

Check:

if (( usage > 80 )); then
    echo "Disk usage critical: ${usage}%"
fi

Using df -P provides POSIX-compatible output.

15. Memory Monitoring

free -m

Used memory:

free -m | awk 'NR==2 {print $3}'

Memory percentage:

free | awk '/Mem:/ {printf "%.2f%%\n", $3/$2*100}'

16. CPU Monitoring

Avoid parsing top when possible.

Preferred:

mpstat 1 1

If using top:

top -bn1 | grep "Cpu(s)"

17. Email Alerts

echo "Disk Usage Critical" \
| mail -s "Server Alert" admin@company.com

Production alternatives:

  • Postfix
  • SMTP relay
  • Amazon SES
  • SendGrid
  • Microsoft Graph

18. Slack Notifications

curl -X POST \
-H "Content-Type: application/json" \
-d '{"text":"Deployment completed successfully"}' \
"$WEBHOOK_URL"

Always store the webhook URL in an environment variable or secret manager.

19. REST API Automation

GET:

curl -fsS https://api.example.com/users

POST:

curl -X POST \
-H "Content-Type: application/json" \
-d '{"name":"john"}' \
https://api.example.com/users

Recommended options:

  • -f → Fail on HTTP errors
  • -s → Silent mode
  • -S → Show errors

20. Retry Logic

Basic:

for i in {1..5}; do
    curl -fsS https://api.example.com && break

    sleep 5
done

Production version:

max_retry=5

for ((i=1;i<=max_retry;i++)); do
    if curl -fsS https://api.example.com; then
        break
    fi

    echo "Retry $i/$max_retry"
    sleep 5
done

21. Lock Files

Simple lock:

LOCK=/tmp/backup.lock

if [[ -f "$LOCK" ]]; then
    echo "Already running"
    exit 1
fi

touch "$LOCK"

trap 'rm -f "$LOCK"' EXIT

Production recommendation:

exec 9>/tmp/backup.lock

flock -n 9 || exit 1

flock avoids race conditions and is more reliable than checking for a file.

22. Signal Handling

cleanup() {
    rm -f /tmp/*.tmp
}

trap cleanup EXIT
trap cleanup INT
trap cleanup TERM

Ensures cleanup on normal exit, Ctrl+C, or termination.

23. Advanced sed

Replace:

sed -i 's/dev/prod/g' app.conf

Delete lines:

sed '/ERROR/d' logs.txt

Replace only first occurrence:

sed 's/dev/prod/'

24. Advanced awk

Sum:

awk '{sum+=$3}
END {print sum}' sales.txt

Average:

awk '{sum+=$3}
END {print sum/NR}' sales.txt

Maximum:

awk 'max<$3{max=$3}
END{print max}' sales.txt

25. Docker Automation

Build:

docker build -t myapp:latest .

Run:

docker run -d --name myapp myapp:latest

Stop all running containers:

docker stop $(docker ps -q)

Remove stopped containers:

docker container prune -f

26. Kubernetes Automation

Pods:

kubectl get pods

Restart Deployment:

kubectl rollout restart deployment app

Check Status:

kubectl rollout status deployment app

27. CI/CD Deployment Script

git pull

npm install

npm run build

systemctl restart app

28. Blue-Green Deployment Script

CURRENT=$(cat active_env)

if [ "$CURRENT" = "blue" ]
then
   deploy green
else
   deploy blue
fi

Switch Traffic:

update_load_balancer

29. Production Health Check Script

#!/bin/bash

check_disk() {
    usage=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')

    if [ $usage -gt 80 ]
    then
        echo "Disk Alert"
    fi
}

check_memory() {
    free -m
}

check_cpu() {
    top -bn1 | head -5
}

check_disk
check_memory
check_cpu

30. Enterprise Deployment Framework

main() {

   validate_config

   backup_current_release

   deploy_application

   run_health_checks

   switch_traffic

   notify_team
}

main

This structure is used in production deployment systems.

Check here for an in-depth learning series on Bash scripting.

👉 Bash Shell Overview for beginners

👉 Bash Shell Scripting Day 1 — Beginner

👉 Bash Scripting — DAY 2 — Complete Practical Guide for DevOps

👉 Bash Scripting — DAY 3 — Advanced Shell Scripting for DevOps

🚀 100+ Handy Knowledge Hub: All-in-One Linux, DevOps & Automation Blogs

👉 **Click here to read all my topic-wise blogs on a single page**

***🐧 Linux Server Configuration — Complete Administrator’s Guide (Beginner → Advanced → Production)***

***🏆 Ultimate DevOps & SRE Learning Hub (2026 Edition) — 100% Free, Real-World Knowledge***

***☸️ Kubernetes & 🐳 Docker Mastery Hub (2026 Edition)***

***🏆 DevOps/SRE, Linux Admin Interview Preparation Hub (2026 Edition) : 500+ Questions from Linux to SRE***

🌟 Final Note

This single page is designed to be:

  • 📌 Bookmarked
  • 📌 Shared
  • 📌 Used daily

Thank you for reading! 😊🚀

If you’re a Linux admin, DevOps engineer, cloud engineer, or SRE — this page is your personal technical library.

👏 If it helped you, clap & share 💬 Drop a comment if you want a topic-wise PDF or roadmap next

🐳Happy Learning & Troubleshooting!


메타데이터
post_id
224f3c532cad
slug
advanced-shell-scripting-part-2-224f3c532cad
url
https://blog.devops.dev/advanced-shell-scripting-part-2-224f3c532cad
canonical_url
https://blog.devops.dev/advanced-shell-scripting-part-2-224f3c532cad
author_url
https://medium.com/@tushar.jadhav29
status
ok
fetched_at
2026-07-15 11:23:19