Where to start First steps track · 15 stages
Linux Labs

Bash scripts: running, variables, arguments

9 min Ubuntu 24.04 · bash 5.2

A script is a text file of shell commands: the #! line picks the interpreter, arguments arrive in $1, $2 and "$@".

Cheat sheet

8 commands Printable version
  • #!/bin/bash # first line: what runs the file
  • set -euo pipefail stop at the first error
  • chmod u+x backup.sh allow running it
  • ./backup.sh "my file.txt" run it by path
  • bash backup.sh run it without the x bit
  • bash -n backup.sh check syntax without running
  • name="value"; echo "$name" a variable — in double quotes
  • for f in "$@"; do echo "$f"; done every argument, one at a time

Overview

A script is a text file with the commands you would otherwise type in a terminal. An interpreter reads and runs it; for a bash script that is bash.

Running a script

Command What it needs Who picks the interpreter
./backup.sh the x permission the #! line
backup.sh x and the script's directory in PATH the #! line
bash backup.sh the r permission you: bash

The #! line (shebang) is a first line such as #!/bin/bash. It tells the kernel which program to hand the file to when the file is run as a command. With bash backup.sh the interpreter is already chosen and the line is just a comment.

On Ubuntu sh is dash: ls -l /bin/sh shows /bin/sh -> dash. It knows only POSIX syntax, so a bash script run through sh fails:

