StackrootServer-side web stacks and the Linux systems that hold them, from config to debug.

Linux Bash

Linux Bash is the Bourne Again Shell, the default command interpreter on the Linux systems a sysadmin spends every day inside, and it is the layer where the kernel's interfaces meet a human's typing.

A black terminal window with a green bash prompt and a few typed commands.

Bash reached its 5.0 milestone in 2019 after decades of steady growth, and the 5.2 release that followed in 2022 added associative array features that script authors had requested since the 4.x line. The shell sits on top of the Linux Kernel, which is why it exposes raw power rather than a guarded interface. If you type ls and it answers, Bash parsed your line, expanded what you typed, and asked the kernel to do the work. This page walks the syntax, the variables, and the scripting habits of a working sysadmin, so the terminal stops being a place to copy from and becomes a place to type in.

Before the shell can run anything, it has to find the program. Bash resolves each command through the PATH variable, which holds a list of directories such as /usr/local/bin, /usr/bin and /bin. The first match in that order wins, and that single fact explains most "wrong binary" incidents in production, because a stray file dropped early in the path shadows the system copy. Two shell families share the Linux landscape. bash and zsh are interactive shells, while sh is the POSIX baseline that deployment scripts still target for portability. The Linux Commands that a sysadmin reaches for daily, such as grep, awk, find and sed, are ordinary executables that Bash merely hands arguments to, which is why their behavior never changes with the shell.

Variables and expansion: the state the shell keeps

Variables are the state the shell keeps between commands, and every variable is a plain name mapped to a string. The built-in variables a sysadmin leans on are HOME, PATH, PWD and SHELL, while a user assignment such as DEPLOY_USER=ops1 creates a new one. Expansion is where Bash turns text into commands: $VAR substitutes the value, ${VAR:-default} falls back when the variable is unset, $? holds the exit status of the last command, and $$ holds the shell's own process ID. Word splitting and globbing then apply to unquoted results, which is why the discipline of quoting, as in echo "$line", is the difference between one argument and a dozen. The same rule holds in scripts, and the habits formed here carry straight into the scripting section below.

Assignment versus execution

A line with no spaces around the equals sign is an assignment, and a line with spaces is a command. So FOO=bar stores a value, FOO = bar tries to run a program called FOO, and FOO=bar cmd runs cmd with that variable exported only for the duration of that one command. That prefix form is how sysadmins scope configuration, running HTTP_PROXY=http://proxy:3128 curl example.com without polluting the rest of the session.

Control flow: the grammar of a working script

Control flow is the grammar that turns a list of commands into a decision, and Bash borrows its keywords from the C tradition. An if block tests a condition, a for loop iterates a fixed set, and a while loop runs until its test fails. The condition is always the exit status of a command, so the most natural test in a sysadmin script is the program itself, as in checking systemctl is-active nginx rather than parsing its text output. A loop over output uses read line by line, which avoids the word-splitting traps of iterating a bare string:

  • if grep -q "pattern" file; then ...; fi tests for presence without printing a match.
  • for f in /etc/nginx/sites-enabled/*; do ...; done iterates a glob of real files.
  • while IFS= read -r line; do ...; done < file consumes lines safely, including leading and trailing spaces.
  • case dispatches on a string, which keeps a long chain of if tests readable.

Each of these reads left to right the way the shell executes it, and keeping the tests as command statuses means the script reports success the same way the underlying tools do.

Functions and scripts: packaging the repeat

Functions package a repeat, and they are the first tool a sysadmin reaches for the moment a sequence of 3 or more commands shows up twice. A definition such as restart_service() { systemctl restart "$1"; systemctl status "$1"; } takes an argument through $1 and stays visible in the current shell until you log out. Scripts go one step further: they live in a file, carry a shebang line such as #!/bin/bash so the kernel knows which interpreter to invoke, and are run as their own process. The split matters for operations, because a sourced function inherits your environment and your mistakes, while a script runs in a clean state that you control. For a stack like the Linux for the LAMP stack, where web, database and PHP workers all share one box, the script is the unit you version, review and roll back, and the function is the unit you keep handy at the keyboard.

Exit codes, error handling and the -e habit

Exit codes are the only error channel the shell has, and every command reports through them. The convention is 0 for success and nonzero for failure, with 1 as the generic error, 2 for misuse, and 126 and 127 reserved for found-but-unexecutable and command-not-found. Chained operators change how a failure propagates: && runs the right side only on success, || only on failure, and ; runs it regardless. The habit that separates careful scripts from careless ones is set -e, which stops the script the moment a command fails, paired with set -u so an unset variable aborts instead of expanding to nothing. A sysadmin wraps commands that are allowed to fail, such as grep -q in a test, and lets everything else trip the guard. The same discipline applies when a script spawns the tools in the next section, because their exit statuses are the only reliable signal that the operation actually happened.

Process management: the shell's view of running work

Process management is where the shell's view of running work meets the kernel's, and Bash gives you a small but complete control surface. Every background job started with & is tracked by job number, and jobs, fg and bg let you switch it between the terminal and the background. For anything that must outlive the login, the pattern is nohup cmd > log 2>&1 & or, better, a systemd unit, because a plain background process dies with its session in most setups. A sysadmin reads state with ps aux, follows a single process with strace when a program behaves wrongly, and kills with kill 15 for a graceful shutdown before escalating to kill 9. The signal numbers matter, since 15 gives a process a chance to flush files and close connections, while 9 is the version that never waits. On a busy box, counting the cost is part of the job: a 2 core node running 4 worker processes keeps its context switches low, and that arithmetic, not folklore, is what sizes the fleet.

Finding your way: files, permissions and the daily commands

Finding your way is the daily traffic of the job, and the shell makes it a matter of habit. The navigation set is small: cd, pwd, ls -l, find, and which. The find command is the workhorse, and the patterns in a guide to Find Files on Linux cover most of it, such as find /var/log -name "*.log" -mtime +7 to list logs older than 7 days. Permissions are the gate on every file, and the 3 digits of a mode like 644 encode the read and write rights of owner, group and others. A sysadmin reads that triple in under a second, because the difference between 750 and 751 is the difference between a directory the public can list and one that is locked. The daily loop is the same on every box: locate, inspect, change, verify, and the shell is the only tool in that loop that does all 4 without leaving the keyboard.

Hardening and operations: the shell as the control surface

Hardening is the final layer, and it is mostly the shell's configuration left behind by every login. A working sysadmin edits /etc/profile and the per-user ~/.bashrc to pin down sane defaults: a PATH that does not include the current directory, a prompt that shows the hostname, and a history size of 1000 entries with histappend so sessions do not overwrite each other. The history file, ~/.bash_history, is also an audit trail, which is why careful teams rotate it and review it like any log. Operations then becomes a rhythm: a status command that returns a single word, a log tail that greps for the error signature, a restart that checks the exit code, and a script that bundles the 3 in the right order. That rhythm is the whole job in miniature, and it runs on bash because bash is the only layer the sysadmin can always reach, from a fresh install to a 12 year old production node.

Where to go next