Featured image of post Bash Script Basics for DevOps

Bash Script Basics for DevOps

Learn Bash Script from the ground up: what Bash is, how to write your first script, use variables, basic commands, debugging, and a DevOps-flavored system check practice.

Bash Script Basics for DevOps

In DevOps work, many daily tasks are repetitive: checking disk usage, verifying whether a service is still running, backing up configuration files, collecting logs, or running a simple deploy sequence. If you do everything manually step by step, it is easy to forget commands, mistype options, or lose time when you need to repeat the same work across multiple servers.

Bash Script helps package those commands into a file that can be run again and again. This is a foundational skill before going deeper into CI/CD, Docker, Kubernetes, Cloud CLI, or more complex automation.


What is Bash?

Bash stands for Bourne Again SHell — a popular shell program on Linux and macOS. A shell receives commands from users, interprets them, and asks the operating system to execute them.

When you type commands in a terminal such as:

1
2
3
ls -lah
df -h
cat /etc/os-release

you are interacting with a shell. When you put multiple commands into a text file, that file becomes a shell script. According to the official GNU Bash documentation, a shell script is a text file containing shell commands; when Bash runs it, Bash reads and executes the commands in the file, then exits.


Why should DevOps engineers know Bash?

Bash does not replace tools like Ansible, Terraform, or Jenkins, but it is a very useful glue layer for connecting tools together.

DevOps situationHow Bash helps
Quick server checksCombine hostname, df, free, and uptime in a script
Small application deploymentRun code pull, build, and service restart in order
Log or temporary file cleanupAutomatically find and delete old files on a schedule
CI/CD pipelineWrite build/test/deploy steps with clear exit codes
Tool integrationCall docker, kubectl, aws, curl, and jq from one script

The strength of Bash is that it is available on most Linux servers and does not require a complicated runtime installation. For small tasks, a 20–50 line script is often fast enough, easy to read, and simple to add to a pipeline.


Your first script with hello.sh

Create the hello.sh file:

1
vi hello.sh

Add the following content:

1
2
3
4
#!/usr/bin/env bash

echo "Hello DevOps!"
echo "Script is running on host: $(hostname)"

Run the script with Bash:

1
bash hello.sh

Or grant execute permission and run it directly:

1
2
chmod +x hello.sh
./hello.sh

What is the shebang for?

The first line:

1
#!/usr/bin/env bash

is called a shebang. It tells the operating system which program should be used to run this file. The #!/usr/bin/env bash form is often more flexible than #!/bin/bash because Bash may be installed in different locations depending on the system.


Working with variables

Variables help a script store values for reuse. In Bash, variable assignment syntax has no spaces around the = sign:

1
2
3
4
5
APP_NAME="manager-blog"
ENVIRONMENT="staging"

echo "Application: $APP_NAME"
echo "Environment: ${ENVIRONMENT}"

You should use ${VAR} when combining variables with strings so Bash does not misunderstand the variable name:

1
2
3
4
BACKUP_DIR="/backup"
APP_NAME="manager-blog"

echo "Backup file: ${BACKUP_DIR}/${APP_NAME}.tar.gz"

Common special variables

VariableMeaning
$0Name of the script being executed
$1First argument passed to the script
$#Total number of arguments
$?Exit code of the previous command
$$Process ID of the current shell

Example script that accepts an environment name:

1
2
3
4
5
#!/usr/bin/env bash

ENVIRONMENT="${1:-dev}"

echo "Deploying to environment: ${ENVIRONMENT}"

Try running it:

1
bash deploy-message.sh staging

If no argument is passed, ${1:-dev} uses dev as the default value.


Basic commands you will use often

echo

echo prints content to the screen and is very useful for showing status messages in scripts:

1
2
echo "Starting system check..."
echo "Done."

read

read reads input from the user:

1
2
3
4
#!/usr/bin/env bash

read -r -p "Enter the service name to check: " SERVICE_NAME
echo "You want to check service: ${SERVICE_NAME}"

In scripts, you should use read -r to prevent backslashes from being interpreted unexpectedly.

cat

cat is commonly used to quickly view file content or create a sample file:

1
cat /etc/os-release

Create a configuration file with a here-document:

1
2
3
4
cat > app.env <<'EOF'
APP_ENV=staging
APP_PORT=8080
EOF

ls

ls lists files and directories:

1
ls -lah /var/log

In production scripts, be careful when processing complex file lists, especially file names containing spaces. Later articles about file handling will go deeper into this topic.


DevOps practice: check_system.sh

The example below creates a script for quickly checking system information: hostname, operating system kernel, uptime, CPU load, memory, and root disk usage.

Create the file:

1
vi check_system.sh

Script content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env bash
set -euo pipefail

echo "========================================"
echo "  System quick check"
echo "========================================"

