From Zero to DevOps: Git, Shell Scripting & Bash Fundamentals You Must Know

A structured deep-dive into version control systems, shell scripting concepts, variables, conditions, functions, and redirections — built for DevOps engineers starting their journey.
Table of Contents
Why You Shouldn't Store Code Without VCS {#why-not-without-vcs}
Storing code without a Version Control System (VCS) is a recipe for disaster in any team or production environment. Here's what you lose:
| What You Lose | Impact |
|---|---|
| Backup & Security | No recovery if your local machine crashes or files get deleted |
| Version History | Can't roll back to a working state after a bad change |
| Collaboration | Multiple developers overwriting each other's work |
| Change Tracking | No audit trail of who changed what and why |
| Branching | Every "branch" becomes a separate physical copy of the codebase — chaotic and unmanageable |
Bottom line: Without VCS, you're one mistake away from losing everything.
Centralized vs Distributed Version Control {#centralized-vs-distributed}
Centralized VCS — SVN (Subversion)
In a centralized model, a single central server holds the entire history of the project — all commits, branches, and tags.
Developer A ──┐
Developer B ──┼──► Central SVN Server (Single Source of Truth)
Developer C ──┘
Drawbacks:
Single point of failure — if the central server goes down, no one can commit, branch, or retrieve old versions
No offline work — you need network access for almost every operation
Entire history lives on one machine — disaster recovery is risky
Distributed VCS — Git
In a distributed model, every developer has a full copy of the repository — including complete history, all branches, and all commits.
Developer A (full repo) ──┐
Developer B (full repo) ──┼──► Remote GitHub/GitLab/Bitbucket
Developer C (full repo) ──┘
Advantages:
No single point of failure — if the central server crashes, any developer's laptop can restore the entire repo
Work offline — commit, branch, and view history without a network connection
Faster operations — most operations are local
Git is the concept. GitHub, GitLab, and Bitbucket are companies that built platforms on top of Git with their own UIs. The underlying Git commands and features remain the same across all of them.
Git: The Distributed VCS {#git-the-distributed-vcs}
Core Git Workflow
Every change you make travels through three stages before reaching the remote repository:
Working Directory ──► Staging Area ──► Local Repo ──► Remote Repo
(edit files) (git add) (git commit) (git push)
Essential Git Commands
# Clone a remote repository to your local machine
git clone https://github.com/daws-90s/shell-practice.git
# Stage a specific file
git add <file-name>
# Stage ALL changed files
git add .
# Commit staged files to your local repo
git commit -m "meaningful commit message"
# Push committed changes to the remote main branch
git push origin main
The main Branch
main(ormaster) always points to production-ready codeNever push broken or untested code directly to
mainUse feature branches for development, merge into
mainonly when stable
Chaining Commands (Quick Push)
git add . ; git commit -m "hello world script created" ; git push origin main
Pro Tip: Spend 90% of your time thinking through the logic, and 10% actually writing code. Good thinking = fewer bugs.
Shell Scripting Fundamentals {#shell-scripting-fundamentals}
What is a Shell Script?
A shell script (also called a bash script) is a file containing a sequence of shell commands that are executed in order. It automates repetitive tasks on Linux/Unix systems.
The Shebang Line
#!/bin/bash
Must be the very first line of every shell script
Tells the OS which interpreter to use to execute the script (
/bin/bashin this case)Without it, the OS may not know how to execute the file
Thinking Before Coding — Pseudocode
Before writing a single line of bash, write your logic in plain English. This is called pseudocode.
Example — Check if today is a holiday:
today = thursday
if (today != sunday) {
print "go to school"
} else {
print "take holiday, enjoy"
}
Example — Check if a number is even or odd:
number = 11
reminder = 11 % 2 # remainder = 1
if (reminder == 0) {
print "number is even"
} else {
print "number is odd"
}
Think generally. Don't think about syntax first. Think about what needs to happen, then translate it to code.
Variables and Data Types {#variables-and-data-types}
Variable Assignment
VAR_NAME=VALUE # No spaces around the = sign
Important: Shell does not allow spaces around
=.VAR_NAME = VALUEwill throw an error.
DRY Principle — Don't Repeat Yourself
Store repeated values in variables. Change in one place, reflects everywhere.
# Bad — hardcoded everywhere
echo "Deploying to prod-server-01"
scp app.tar prod-server-01:/opt/
# Good — use a variable
SERVER="prod-server-01"
echo "Deploying to $SERVER"
scp app.tar $SERVER:/opt/
Ways to Assign Variables
# 1. Direct assignment
NAME="DevOps"
# 2. From command-line arguments
# sh script.sh Trump Iran
# \(1 = Trump, \)2 = Iran
# 3. Interactive input from user
read VAR_NAME
# 4. Capture output of a command
TIMESTAMP=$(date)
VAR_NAME=$(command)
Data Types in Shell
Shell treats everything as a string by default. This is unlike strongly-typed languages (Java, C++) where you must declare the type.
# Shell — no type declaration needed
NAME="siva123"
NUMBER=42
# Compare with Java
# String name = "siva123";
# int number = 42;
Real-world lesson: The Ariane 4 rocket used integer speed (9 km/s). The Ariane 5 rocket went faster (8.2 km/s as a float). When Ariane 5's float was stored in an integer variable, it caused an overflow, leading to the rocket's self-destruction 37 seconds after launch. Data types matter.
Special Shell Variables {#special-shell-variables}
Shell automatically creates and populates several variables that are extremely useful in scripting:
echo "All arguments passed: $@"
echo "Number of arguments: $#"
echo "First argument: $1"
echo "Script name: $0"
echo "Current user: $USER"
echo "Current directory: $PWD"
echo "Home directory: $HOME"
echo "PID of current script: $$"
# For background processes
sleep 5 &
echo "PID of last background process: $!"
wait $!
echo "Current line number: $LINENO"
echo "Script ran for: $SECONDS seconds"
echo "Random number: $RANDOM"
| Variable | Description |
|---|---|
\(1, \)2, ... |
Positional arguments passed to the script |
$@ |
All arguments as separate words |
$# |
Count of arguments passed |
$0 |
Name of the script itself |
$$ |
PID of the current script |
$! |
PID of the last background process |
$USER |
Currently logged-in user |
$PWD |
Present working directory |
$HOME |
Home directory of the current user |
$LINENO |
Current line number in the script |
$SECONDS |
Seconds elapsed since script started |
$RANDOM |
A random number between 0–32767 |
Exit Codes {#exit-codes}
Every command in Linux returns an exit code when it finishes. This tells you whether it succeeded or failed.
$? # Holds the exit code of the last executed command
| Exit Code | Meaning |
|---|---|
0 |
Success — command ran without errors |
1–127 |
Failure — various error conditions |
# Example usage
apt install mysql-server
if [ $? -eq 0 ]; then
echo "MySQL installed successfully"
else
echo "Installation failed"
fi
Best practice: Always check
$?after critical commands in your scripts. Never assume a command succeeded.
Conditions in Shell {#conditions-in-shell}
Basic Syntax
# Simple if
if [ expression ]; then
# commands if true
fi
# if-else
if [ expression ]; then
# commands if true
else
# commands if false
fi
# if-elif-else
if [ expression1 ]; then
# commands
elif [ expression2 ]; then
# commands
elif [ expression3 ]; then
# commands
else
# commands if none matched
fi
Comparison Operators
# Numeric comparisons
-eq # equal to
-ne # not equal to
-gt # greater than
-lt # less than
-ge # greater than or equal to
-le # less than or equal to
# String comparisons
== # equal
!= # not equal
-z # string is empty
-n # string is not empty
# File checks
-f # is a regular file
-d # is a directory
-e # file/directory exists
Example — Number Comparison
NUMBER=20
if [ $NUMBER -gt 10 ]; then
echo "Number $NUMBER is greater than 10"
else
echo "Number $NUMBER is less than 10"
fi
Real-World Example — MySQL Install Script
#!/bin/bash
# 1. Check if running as root
if [ "$USER" != "root" ]; then
echo "ERROR: This script must be run as root. Exiting."
exit 1
fi
# 2. Check if MySQL is already installed
if command -v mysql &>/dev/null; then
echo "MySQL is already installed. Skipping."
exit 0
fi
# 3. Install MySQL
echo "Installing MySQL..."
apt install mysql-server -y
# 4. Check if installation succeeded
if [ $? -eq 0 ]; then
echo "SUCCESS: MySQL installed successfully."
else
echo "FAILURE: MySQL installation failed."
exit 1
fi
Functions {#functions}
Functions let you package reusable logic, avoid code duplication, and keep your scripts clean.
Syntax
FUNC_NAME() {
# \(1, \)2 are arguments passed to the function
echo "First arg: $1"
echo "Second arg: $2"
}
# Call the function
FUNC_NAME arg-1 arg-2
Benefits of Functions
Less code — write once, use many times (DRY principle)
Less time — modify logic in one place
Fewer bugs — fewer places for errors to hide
Example — Reusable Logger Function
#!/bin/bash
log() {
local LEVEL=$1
local MESSAGE=$2
echo "[\((date '+%Y-%m-%d %H:%M:%S')] [\)LEVEL] $MESSAGE"
}
install_package() {
local PACKAGE=$1
log "INFO" "Installing $PACKAGE..."
apt install "$PACKAGE" -y
if [ $? -eq 0 ]; then
log "SUCCESS" "$PACKAGE installed."
else
log "ERROR" "$PACKAGE installation failed."
fi
}
install_package mysql-server
install_package nginx
Redirections {#redirections}
Shell redirections let you control where a command's input comes from and where its output goes.
Redirection Operators
< # Redirect INPUT to a command
> # Redirect OUTPUT (overwrites file)
>> # Redirect OUTPUT (appends to file)
File Descriptors
| Descriptor | Meaning |
|---|---|
0 |
Standard Input (stdin) |
1 |
Standard Output (stdout) — success |
2 |
Standard Error (stderr) — failure |
& |
Both stdout and stderr |
Common Examples
# Redirect only success output to a file
ls -l 1> output.log
# Redirect only error output to a file
ls /nonexistent 2> error.log
# Redirect both success and error to the same file
ls -l &> all-output.log
# Append output (don't overwrite)
echo "New log entry" >> output.log
# Discard errors (send to /dev/null)
command 2>/dev/null
# Redirect both stdout and stderr, append
command >> all.log 2>&1
Practical Script with Timing
#!/bin/bash
TIMESTAMP_START=$(date +%s)
echo "Script started at: $(date)"
# ... your script logic here ...
TIMESTAMP_END=$(date +%s)
echo "Script completed at: $(date)"
echo "Time taken: $((TIMESTAMP_END - TIMESTAMP_START)) seconds"
Summary
Git Workflow: Edit → git add → git commit → git push
Script Flow: Shebang → Variables → Conditions → Functions → Redirections
Debug Habit: Always check $? after critical commands
Code Hygiene: Follow DRY — variables over hardcoded values
Think First: Write pseudocode before bash code
Happy scripting! If you found this helpful, drop a ❤️ and share it with someone starting their DevOps journey.