text
student@ubuntu:~$ sh b.sh x
b.sh: 2: [[: not found
b.sh: 3: Syntax error: "(" unexpected

To run a script by name without ./, put it in ~/bin: if that directory exists, ~/.profile adds it to PATH at login. A newly created directory takes effect from the next login.

Variables and quotes

name=value has no spaces around =. bash reads name = "x" as the command name with arguments: name: command not found.

$name substitutes the value. Unquoted, the shell splits it on spaces — word splitting — and expands patterns such as *:

text
student@ubuntu:~$ f="my file.txt"; ls $f
ls: cannot access 'my': No such file or directory
ls: cannot access 'file.txt': No such file or directory
student@ubuntu:~$ ls "$f"
my file.txt

Inside double quotes the value stays one argument while $name and $(command) are still substituted. Inside single quotes the text is taken literally. $(command) substitutes the command's output: today=$(date +%F).

Arguments

A script's arguments arrive in positional parameters:

Form Meaning
$0 the script name as it was called
$1, $2… the first, second argument
$# the number of arguments
"$@" all arguments, each as a separate word
"$*" all arguments as one string joined by spaces
text
student@ubuntu:~$ cat args.sh
#!/bin/bash
echo "args: $#"
for a in "$@"; do echo "[$a]"; done
student@ubuntu:~$ ./args.sh "my file.txt" two
args: 2
[my file.txt]
[two]

Without quotes, $@ and $* split my file.txt into two words again.

Strict mode

set -euo pipefail at the top of a script:

  • -e — exit as soon as a command returns non-zero; tests inside if, && and || do not stop the script;
  • -u — an unset variable is an error, so a typo in a name does not turn into an empty string; the script exits with code 1;
  • -o pipefail — a pipeline fails if any of its stages fails.

What the codes mean and how to test them is covered in exit-codes.

Recipes

4

Conditions and loops

bash
if [ -f "$file" ]; then
  echo "the file exists"
elif [ -d "$file" ]; then
  echo "it is a directory"
else
  echo "no such thing: $file" >&2
fi

for f in /var/log/*.log; do echo "$f"; done
while read -r host; do ping -c1 "$host"; done < hosts.txt

The [ -f ], [ -z ] and -eq checks are in test-conditions.

Default values and parts of a path

bash
dir="${1:-/srv/backup}"     # the first argument or /srv/backup
f=/var/log/app.log
echo "${f##*/}"             # app.log — the name without the directory
echo "${f%.log}"            # /var/log/app — without the extension
echo "${f%/*}"              # /var/log — the directory

Check the syntax without running

text
student@ubuntu:~$ bash -n syn.sh; echo "rc=$?"
syn.sh: line 5: syntax error: unexpected end of file
rc=2

shellcheck goes deeper: quoting, an unchecked cd, $?.

File names with spaces

bash
for f in /srv/data/*.csv; do gzip "$f"; done   # a pattern, not ls
find /srv/data -name '*.csv' -exec gzip {} +   # with subdirectories

A loop over ls or find output inside $( ) splits names on spaces. A pattern and find -exec pass every name whole (see xargs for -print0).

Troubleshooting

9

Permission denied

bash: ./backup.sh: Permission denied, code 126: the file lacks the x permission. Run chmod u+x backup.sh or start it with bash backup.sh.

command not found

backup.sh: command not found, code 127: a name without a path is looked up only in the PATH directories. Run ./backup.sh or put the script in ~/bin.

cannot execute: required file not found

bash: ./backup.sh: cannot execute: required file not found — the kernel could not find the interpreter named on the #! line. Either the path has a typo, or the file came from Windows with CRLF line endings: file backup.sh says with CRLF line terminators. Strip the \r: sed -i 's/\r$//' backup.sh.

[[: not found, Syntax error: "(" unexpected

A bash script was run by sh (dash): it was started as sh backup.sh, or its #! line says /bin/sh. Run ./backup.sh with #!/bin/bash, or bash backup.sh.

name: command not found on a line with =

Spaces around =: name = "x". Write name="x".

unbound variable

backup.sh: line 3: name: unbound variable — set -u caught a variable with no value, often a typo. Fix the name or give it a default: "${name:-}".

syntax error: unexpected end of file

A block or a quote is not closed: if without fi, do without done, a stray ". The line number points at the end of the file — look further up.

The script breaks on a name with a space

An unquoted variable: cp $f /backup/ gets two arguments instead of one. Write "$f" and "$@"; shellcheck finds such places (SC2086).

Works by hand, fails on a schedule

Cron and systemd services have a different environment: a short PATH, the directory /, none of the variables from ~/.bashrc. Details are in the service environment.

Danger zone

2

rm with an empty variable

rm -rf "$dir/"* with an empty dir turns into rm -rf /*. Use set -u or write "${dir:?}" — an empty value stops the script with an error.

cd without a check

cd /srv/tmp; rm -rf ./* deletes the files in the current directory if cd failed. Write cd /srv/tmp || exit 1.

Terms

6
shebang
The first line of a script, such as #!/bin/bash: the kernel uses it to pick the interpreter when the file runs as a program. With "bash file" the interpreter is already chosen and the line is a comment. Also appears in: File permissions
interpreter
A program that reads a script and runs its commands one by one: bash, sh, python3. On Ubuntu sh is dash, which has fewer features than bash. Also appears in: Capabilities: root split into parts, auditd, cat, pgrep
word splitting
A step of command parsing: the shell splits the value of an unquoted $name on spaces into separate arguments. In double quotes "$name" stays one argument. Also appears in: shellcheck
positional parameter
A script argument by number: $1, $2 and on; $# is the number of arguments, "$@" is all of them, each as a separate word.
set -e (errexit)
A bash mode: the script exits as soon as a command returns non-zero. Tests in if and commands on the left of && and || do not trigger it. Also appears in: Exit codes: $?, && and ||, Capabilities: root split into parts, flock, wait
pipefail
A bash mode (set -o pipefail): a pipeline returns the code of a failed stage; without it, the code of the last stage, even if an earlier one failed. Also appears in: Exit codes: $?, && and ||, alias, Ansible: inventory, modules and one-off commands, bash and 6 more

See also

  • exit-codes — exit codes, &&, ||, exit.
  • test-conditions — checks on files, strings and numbers.
  • shellcheck — find bugs before running.
  • env-vars — PATH and environment variables.
  • command-syntax — quoting and arguments on the command line.
  • redirection — output to a file and to standard error with >&2.
  • flock — guarding against a second run.
  • bash — shell modes and startup files.
Loading…