HOSTNAME_VALUE="$(hostname)"
KERNEL_VALUE="$(uname -sr)"
UPTIME_VALUE="$(uptime -p 2>/dev/null || uptime)"
DISK_USAGE_PERCENT="$(df -P / | awk 'NR==2 {gsub("%", "", $5); print $5}')"

if [[ -r /proc/loadavg ]]; then
  LOAD_1M="$(awk '{print $1}' /proc/loadavg)"
else
  LOAD_1M="$(uptime | awk -F'load average: ' '{print $2}' | awk -F',' '{print $1}')"
fi

echo "Hostname : ${HOSTNAME_VALUE}"
echo "Kernel   : ${KERNEL_VALUE}"
echo "Uptime   : ${UPTIME_VALUE}"
echo "Load 1m  : ${LOAD_1M}"
echo "Disk /   : ${DISK_USAGE_PERCENT}%"

if command -v free >/dev/null 2>&1; then
  echo
  echo "Memory:"
  free -h
else
  echo
  echo "Memory: command 'free' is not available on this system."
fi

echo
if (( DISK_USAGE_PERCENT >= 80 )); then
  echo "WARNING: Disk usage for / has exceeded the 80% threshold."
  exit 1
else
  echo "OK: Disk usage for / is still within the safe threshold."
fi

Grant permission and run it:

1
2
chmod +x check_system.sh
./check_system.sh

The output may look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
========================================
  System quick check
========================================
Hostname : webserver-01
Kernel   : Linux 6.8.0-31-generic
Uptime   : up 3 days, 4 hours, 12 minutes
Load 1m  : 0.21
Disk /   : 42%

Memory:
               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       1.1Gi       720Mi        32Mi       2.0Gi       2.4Gi
Swap:          2.0Gi          0B       2.0Gi

OK: Disk usage for / is still within the safe threshold.

Notable points in this script:

  • set -euo pipefail helps the script stop when it encounters an error, uses an undefined variable, or when a command in a pipeline fails.
  • df -P / uses POSIX output format so the result is more stable when parsed with awk.
  • command -v free checks whether the free command exists before calling it.
  • exit 1 when disk usage crosses the threshold helps CI/CD or cron jobs recognize that the script is reporting a problem.

Debug scripts with bash -n and bash -x

As scripts get longer, you need to check syntax errors and see how Bash executes each command.

Check syntax with bash -n

1
bash -n check_system.sh

The -n option tells Bash to read commands but not execute them. It is useful for detecting issues such as missing fi, missing quotes, or incorrect loop syntax.

Trace commands with bash -x

1
bash -x check_system.sh

The -x option prints each command after Bash expands variables and before executing it. This is a very quick way to see what values your script is receiving.

You can also enable and disable tracing for a specific section:

1
2
3
set -x
DISK_USAGE_PERCENT="$(df -P / | awk 'NR==2 {gsub("%", "", $5); print $5}')"
set +x

Security note: Do not enable set -x around code that handles secrets or tokens, because sensitive values may be printed to logs.


Best practices when you are new to Bash

A few small habits will make scripts easier to read and less error-prone:

  • Always quote variables: use "${VAR}" instead of $VAR, especially when values may contain spaces.
  • Use meaningful variable names: DISK_USAGE_PERCENT is clearer than D or VALUE.
  • Use set -euo pipefail intentionally: it is good for many automation scripts, but you should understand the error flow before using it in complex scripts.
  • Check commands before using them: command -v docker, command -v jq, command -v kubectl.
  • Do not hardcode sensitive information: tokens, passwords, and private keys should come from environment variables or a secret manager.
  • Return clear exit codes: exit 0 for success and non-zero values for errors so cron/CI/CD can detect the result.
  • Run bash -n before deployment: it is a quick but very useful check.

Implementation notes

  • When applying this to your project, start with small scripts that have clear goals: check_system.sh, backup_config.sh, restart_service.sh. Do not try to write a very large script right away.
  • Best practices:
    • Put scripts in a dedicated directory such as scripts/ or ops/.
    • Add a short README describing how to run the script, its input parameters, and sample output.
    • For scripts running in CI/CD, always check the exit code and log enough information for debugging.
  • Troubleshooting:
    • Permission denied? → Run chmod +x script.sh or use bash script.sh.
    • command not found? → Check whether the command is installed and whether PATH is correct.
    • Script behaves differently between local and server? → Check the Bash version with bash --version and the operating system with cat /etc/os-release.

🎯 Conclusion

Bash Script is a foundational but very practical skill in DevOps. With only a few basic commands like echo, read, cat, and ls, combined with variables and exit-code checks, you can already automate many daily operations tasks.

In the next article, we will explore conditions in Bash with if/elif/else, comparison operators, file checks, and case...esac — the building blocks that help scripts start handling logic more flexibly. 🚀


References