Shell Scripting Fundamentals
AI-Generated Content
Shell Scripting Fundamentals
Shell scripting transforms you from a passive user of the command line into an active automator, turning repetitive, error-prone tasks into reliable, one-command solutions. It is the glue of system administration, the backbone of deployment pipelines, and a critical skill for any developer or engineer working in Linux or Unix-like environments. Mastering the fundamentals allows you to manage files, monitor systems, and orchestrate complex workflows efficiently, directly from the terminal.
The Foundation: Scripts, Shells, and the Shebang
A shell script is a plain text file containing a sequence of commands for a command-line interpreter, or shell. The most common shell on Linux and macOS is bash (Bourne Again SHell), which this guide focuses on. The script itself is not compiled; it is executed line-by-line by the shell.
The first line of any script is crucial: the shebang (#!). It tells the operating system which interpreter to use to run the script. For a bash script, this line is:
#!/bin/bashThis path points to the bash executable. Without a correct shebang, your script may fail or behave unexpectedly depending on the user's default shell.
To make a script executable, you use the chmod command:
chmod +x my_script.shYou can then run it with ./my_script.sh. This combination—shebang plus execute permission—is the entry point to all scripting.
Storing Data: Variables and User Input
Variables allow you to store and manipulate data. In bash, you create a variable by assigning a value with an equals sign (no spaces around it) and reference it with a dollar sign $.
filename="backup_$(date +%Y%m%d).tar.gz"
echo "Creating archive: $filename"Here, $(date +%Y%m%d) is command substitution, where the output of the date command is captured and used as part of the string.
To make a variable available to subprocesses (scripts or commands launched from your script), you must export it:
export DATABASE_URL="postgres://localhost/mydb"For interactivity, you can capture user input using the read command.
echo "Enter the target directory:"
read target_dir
if [ -d "$target_dir" ]; then
echo "Directory exists."
fiNotice the quotes around "$target_dir". This is essential to handle filenames or input with spaces correctly—a common pitfall we'll address later.
Controlling Execution Flow: Conditionals and Loops
Scripts need to make decisions and repeat actions. Conditionals use the if, then, elif, else, and fi structure. The test condition is placed within square brackets [ ] or double brackets [[ ]], which are preferred for fewer surprises with strings and patterns.
if [[ -f "/etc/config.yaml" ]]; then
echo "Configuration file found."
elif [[ -d "/etc/config.d" ]]; then
echo "Config directory found."
else
echo "No config found." >&2
exit 1
fiKey test operators include -f (file exists), -d (directory exists), -z (string is empty), and comparison operators like -eq (equal) for numbers and == for strings within [[ ]].
Loops automate repetition. The for loop iterates over a list or a range.
# Iterate over a list of names
for user in alice bob charlie; do
echo "Processing $user"
mkdir -p "/home/projects/$user"
done
# Iterate over files matching a pattern
for logfile in /var/log/*.log; do
echo "Analyzing: $logfile"
doneThe while loop runs as long as a condition is true, perfect for reading a file line-by-line or waiting for a condition.
while read -r line; do
echo "Read: $line"
done < "input.txt"Building Blocks: Functions and Exit Codes
As scripts grow, functions help you organize code into reusable blocks. They are defined with a name and parentheses, and called like any other command.
# Function definition
create_backup() {
local source_dir=$1 # 'local' restricts variable scope to the function
local backup_name="backup___MATH_INLINE_0__source_dir").tgz"
tar -czf "__MATH_INLINE_1__source_dir"
echo "Backup created: $backup_name"
}
# Function call
create_backup "/home/user/documents"Using local for variables inside functions prevents them from accidentally overwriting global variables.
Every command returns an exit code (0 for success, non-zero for failure). You can check this code in the special variable $? and use it for error handling. The exit command terminates the script with a given code.
grep "error" /var/log/syslog
if [[ $? -eq 0 ]]; then
echo "Errors found in syslog."
exit 1
fimkdir /tmp/backup && cp -r data /tmp/backup || echo "Backup failed!" >&2Common Pitfalls
- Unquoted Variables: Forgetting to quote variables is the top cause of bugs. Always use double quotes around variable expansions:
"$myvar". Without quotes, word-splitting and globbing will occur on the variable's value, causing commands to break on filenames with spaces or wildcard characters to be unexpectedly expanded.
- Incorrect:
rm $old_files - Correct:
rm "__MATH_INLINE_2__old_files; do rm "$f"; done
- Ignoring Exit Codes: Assuming a command always succeeds leads to silent failures. Check critical commands' exit status or use
set -eat the script's start to make it exit immediately on any error (though use this with caution, as some commands may return non-zero exit codes benignly).
- Overlooking the Shebang: Running a script without a proper shebang (
#!/bin/bash) means it will execute in the user's current shell, which could besh,dash, orzsh. These shells have different features, leading to syntax errors. Always specify the interpreter explicitly.
- Forgetting Execute Permissions: A script that runs with
bash script.shbut fails with./script.shlikely lacks execute permissions. Rememberchmod +xis required to run a script as a direct command.
Summary
- Shell scripting is the art of automating command-line tasks by writing them into executable text files, primarily using the bash shell.
- Scripts combine core programming constructs—variables for data, conditionals (
if/else) and loops (for/while) for logic, and functions for reusable code—to manage files, automate deployment, and monitor systems. - The foundation of any script is the shebang (
#!/bin/bash) and execute permissions (chmod +x), which allow the system to run it correctly. - Robust scripts handle errors by checking exit codes (
$?) and always quote variable expansions to prevent parsing errors caused by spaces or special characters. - This knowledge is indispensable for DevOps, system administration, and general developer productivity, turning complex, multi-step procedures into simple, repeatable commands.