Automating with Bash

NUnix · L3 MIAGE · CM3 · 90 minutes

Louis Ledoux

ISTIC · University of Rennes

2026–2027

Writing Bash scripts

A script saves commands so you can run them again.
Arguments let you use the same script with different inputs.

  1. Read arguments and store values in variables.
  2. Use conditions, loops and functions.
  3. Handle errors and test the script.

1. Scripts, arguments and variables

Run a script, pass arguments and store values

Bash: Bourne again

Bash = Bourne-Again SHell.

The name puns on Stephen Bourne, author of the earlier Unix shell sh.

We use Bash syntax in this course.
sh script.sh may select a different shell with fewer features.

A first script

#!/bin/bash
# hello.sh
name="MIAGE"
echo "Hello, $name"
  • The first line selects an interpreter for direct execution.
  • # begins a comment when used in this position.
  • .sh is a naming convention, not a requirement.

Two ways to run a script

Invoke Bash

bash hello.sh

Bash reads the file.
It needs read access to it.

Execute the file

chmod u+x hello.sh
./hello.sh

Requires execute permission and a usable interpreter.

The shebang: selecting an interpreter

#!/bin/bash
printf 'Hello, MIAGE\n'

On Linux, direct execution of ./hello.sh uses the interpreter named after #!.

The extension .sh does not choose Bash.
The first line does. We call it the shebang.

One symbol, several names

Symbol Names you may hear
# hash, number sign; pound sign in US usage
# French: croisillon, also carré
♯ The musical sharp sign (dièse)

#!: hash followed by bang.
The symbol’s name and its role depend on context.

Positional parameters

./hello.sh Alice "L3 MIAGE"
Parameter Value inside this script
$0 ./hello.sh
$1 Alice
$2 L3 MIAGE
$# 2
"$@" Every argument, each kept as a separate word

Passing arguments with spaces

set -- "L3 MIAGE" Unix
printf '<%s>\n' "$@"
printf '<%s>\n' "$*"
<L3 MIAGE>
<Unix>
<L3 MIAGE Unix>

Assigning and reading a variable

course="NUnix"
echo "$course"
course="Bash scripting"
echo "Today: $course"

Assignment: name=value, with no spaces around =.
Expansion: $name or ${name}.

Quoted variable expansions

filename="meeting notes.txt"
cat "$filename"
echo '${filename}'
echo "${filename}"
  • Double quotes preserve the expanded value as one argument.
  • Single quotes preserve the literal text ${filename}.

Capturing command output

directory=$(pwd)
message="$1 and $2"
echo "$directory"
echo "$message"

$(command) captures the command's standard output.
$1 and $2 are positional arguments, introduced earlier.

Printing values

name="Alice"
echo "Hello, $name"
printf 'Hello, %s\n' "$name"

echo is convenient for simple messages.
printf gives explicit control over formatting and newlines.

Exporting a variable

message="hello"
bash -c 'echo "$message"'
export message
bash -c 'echo "$message"'

The first child prints an empty line.
After export, the next child prints hello.

A child receives a copy

export message="parent"
bash -c 'message="child"; echo "$message"'
echo "$message"
child
parent

2. Conditions and exit status

Choose which commands to run with if, && and ||

Tests are commands too

