Linux shell scripting turns a sequence of terminal commands into a reusable automation tool. A script can collect system information, check services, process files, create backups or run deployment steps consistently.
This guide teaches the core syntax through practical Bash examples. You should be comfortable navigating directories and running basic Linux commands before starting.
What Is a Linux Shell Script?
A Linux shell script is a text file containing commands that a shell reads and executes in order. Bash is commonly used for scripting on Linux, although other shells such as sh, zsh and ksh are also available.
Think of the execution flow as a diagram in words:
Script file → Bash interpreter → command expansion → command execution → output and exit status
The shell reads a line, expands variables and command substitutions, runs the command, and records whether it succeeded. This makes it possible to use decisions, loops and error handling around normal Linux commands.
Shell script versus interactive command
| Interactive terminal | Shell script |
|---|---|
| Commands are entered manually | Commands are stored in a file |
| Suitable for one-time work | Suitable for repeatable work |
| Results depend on manual steps | Logic can validate inputs and failures |
| Difficult to schedule as a workflow | Can run through cron or another scheduler |
Shell scripts are useful in DevOps because they connect operating system commands, cloud CLIs and deployment tools. Learners building these operational skills can explore the practical modules in the AWS DevOps course.
How Do You Create and Run a Bash Script?
Create a text file, add a Bash shebang, grant execute permission and run the file. The shebang tells Linux which interpreter should process the script.
Create a file named hello.sh:
#!/usr/bin/env bash
echo "Hello from Bash"
date
whoamiThe first line is the shebang:
#!/usr/bin/env bashUsing /usr/bin/env bash finds Bash through the current PATH. A fixed #!/bin/bash shebang is also common when Bash is known to exist at that location.
Make the file executable and run it:
chmod +x hello.sh
./hello.shExample output:
Hello from Bash
Sun Oct 11 10:30:14 IST 2026
studentAlternatively, run it by passing the file directly to Bash:
bash hello.shThis method does not require execute permission, but Bash must have read permission on the file. For a deeper explanation of mode bits and ownership, see Linux file permissions and ownership.
How Do Variables and Command Substitution Work?
Variables store text or numbers for later use, while command substitution captures the output of another command. Do not place spaces around the = sign in a Bash variable assignment.
server_name="web01"
backup_dir="/var/backups/app"
current_date=$(date +%F)
echo "Server: $server_name"
echo "Backup directory: $backup_dir"
echo "Date: $current_date"Double quotes allow variables to expand. Single quotes preserve the text literally:
name="Anita"
echo "User: $name"
echo 'User: $name'Output:
User: Anita
User: $nameQuote variable expansions unless you specifically need word splitting or wildcard expansion:
cp "$source_file" "$destination_dir/"Without quotes, a path such as /home/student/project files would be treated as multiple arguments.
Environment variables
Environment variables are inherited by child processes. Common examples include HOME, USER, PATH and SHELL.
echo "$HOME"
echo "$PATH"
export APP_ENV="testing"Use export when programs launched by the script need access to the variable.
How Does a Script Accept Input and Arguments?
A script can accept positional arguments from its command line or request interactive input with read. Positional arguments are generally better for unattended automation because they do not require a person at the terminal.
Create greet.sh:
#!/usr/bin/env bash
name=${1:-"Guest"}
environment=${2:-"development"}
echo "Hello, $name"
echo "Environment: $environment"Run it with two arguments:
./greet.sh Ravi productionImportant argument variables include:
| Variable | Meaning |
|---|---|
$0 | Script name |
$1 to $9 | Positional arguments |
$# | Number of arguments |
"$@" | All arguments, preserving boundaries |
$? | Exit status of the previous command |
For interactive input:
read -r -p "Enter the service name: " service_name
echo "Checking $service_name"The -r option prevents backslashes from being interpreted as escape characters. Avoid interactive prompts in scripts intended for cron, CI/CD or remote automation.
How Do Conditions and Exit Codes Control a Script?
Conditions let a script choose an action based on file state, values or command results. Linux commands normally return exit status 0 for success and a non-zero status for failure.
The flow can be pictured as:
Test condition → true branch runs → continue Test condition → false branch runs → alternative action or exit
This example validates a directory argument:
#!/usr/bin/env bash
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <directory>" >&2
exit 2
fi
target_dir=$1
if [[ -d "$target_dir" ]]; then
echo "Directory exists: $target_dir"
else
echo "Directory not found: $target_dir" >&2
exit 1
fiCommon test operators are:
| Test | Meaning |
|---|---|
-f FILE | File exists and is a regular file |
-d DIR | Directory exists |
-r FILE | Current process can read the file |
-w FILE | Current process can write the file |
-z STRING | String is empty |
STRING1 == STRING2 | Strings are equal inside [[ ]] |
NUMBER1 -gt NUMBER2 | First number is greater |
Use && to run a command after success and || to react to failure:
mkdir -p "$HOME/reports" && echo "Report directory is ready"
systemctl is-active --quiet sshd || echo "sshd is not active"The SSH service may be named ssh instead of sshd on some distributions.
How Do Loops Automate Repeated Tasks?
Loops repeat commands over a list or while a condition remains true. A for loop is suitable for known items, while a while loop is useful when reading files or testing changing conditions.
For loop example
#!/usr/bin/env bash
for host in web01 web02 db01; do
echo "Processing $host"
doneTo loop over script arguments safely:
for item in "$@"; do
printf 'Argument: %s\n' "$item"
doneReading a file line by line
Suppose servers.txt contains one hostname per line:
web01
web02
db01Read it with:
while IFS= read -r server; do
[[ -z "$server" ]] && continue
echo "Checking $server"
done < servers.txtIFS= preserves leading and trailing whitespace, while read -r prevents backslash processing. The input redirection keeps the loop in the current shell, which matters if variables changed inside the loop are needed later.
Why Should Bash Scripts Use Functions?
Functions group related commands into reusable units, reducing duplication and making scripts easier to test. A function can receive positional arguments and return a command-style exit status.
#!/usr/bin/env bash
log_message() {
local level=$1
local message=$2
printf '%s [%s] %s\n' "$(date '+%F %T')" "$level" "$message"
}
check_service() {
local service=$1
if systemctl is-active --quiet "$service"; then
log_message "INFO" "$service is active"
return 0
else
log_message "ERROR" "$service is inactive"
return 1
fi
}
check_service "sshd"local limits a variable to its function. return is used for numeric status codes; use echo or printf if a function must produce text that will be captured.
How Can You Build a Practical Backup Script?
A reliable backup script should validate its source, create the destination, generate a unique archive name and report failures. It should also quote paths and return a non-zero status when the backup cannot be created.
The following script archives a directory and removes archives older than seven days:
#!/usr/bin/env bash
set -u
source_dir=${1:-"/etc"}
backup_dir=${2:-"$HOME/backups"}
timestamp=$(date '+%Y%m%d_%H%M%S')
archive="$backup_dir/backup_$timestamp.tar.gz"
log() {
printf '%s %s\n' "$(date '+%F %T')" "$1"
}
if [[ ! -d "$source_dir" ]]; then
log "ERROR: Source directory does not exist: $source_dir" >&2
exit 1
fi
if ! mkdir -p "$backup_dir"; then
log "ERROR: Cannot create backup directory: $backup_dir" >&2
exit 1
fi
log "Creating backup of $source_dir"
if tar -czf "$archive" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"; then
log "Backup created: $archive"
else
log "ERROR: Backup failed" >&2
rm -f "$archive"
exit 1
fi
find "$backup_dir" -type f -name 'backup_*.tar.gz' -mtime +7 -delete
log "Old backup cleanup completed"Run it with defaults:
./backup.shOr provide source and destination paths:
./backup.sh /opt/application /mnt/backup/applicationThe -C option changes directory before archiving, preventing the archive from storing a long absolute-style path. set -u reports an error when an unset variable is expanded. Stricter modes such as set -e and set -o pipefail are useful, but they should be introduced carefully because expected non-zero results can terminate a script unexpectedly.
How Do You Schedule a Script with Cron?
Cron runs commands on a time-based schedule without interactive input. Use absolute paths, redirect output to a log and confirm that the cron environment contains every required variable.
Edit the current user's crontab:
crontab -eRun the backup every day at 2:30 AM:
30 2 * * * /home/student/scripts/backup.sh /etc /home/student/backups >> /home/student/logs/backup.log 2>&1The five scheduling fields represent:
minute hour day-of-month month day-of-week commandCron usually has a smaller PATH than an interactive shell. Use absolute command paths when needed, create the log directory beforehand, and ensure the cron user has permission to access every source and destination.
How Do You Troubleshoot Shell Scripts?
Start by checking syntax, permissions, interpreter paths, variables and command exit codes. Bash tracing then shows expanded commands in execution order, which helps locate the failing line.
1. Check syntax without running the script
bash -n backup.shNo output normally means that Bash found no syntax error.
2. Enable execution tracing
bash -x backup.shOr trace only part of a script:
set -x
command_to_debug
set +xDo not use tracing around passwords, tokens or other secrets because expanded values may appear in logs.
3. Inspect the exit code
./backup.sh
echo "$?"A non-zero value indicates failure. Capture it immediately because the next command replaces $?.
4. Check common execution errors
| Error | Likely cause | Fix |
|---|---|---|
Permission denied | Execute bit missing or directory inaccessible | Use chmod +x script.sh and check parent permissions |
bad interpreter | Invalid shebang or Windows line endings | Correct the shebang; use sed -i 's/\r$//' script.sh |
command not found | Incorrect command or limited PATH | Check with command -v name; use an absolute path if required |
unbound variable | Unset variable with set -u | Initialise the variable or use ${value:-default} |
| Works manually, fails in cron | Different environment, path or permissions | Use absolute paths and redirect logs |
ShellCheck can identify quoting errors, unsafe expansions and portability problems when it is installed:
shellcheck backup.shFor problems involving storage, processes, services or system logs, follow a structured Linux troubleshooting workflow instead of changing the script at random.
What Are the Main Shell Scripting Practices to Remember?
Good scripts validate input, quote variable expansions, check failures and produce useful logs. Begin with small scripts, test each command independently, and only then schedule or deploy the automation.
- Use a clear shebang.
- Prefer
printffor predictable formatted output. - Quote paths and variables as
"$variable". - Use
"$@"when forwarding arguments. - Send error messages to standard error with
>&2. - Return meaningful non-zero exit codes on failure.
- Avoid placing passwords or access keys directly in scripts.
- Test with
bash -n,bash -xand ShellCheck. - Use version control to review script changes.
Summary
Linux shell scripting basics include interpreters, variables, arguments, conditions, loops, functions, exit codes and scheduling. Together, these features turn normal Linux commands into repeatable automation for backups, monitoring, file processing and operational tasks.
To practise shell automation alongside Git, CI/CD, containers and cloud operations, enquire about curriculum and batch details for the AWS DevOps course.
Reviewed by Network Rhinos Linux and DevOps trainers.
Related reading: SSH Hardening on Linux: Keys, Settings and Mistakes
