Bash Shell Scripting: A Ready Reckoner for Linux, Unix and Windows

📅 Nov 12, 2013
⏱️ 15 min read

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:

OperatorMeaning
-e fileFile or path exists.
-f fileRegular file exists.
-d pathDirectory exists.
-r fileFile is readable.
-w fileFile is writable.
-x fileFile 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

CommandPurposeExample
grepSearch text patterns.grep -i "error" app.log
findLocate files and directories.find /var/log -type f
awkProcess structured text.awk '{print $1}' file.txt
sedTransform text.sed 's/old/new/g' file.txt
sortSort lines.sort names.txt
uniqRemove adjacent duplicate lines.sort names.txt | uniq
cutExtract delimited fields.cut -d: -f1 /etc/passwd
wcCount lines, words or bytes.wc -l app.log
xargsPass input as command arguments.printf '%s\n' *.log | xargs -r ls -l
teeWrite output to a file and standard output.command | tee output.log
curlMake HTTP requests.curl -fsS https://example.com
jqProcess 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
Important: Check the exit status of commands that matter. A script that continues after a failed deployment, backup or configuration change can create serious operational problems.

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:

OptionEffect
set -eExit on many unhandled command failures, subject to Bash's documented exceptions.
set -uTreat unset variables as errors.
set -o pipefailReturn a failure status when a command within a pipeline fails.
set -EAllow 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 languageLinuxUnix and Unix-like systemsWindowsBest use case
BashWidely available.Available on many systems; not guaranteed by default.WSL, Git Bash and compatible environments.Linux administration, DevOps and CI/CD.
POSIX shWidely 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.
ZshAvailable on many distributions.Available on macOS and other Unix-like systems.Requires an additional environment.Interactive shell productivity and scripting.
FishAvailable as an additional shell.Available on many Unix-like systems.Requires an additional environment.User-friendly interactive command-line work.
PowerShellPowerShell 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.
PerlAvailable 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.
PythonWidely 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

FeatureBashPowerShellPerlPythonPOSIX sh
Primary strengthShell and command automation.System administration and structured objects.Text processing and regular expressions.General-purpose automation and application development.Portable Unix shell scripting.
Pipeline modelPrimarily text streams.Structured objects.Language-level data processing.Language-level data processing.Primarily text streams.
Variables$name$name$namename$name
FunctionsYesYesYesYesYes
Regular expressionsThrough tools and Bash features.Supported through .NET.Strong built-in support.Built-in re module.Usually through utilities such as grep and sed.
JSON processingCommonly uses jq.Built-in cmdlets such as ConvertFrom-Json.Available through modules.Built-in json module.Usually requires external tools.
Cloud automationCLI tools and SDK integrations.Azure and other cloud modules.Available through modules and APIs.Extensive cloud SDK ecosystem.Primarily CLI tools.
PortabilityRequires 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 mktemp instead of predictable temporary filenames.
  • Use ShellCheck and bash -n before 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

TaskCommand
Current directorypwd
List filesls -lah
Search running processesps aux
Check disk usagedf -h
Check directory sizedu -sh /var/log
Check memoryfree -h
Find listening portsss -lntup
Search logsgrep -i "error" app.log
Find files modified in the last dayfind /path -type f -mtime -1
Check HTTP endpointcurl -fsS https://example.com
Schedule recurring taskscrontab -e
Inspect a systemd servicesystemctl status nginx
Validate Bash syntaxbash -n script.sh
Trace script executionbash -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.

Key takeaway: A good shell script does more than execute commands. It validates inputs, handles failures, records useful information and produces predictable results.
Topics & Tags
Bash Linux Unix Shell Scripting DevOps Automation Cheat Sheet PowerShell Python