test -d logs
[ -d logs ]
[[ -d logs ]]
(( 3 < 5 ))
  • test and [ are command names. ] closes the [ form.
  • [[ ... ]] is a Bash conditional construct.
  • (( ... )) tests an arithmetic expression.

if, elif and else

if [ "$count" -gt 10 ]; then
    echo "More than ten"
elif [ "$count" -eq 10 ]; then
    echo "Exactly ten"
else
    echo "Fewer than ten"
fi

The first successful test selects the branch.
else handles the remaining case.

The condition can be grep

if grep ' ERROR ' app.log; then
    echo "At least one error was found"
else
    echo "No match, or grep could not read the file"
fi

if checks a command’s status.
Square brackets are needed only when we choose a test command.

Conditional command lists

mkdir results && echo "Directory created"
test -d results || echo "Directory is missing"
  • a && b: run b only when a succeeds.
  • a || b: run b only when a fails.
  • ! a: invert the success/failure result.

Which messages appear?

true  || echo "A"
false || echo "B"
true  && echo "C"
false && echo "D"
B
C

Checkpoint: command status

  1. Status 0: true or false?
  2. Does false; echo yes print?
  3. Does false && echo yes print?
  4. Status of false || true?
  1. True / success.
  2. Yes. ; always continues.
  3. No.
  4. 0, from the final executed command, true.

Check the argument count

Save as count-lines.sh:

#!/bin/bash
if [ "$#" -ne 1 ]; then
    printf 'Usage: %s FILE\n' "$0" >&2
    exit 2
fi
wc -l < "$1"

Check the argument count before using $1.
Print the usage message on stderr.

Integer arithmetic

count=3
echo "$((count + 2))"
count=$((count + 1))
((count += 2))
echo "$count"
  • $((...)) expands to an integer result.
  • ((...)) is a Bash arithmetic command.

3. Loops

Process a list of arguments or read a file line by line

for visits a list of words

for group in L3info L3miage M1info; do
    echo "$group"
done

Each iteration assigns the next word to group, then runs the body.

The list can contain literal words, numbers, filenames or "$@".

while repeats after a successful test

i=1
while [ "$i" -lt 10 ]; do
    echo "$i"
    ((i += 1))
done

Test before each iteration.
The loop prints the integers 1 through 9.

while can test a whole command list

i=1
while
    echo "Checking i=$i"
    [ "$i" -le 3 ]
do
    echo "Body: $i"
    ((i += 1))
done

The last command before do decides whether the body runs.

The condition runs once more than the body

Checking i=1
Body: 1
Checking i=2
Body: 2
Checking i=3
Body: 3
Checking i=4
  • At 1, 2, 3, the test succeeds.
  • At 4, the final test fails.
  • The body does not run a fourth time.

Read a file line by line

while IFS= read -r name; do
    printf 'Hello, %s\n' "$name"
done < names.txt
  • read succeeds when it reads a complete line.
  • At EOF, it fails and the loop stops.
  • < feeds the file to the whole loop.

until repeats after a failed test

i=1
until [ "$i" -ge 10 ]; do
    echo "$i"
    ((i += 1))
done

This also prints 1 through 9.
The loop stops when the condition succeeds.

break, continue and exit

Command Effect
continue Skip the rest of the current iteration
break Leave the current loop
exit n End the script with status n

After break, execution continues after the loop.

case selects a matching pattern

read -r -p "Enter a command: " choice
case "$choice" in
    all|ALL)
        ls -la ;;
    list|LIST)
        ls -l ;;
    *)
        echo "Invalid choice" ;;
esac

| separates alternative patterns. ;; ends a branch.

4. Functions and debugging

Group commands in functions and trace script execution

Defining and calling a function

greet() {
    echo "Hello, $1"
}

greet "MIAGE"
  • Define the function before calling it.
  • Inside it, $1 is its first argument.
  • return n finishes the function with exit status n.

Check syntax and trace execution

bash -n count-lines.sh
bash -x count-lines.sh notes.txt
  • -n: parse the script without executing its commands.
  • -x: execute and trace expanded commands on stderr.
  • Check output and status on ordinary and failing inputs.

CM3 summary: Bash scripts

  1. Define inputs, outputs and failure statuses.
  2. Quote variable and filename arguments.
  3. Use command status for branches and loop conditions.
  4. Test normal, empty and failing inputs.

Optional reference

More examples and a command reference

Command substitution

echo "Current directory: $(pwd)"

The shell runs pwd and replaces $(pwd) with its standard output.

The older form is `pwd`.
We will write $(pwd) because nested commands are easier to read.

Newlines, semicolons and continuations

echo "first"
echo "second"; echo "third"
echo "a longer message" \
     "continued on the next line"
  • A newline or ; separates commands.
  • A backslash immediately before a newline continues the line.

Reading a line

read -r -p "Your name: " name
echo "Hello, $name"
  • read reads from standard input.
  • -p displays a prompt in an interactive terminal.
  • -r preserves backslashes in the input.

Integer, readonly and array variables

declare -i count=3
readonly course="NUnix"
declare -a words
words[0]="one"
words[1]="two"
echo "${words[0]}"

