Shell script

On this page 7

A shell script is a plain text file containing shell commands that a command interpreter, such as Bash, Zsh or a POSIX sh, executes in sequence. On Linux and other Unix-like systems, shell scripts are the most common way to automate administration tasks, glue programs together and capture a repeatable procedure in a single executable file.

A shell script uses the same language a user types interactively at the terminal, extended with variables, conditions, loops and functions. Its strength is direct access to every command-line program on the system; its weakness is a language with subtle quoting and error-handling rules that make larger programs fragile.

How a shell script is executed

A script usually begins with a shebang line, such as #!/bin/bash or #!/usr/bin/env bash, which tells the kernel which interpreter to start when the file is executed directly. The file must have execute permission, set with chmod +x, and is then run by path, for example ./backup.sh, or by name if its directory is listed in the PATH variable.

The way a script is started determines its environment. Running it as a program creates a child process with a copy of exported environment variables, so changes to variables or the working directory do not affect the calling shell. Sourcing the file with source or . runs it inside the current shell, which is how configuration files such as .bashrc work.

MethodExampleEffect
Direct execution./deploy.shNew process, interpreter chosen by the shebang
Explicit interpreterbash deploy.shNew process, shebang ignored, no execute bit needed
Sourcingsource env.shRuns in the current shell and changes its state
Scheduled runcron or a systemd timerRuns unattended with a minimal environment
Ways to run a shell script

Core language elements

Shell scripts are built from a small set of constructs. Every command returns an exit status, where 0 means success and any value from 1 to 255 signals failure; conditions and error handling are built on these codes rather than on Boolean values.

ElementExample syntaxPurpose
Variablename=value, used as "$name"Stores text; no spaces around the equals sign
Positional parameters$1, $2, "$@"Access arguments passed to the script
Exit status$?Result of the last command
Conditionalif [ -f "$file" ]; then ... fiBranches on tests or command results
Loopfor f in *.log; do ... doneRepeats commands over items
Functioncleanup() { ... }Groups reusable commands
Pipelinegrep error app.log | wc -lPasses output of one command to another
Command substitutiontoday=$(date +%F)Captures command output in a variable
Building blocks of a shell script

Correct quoting is the single most important habit. An unquoted variable undergoes word splitting and filename expansion, so a filename containing a space becomes two arguments. Writing "$var" and "$@" prevents a large class of bugs.

Shell dialects and portability

Not every shell understands the same syntax. The POSIX shell standard defines a common core, while Bash and Zsh add arrays, extended tests and other conveniences. A script that uses these extensions must declare the right interpreter in its shebang.

ShellTypical roleScripting notes
POSIX sh (dash)System scripts on Debian and UbuntuFast and minimal, no arrays or double brackets
BashDefault scripting shell on most distributionsArrays, [[ ]] tests, rich parameter expansion
ZshInteractive shell, default on macOSPowerful but different array and globbing rules
BusyBox ashMinimal containers and embedded systemsClose to POSIX with limited extras
Common shells for scripting

A frequent error is writing Bash syntax under a #!/bin/sh shebang. On systems where sh points to dash, such scripts fail with syntax errors that never appeared on the developer’s machine.

Writing robust shell scripts

By default, a shell script continues after a failed command, which can turn a small error into data loss. Defensive settings and conventions make scripts safer and easier to maintain.

  • Use strict mode with set -euo pipefail in Bash to stop on errors, unset variables and failed pipeline stages.
  • Clean up with traps, for example trap cleanup EXIT, so temporary files are removed even on failure.
  • Check scripts with a linter such as ShellCheck, which catches quoting and portability mistakes.
  • Validate arguments at the start and print a usage message when they are missing.
  • Use mktemp for temporary files instead of fixed names in shared directories.

Strict mode has its own edge cases: commands inside conditions do not trigger set -e, and some commands legitimately return non-zero codes. Understanding these exceptions is part of writing reliable automation.

When a shell script is the wrong tool

Shell scripts excel at orchestrating existing programs but become hard to maintain as logic grows. The table helps decide when to switch to another tool.

TaskBetter choiceReason
Running a few commands in orderShell scriptShortest and most direct
Complex data structures or JSON processingPython or another general languageReal types and libraries
Configuring many servers consistentlyConfiguration management toolIdempotency and inventory handling
Script exceeding a few hundred linesGeneral-purpose languageTesting and error handling scale better
Container startup logicShort shell entrypointMinimal dependencies in the image
Choosing between a shell script and alternatives

A practical rule used by many teams is to rewrite a script in a general-purpose language once it needs nested data structures, extensive argument parsing or unit tests. Until that point, a short, well-quoted and linted shell script remains the fastest way to automate routine work on a Linux system.

The idea in brief

A shell script is a saved list of commands for a computer. Instead of typing the same ten commands every morning, a person writes them into a file once. After that, one short command runs the whole list.

It works like a recipe card. The steps are written down, followed in order and give the same result every time. The main benefit is reliable repetition without manual typing mistakes.

A practical scenario

A system administrator must back up a web application’s database and uploaded files every night. She writes a Bash script of about 40 lines that dumps the database, compresses the upload directory into an archive named with the current date, copies both files to a backup server and deletes local archives older than 14 days.

The script uses set -euo pipefail, a cleanup trap and a final check that sends an alert if any step fails. A systemd timer runs it at 02:30 each night. When a disk later fills up, the script stops at the compression step and the alert arrives within minutes, so no incomplete backup is mistaken for a good one.