Bash Shell Scripting: A Ready Reckoner for Linux, Unix and Windows
A practical cheat sheet for system administrators, DevOps engineers, cloud architects and developers. Shell scripting remains one of the most effective ways to automate repetitive IT operations. Whether you are managing Linux servers, administering Unix systems, automating cloud infrastructure or working with Windows, understanding shell scripting helps you save time, reduce manual errors and improve operational consistency.
Bash (Bourne Again SHell) is one of the most widely used shells in Linux environments. It is particularly useful for server administration, log analysis, backup automation, deployment pipelines, system monitoring and cloud operations.
1. Bash scripting essentials
A Bash script is a text file containing commands that the Bash interpreter executes sequentially.
Create and execute your first script.
#!/usr/bin/env bash
# Display a welcome message.
echo "Welcome to Bash scripting!"
# Display the current user and hostname.
echo "User: $(whoami)"
echo "Host: $(hostname)"
echo "Date: $(date)"
exit 0
Save the file as hello.sh and execute it:
chmod +x hello.sh
./hello.sh
Alternatively, execute it explicitly through Bash:
bash hello.sh
The first line, called the shebang, specifies the interpreter. The chmod +x command grants executable permission, while exit 0 indicates successful completion.
2. Bash shell scripting ready reckoner
Variables and user input
name="Brits"
count=10
echo "Hello, $name"
echo "Count: $count"
read -r -p "Enter your name: " username
printf 'Welcome, %s\n' "$username"
Remember that Bash variable assignments must not contain spaces around =.
# Correct.
environment="production"
# Incorrect.
# environment = "production"
Use double quotes around variables to prevent unintended word splitting and wildcard expansion.
Command substitution and arithmetic
current_date=$(date +%F)
hostname=$(hostname)
echo "Date: $current_date"
echo "Hostname: $hostname"
count=5
count=$((count + 1))
echo "$count"
Use $(command) instead of the older backtick syntax for command substitution.
Conditional statements
if [[ -f "/etc/passwd" ]]; then
echo "File exists."
else
echo "File does not exist."
fi
Common file tests include:
| Operator | Meaning |
|---|---|
-e file | File or path exists. |
-f file | Regular file exists. |
-d path | Directory exists. |
-r file | File is readable. |
-w file | File is writable. |
-x file | File is executable. |
-z "$var" | String is empty. |
"$a" == "$b" | Strings are equal inside [[ ]]. |
Loops
For loop:
for server in web01 web02 web03; do
echo "Checking $server"
done
While loop:
count=1
while (( count <= 5 )); do
echo "Iteration: $count"
((count += 1))
done
Loop control:
break # Exit the loop.
continue # Skip to the next iteration.
Functions
Functions help organise reusable operations.
log_message() {
local level="$1"
local message="$2"
printf '[%s] %s: %s\n' \
"$(date '+%F %T')" "$level" "$message"
}
log_message "INFO" "Deployment started"
log_message "ERROR" "Configuration validation failed"
Use local for function-scoped variables and pass arguments explicitly.
Arrays
servers=("web01" "web02" "db01")
echo "First server: ${servers[0]}"
for server in "${servers[@]}"; do
printf 'Server: %s\n' "$server"
done
Bash arrays use zero-based indexing. Quoting "${servers[@]}" preserves individual elements containing spaces.
3. Essential Linux commands for Bash scripts
| Command | Purpose | Example |
|---|---|---|
grep | Search text patterns. | grep -i "error" app.log |
find | Locate files and directories. | find /var/log -type f |
awk | Process structured text. | awk '{print $1}' file.txt |
sed | Transform text. | sed 's/old/new/g' file.txt |
sort | Sort lines. | sort names.txt |
uniq | Remove adjacent duplicate lines. | sort names.txt | uniq |
cut | Extract delimited fields. | cut -d: -f1 /etc/passwd |
wc | Count lines, words or bytes. | wc -l app.log |
xargs | Pass input as command arguments. | printf '%s\n' *.log | xargs -r ls -l |
tee | Write output to a file and standard output. | command | tee output.log |
curl | Make HTTP requests. | curl -fsS https://example.com |
jq | Process JSON data. | jq '.status' response.json |
These tools are especially valuable when building log-processing scripts, monitoring checks, deployment automation and incident-response utilities.
4. Pipes, redirection and exit codes
Pipes and redirection allow you to combine commands into automated workflows.
# Redirect standard output.
command > output.log
# Append standard output.
command >> output.log
# Redirect standard error.
command 2> error.log
# Redirect both standard output and standard error.
command > output.log 2>&1
# Send output to another command.
ps aux | grep '[n]ginx'
Bash uses exit status 0 to indicate success and a non-zero status to indicate failure.
if curl -fsS https://example.com -o /dev/null; then
echo "Endpoint is reachable."
else
echo "Endpoint check failed."
fi
5. Error handling and production-ready scripting
For automation that runs on production servers, error handling is essential.
#!/usr/bin/env bash
# Stop on errors, undefined variables and failed pipeline commands.
set -Eeuo pipefail
# Display a useful error message and line number.
trap 'printf "ERROR: Script failed at line %s.\n" "$LINENO" >&2' ERR
main() {
local service="nginx"
printf 'Checking service: %s\n' "$service"
if systemctl is-active --quiet "$service"; then
echo "Service is running."
else
echo "Service is not running." >&2
return 1
fi
}
main "$@"
Important options:
| Option | Effect |
|---|---|
set -e | Exit on many unhandled command failures, subject to Bash's documented exceptions. |
set -u | Treat unset variables as errors. |
set -o pipefail | Return a failure status when a command within a pipeline fails. |
set -E | Allow an ERR trap to be inherited by functions and certain other execution contexts. |
These options improve reliability but do not replace explicit error checking. Commands inside conditions, pipelines, functions and subshells require particular attention.
Script debugging
# Check Bash syntax without executing the script.
bash -n script.sh
# Execute with command tracing.
bash -x script.sh
# Display Bash version.
bash --version
# Check the last command's exit status.
echo "$?"
Use shellcheck script.sh to identify common scripting mistakes and portability issues. ShellCheck is a widely used static analysis tool for shell scripts.
6. Shell scripting and automation comparison: Linux, Unix and Windows
Bash is not the only option for system automation. Perl and Python are powerful scripting languages, while PowerShell is particularly useful for Windows administration. Choosing the right tool depends on portability, data-processing requirements, system integration and maintainability.
| Shell or scripting language | Linux | Unix and Unix-like systems | Windows | Best use case |
|---|---|---|---|---|
| Bash | Widely available. | Available on many systems; not guaranteed by default. | WSL, Git Bash and compatible environments. | Linux administration, DevOps and CI/CD. |
| POSIX sh | Widely available. | Common on POSIX-compliant systems. | Through compatible Unix environments. | Portable Unix shell scripts. |
| KornShell (ksh) | Available on many distributions. | Common in traditional enterprise Unix environments. | Requires a compatible environment. | Enterprise Unix and legacy automation. |
| Zsh | Available on many distributions. | Available on macOS and other Unix-like systems. | Requires an additional environment. | Interactive shell productivity and scripting. |
| Fish | Available as an additional shell. | Available on many Unix-like systems. | Requires an additional environment. | User-friendly interactive command-line work. |
| PowerShell | PowerShell 7+ is cross-platform. | Available on macOS and many Unix-like systems. | Windows PowerShell 5.1 and PowerShell 7+. | Windows administration, Azure and cross-platform automation. |
| Perl | Available through packages or installation. | Available on many Unix systems. | Available through distributions such as Strawberry Perl and ActivePerl. | Text processing, log analysis, regular expressions and legacy automation. |
| Python | Widely available, although versions vary. | Available on many Unix-like systems. | Available through official installers and other distributions. | General-purpose automation, APIs, cloud SDKs, data processing and application development. |
| Windows Command Prompt (cmd.exe) | Not native. | Not native. | Built into Windows. | Basic Windows administration and legacy commands. |
| Windows batch (.bat, .cmd) | Requires a compatible Windows environment or emulator. | Not native. | Native Windows command scripting. | Simple Windows automation and legacy workflows. |
Availability depends on the operating system version, installed packages and execution environment.
Bash vs PowerShell vs Perl vs Python vs POSIX sh
| Feature | Bash | PowerShell | Perl | Python | POSIX sh |
|---|---|---|---|---|---|
| Primary strength | Shell and command automation. | System administration and structured objects. | Text processing and regular expressions. | General-purpose automation and application development. | Portable Unix shell scripting. |
| Pipeline model | Primarily text streams. | Structured objects. | Language-level data processing. | Language-level data processing. | Primarily text streams. |
| Variables | $name | $name | $name | name | $name |
| Functions | Yes | Yes | Yes | Yes | Yes |
| Regular expressions | Through tools and Bash features. | Supported through .NET. | Strong built-in support. | Built-in re module. | Usually through utilities such as grep and sed. |
| JSON processing | Commonly uses jq. | Built-in cmdlets such as ConvertFrom-Json. | Available through modules. | Built-in json module. | Usually requires external tools. |
| Cloud automation | CLI tools and SDK integrations. | Azure and other cloud modules. | Available through modules and APIs. | Extensive cloud SDK ecosystem. | Primarily CLI tools. |
| Portability | Requires Bash for Bash-specific features. | PowerShell 7+ supports multiple operating systems. | Requires a compatible Perl installation. | Requires a compatible Python installation and dependencies. | Strong across POSIX-compliant systems. |
| Typical file extension | .sh | .ps1 | .pl | .py | .sh |
7. When should you choose Bash, Perl or Python?
Choose Bash when:
- You need to combine Linux commands such as grep, find, awk, sed and systemctl.
- You are writing deployment scripts, backups, scheduled jobs or container entrypoint scripts.
- You want lightweight automation with minimal dependencies on Linux systems.
Choose Perl when:
- You maintain existing Perl-based infrastructure and administration tools.
- You need sophisticated regular expressions and text transformation.
- You are processing logs, configuration files or large volumes of structured text in established Perl environments.
Choose Python when:
- You need to interact with REST APIs, JSON, databases or cloud SDKs.
- You are building reusable automation with testing, logging, exception handling and complex business logic.
- You need cross-platform scripts that run on Linux, Unix-like systems and Windows.
- Your automation is likely to evolve into a larger application.
Choose PowerShell when:
- You manage Windows services, Active Directory, Microsoft 365 or Azure.
- You need to manipulate structured objects instead of parsing command output as text.
- You want a consistent automation language across Windows and supported Unix-like platforms.
Choose POSIX sh when:
- Portability across different Unix-like operating systems is more important than Bash-specific conveniences.
- You are creating minimal shell scripts for environments with limited installed software.
Practical example: Count errors in an application log
Bash:
grep -ic 'error' application.log
Perl:
#!/usr/bin/env perl
use strict;
use warnings;
my $file = 'application.log';
my $count = 0;
open my $fh, '<', $file
or die "Cannot open $file: $!\n";
while (my $line = <$fh>) {
$count++ if $line =~ /error/i;
}
close $fh or die "Cannot close $file: $!\n";
print "Error count: $count\n";
Python:
#!/usr/bin/env python3
from pathlib import Path
log_file = Path("application.log")
try:
with log_file.open(encoding="utf-8", errors="replace") as handle:
error_count = sum(
1 for line in handle if "error" in line.lower()
)
print(f"Error count: {error_count}")
except OSError as exc:
raise SystemExit(f"Unable to read {log_file}: {exc}")
The Bash version is concise and uses an existing command-line utility. Perl offers powerful text-processing capabilities, while Python makes it straightforward to extend the script with additional logic, structured output, APIs and automated tests.
8. Practical Bash scripting best practices
Use this checklist before deploying a script to a production environment.
- Start with an appropriate shebang, such as
#!/usr/bin/env bash. - Quote variable expansions unless word splitting or glob expansion is intentional.
- Use
"$@"when forwarding script arguments. - Validate input and check required commands before execution.
- Check exit statuses for critical operations.
- Use meaningful variable and function names.
- Add logging with timestamps and severity levels.
- Use temporary files safely and clean them up when appropriate.
- Avoid hard-coded credentials and secrets.
- Use
mktempinstead of predictable temporary filenames. - Use ShellCheck and
bash -nbefore deployment. - Test scripts with missing files, invalid input, permission failures and network outages.
- Review the script's portability requirements before using Bash-specific syntax.
9. A quick reference for everyday automation
| Task | Command |
|---|---|
| Current directory | pwd |
| List files | ls -lah |
| Search running processes | ps aux |
| Check disk usage | df -h |
| Check directory size | du -sh /var/log |
| Check memory | free -h |
| Find listening ports | ss -lntup |
| Search logs | grep -i "error" app.log |
| Find files modified in the last day | find /path -type f -mtime -1 |
| Check HTTP endpoint | curl -fsS https://example.com |
| Schedule recurring tasks | crontab -e |
| Inspect a systemd service | systemctl status nginx |
| Validate Bash syntax | bash -n script.sh |
| Trace script execution | bash -x script.sh |
Note: Some commands and options vary between Linux distributions and Unix implementations. For example, free and systemctl are not universally available across Unix systems.
Conclusion
Bash shell scripting is a foundational skill for Linux system administration, cloud engineering and DevOps. Mastering variables, conditions, loops, functions, pipes, redirection and error handling enables you to automate repetitive tasks and build reliable operational workflows.
Start with small scripts, validate them using ShellCheck, test failure scenarios and gradually introduce logging, input validation and reusable functions. For cross-platform environments, understand the differences between Bash, POSIX sh and PowerShell before choosing an implementation.