unset count removes a variable. A readonly variable cannot be reassigned or unset in that shell.

Familiar environment variables

Variable Meaning
HOME Home directory
USER Account name, usually set at login
PWD, OLDPWD Current and previous working directories
PATH Colon-separated command search directories
SHELL Configured login shell, not necessarily the current shell
TERM Terminal type
HOSTNAME Host name in Bash
MANPATH Manual search path, when explicitly set

Arithmetic with let and expr

let 'count = 3 + 5'
echo "$count"
expr 3 + 5
expr 3 \* 5
  • let evaluates an arithmetic expression in Bash.
  • expr takes separate arguments, so its spaces matter.
  • Escape * so the shell passes it to expr literally.

Comparing numbers and strings

Relation Integers with [ Strings with [
Equal -eq =
Different -ne !=
Less / greater -lt, -gt \<, \>
Less / greater or equal -le, -ge No dedicated operator
[ "$count" -gt 3 ]
[ "$name" = "Alice" ]

Testing strings and files

Test True when…
-n "$value" The string is nonempty
-z "$value" The string is empty
-e "$path" The path exists
-f "$path" It is a regular file
-d "$path" It is a directory
-s "$path" Its size is greater than zero
-r, -w, -x Read, write or execute/search access is allowed

Checkpoint: arithmetic and tests

  1. Value of $((5 / 2))?
  2. Is n = 3 an assignment?
  3. Status of false?
  4. Does -f accept every existing directory?
  1. 2 (integer division).
  2. No. Use n=3.
  3. 1
  4. No. It tests for a regular file.

Checking access to a file

#!/bin/bash
read -r -p "Enter a filename: " filename
if [ ! -w "$filename" ]; then
    echo "File is not writable"
    exit 1
elif [ ! -r "$filename" ]; then
    echo "File is not readable"
    exit 1
else
    echo "File is writable and readable"
fi

Comparing two files

diff "$1" "$2" > diff.out
status=$?
if [ "$status" -eq 0 ]; then
    echo "Files are identical"
elif [ "$status" -eq 1 ]; then
    cat diff.out
else
    echo "Comparison failed" >&2
fi
rm -f diff.out

Copying a known list of files

#!/bin/bash
destination=$1
for file in f1 f2 f3; do
    cp "$file" "$destination/$file"
done

Call with an existing destination directory as the first argument.

A loop can feed a pipeline

for number in 6 3 1 2; do
    echo "$number"
done | sort -n
1
2
3
6

Repeating until interrupted

while true; do
    echo "alive..."
    sleep 3
done

true always succeeds.
Use Ctrl+C to interrupt this foreground loop.

Skipping 4, stopping at 7

for x in 1 2 3 4 5 6 7 8 9; do
    if [ "$x" -eq 4 ]; then
        continue
    fi
    echo "$x"
    if [ "$x" -eq 7 ]; then
        break
    fi
done
echo "After the loop: x=$x"

The loop's output

1
2
3
5
6
7
After the loop: x=7
  • At 4, continue skips echo.
  • At 7, echo runs before break.
  • The final message runs after the loop.

Function scope

outside="a global variable"
mine() {
    local inside="this is local"
    echo "$outside"
    echo "$inside"
    outside="a global with new value"
}
echo "$outside"
mine
echo "$outside"
echo "${inside-unset}"

What remains after the call

a global variable
a global variable
this is local
a global with new value
unset

outside changed.
The function's local variable is no longer in scope.

Builtins: shell and process control

Builtin Use
cd, pwd Change / report the current directory
history Display command history
exec Replace the shell with another program
exit End the current shell
kill Send a signal
trap Arrange a response to a signal or shell event

Builtins: data and parameters

Builtin Use
declare, readonly, local Define variable attributes or scope
export, set Manage the environment / shell settings
echo, read Print output / read input
let, test Arithmetic / condition tests
shift Discard leading positional parameters
return End a function and report its status

Reading a small script

#!/bin/bash
for filename in "$@"; do
    if [ -f "$filename" ]; then
        wc -l "$filename"
    fi
done

For each argument that names a regular file, print its line count.

Université de Rennes Louis Ledoux