Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Using the terminal

University of Pisa

Bash

Default on most Linux systems.

greet() { echo "Hello, $1!"; }
greet world

Zsh

Default on macOS.

PROMPT='%F{green}%n@%m%f:%F{blue}%~%f $(git_prompt_info)%f '

Fish

Friendly Interactive Shell.

set greeting "Hello, fish!"
echo $greeting

PowerShell

Cross-platform, object-based.

Get-Process | Sort-Object CPU -Descending | Select-Object -First 5

Summary

ShellUseStrength
BashScriptsPOSIX, standard
ZshInteractiveCompletion, themes
FishInteractiveAutosuggest, simplicity
PowerShellDevOpsObject pipelines

Pick: Bash for scripts, Zsh/Fish for interactivity, PowerShell for cross-platform.


Bash


Basic navigation

pwd        # show path
ls -la     # list files
mkdir dir  # make dir
cd dir     # change dir
cd ..      # up one
cd -       # previous dir

Files

touch f.txt      # new file
rm f.txt         # delete
rm -r dir        # remove dir
cp a b           # copy
mv a b           # move/rename

Permissions

chmod 644 file.txt

rw- r-- r--: owner read/write, others read. Use man chmod for more modes.


Searching and reading

find . -name "*.txt"
grep -nr "hello" .
cat hello.txt
less hello.txt

Scroll: Shift + Arrow, search: Ctrl+R.


Redirection

ls -l > out.txt   # write
echo hi >> out.txt  # append
1> stdout  2> stderr  &> both
history | grep "cd"

/dev/null discards output.


Processes

ps -A     # all
top -o %MEM
kill -9 PID

.bashrc / .zshrc

Run at login. Add aliases or settings.

alias pg='ping google.com'

Lab 01

Automating slide page generation

Assume you have cloned the course repository

git clone git@github.com:luca-heltai/sspa.git

Let’s explore the structure of the repository:


Overview of the course page


Problem statement


Solution

Problem:


Technical issue

Look at the Makefile, and see how the slides are copied after the book is built:

make build
make copy

Goal of today’s lab


What

Inputs


Outputs

Mapping:

slides00.md → slides00.html → referenced in lecture00.md
slides01.md → slides01.html → referenced in lecture01.md
...

How

  1. Find all jupyterbook/slides/slides*.md.

  2. Extract XX.

  3. Create slides/slidesXX.html by replacing slidesXX.md in the HTML template with the real filename (sed).

  4. Build footer by replacing XX in lecture_footer.md.

  5. If lectureXX.md does not reference /slides/slidesXX.html, append the footer.


Script: codes/lab_01/generate_slides.sh

generate_slides.sh
#!/usr/bin/env bash
#
# Generate HTML landing pages from Markdown slides,
# then ensure each lecture file ends with the correct footer block.
#
# Usage:
#   ./generate_slides.sh [--slides-dir PATH] [--lectures-dir PATH] [--templates-dir PATH] [--output-dir PATH] [-n] [-v]
#
# Defaults match the instructor’s layout.

set -euo pipefail

# Defaults (adjustable via flags)
SLIDES_DIR="jupyterbook/slides"
LECTURES_DIR="jupyterbook/lectures"
TEMPLATES_DIR="codes/lab01"
OUTPUT_DIR="jupyterbook/slides"
BASE_URL=""
DRY_RUN=0
VERBOSE=0

log() { printf '%s\n' "$*" >&2; }
vlog() { [ "$VERBOSE" -eq 1 ] && log "$@"; }

# Parse flags
while [ $# -gt 0 ]; do
  case "$1" in
    --slides-dir)     SLIDES_DIR="$2"; shift 2;;
    --lectures-dir)   LECTURES_DIR="$2"; shift 2;;
    --templates-dir)  TEMPLATES_DIR="$2"; shift 2;;
    --output-dir)     OUTPUT_DIR="$2"; shift 2;;
    --base-url)       BASE_URL="$2"; shift 2;;
    -n|--dry-run)     DRY_RUN=1; shift;;
    -v|--verbose)     VERBOSE=1; shift;;
    -h|--help)
      sed -n '1,30p' "$0" | sed 's/^# \{0,1\}//'
      exit 0;;
    *) log "Unknown option: $1"; exit 2;;
  esac
done

HTML_TEMPLATE="$TEMPLATES_DIR/slides_template.html"
FOOTER_TEMPLATE="$TEMPLATES_DIR/lecture_footer.md"

# Checks
[ -f "$HTML_TEMPLATE" ]   || { log "Missing template: $HTML_TEMPLATE"; exit 1; }
[ -f "$FOOTER_TEMPLATE" ] || { log "Missing template: $FOOTER_TEMPLATE"; exit 1; }
[ -d "$SLIDES_DIR" ]      || { log "Missing slides dir: $SLIDES_DIR"; exit 1; }
mkdir -p "$OUTPUT_DIR"

# Create HTML from template for each slidesXX.md
shopt -s nullglob
slides=( "$SLIDES_DIR"/slides*.md )
if [ ${#slides[@]} -eq 0 ]; then
  log "No slide sources found in $SLIDES_DIR (expected files like slides00.md)."
  exit 0
fi

for md in "${slides[@]}"; do
  md_base="$(basename "$md")"                 # slidesXX.md
  num="${md_base#slides}"                     # XX.md
  num="${num%.md}"                            # XX
  html_out="$OUTPUT_DIR/slides${num}.html"    # slides/slidesXX.html
  lecture_md="$LECTURES_DIR/lecture${num}.md" # jupyterbooks/lectures/lectureXX.md

  vlog "Processing $md_base -> $html_out; lecture file: $(basename "$lecture_md")"

  # 1) Generate HTML landing page from template, replacing placeholder
  # Replace both 'slidesXX.md' and 'BASEURL' placeholders.
  if [ "$DRY_RUN" -eq 0 ]; then
    sed -e "s/slidesXX\.md/$md_base/g" \
        -e "s|BASEURL|$BASE_URL|g" \
        "$HTML_TEMPLATE" > "$html_out"
  fi
  vlog "Wrote $html_out"

  # 2) Ensure lecture footer exists and is up to date
  if [ ! -f "$lecture_md" ]; then
    log "Lecture file not found, skipping footer: $lecture_md"
    continue
  fi

  # Build the concrete footer block by substituting XX with the numeric token.
  # This covers both iframe and link occurrences.
  # Use 'printf %s' to preserve newlines exactly.
  footer_block="$(sed -e "s/XX/$num/g" -e "s|BASEURL|$BASE_URL|g" "$FOOTER_TEMPLATE")"

  # Check if the lecture already references the correct HTML slide URL.
  # If it does, we assume the footer is present.
  if grep -Fq "slides${num}.html" "$lecture_md"; then
    vlog "Footer already present in $(basename "$lecture_md")"
    # Remove any existing footer block to replace it with the updated one.
    sed -e '/<!-- FOOTER START -->/,/<!-- FOOTER END -->/d' "$lecture_md" > "${lecture_md}.tmp"
    mv "${lecture_md}.tmp" "$lecture_md"
    vlog "Removed old footer in $(basename "$lecture_md")"
  fi

  vlog "Appending footer to $(basename "$lecture_md")"
  if [ "$DRY_RUN" -eq 0 ]; then
    # Ensure the file ends with a newline, then append a blank line and the footer.
    # Also insert a horizontal separator if the file already has content.
    if [ -s "$lecture_md" ]; then
      sed '${/^$/d;}' "$lecture_md" > "${lecture_md}.tmp"   # remove last blank line if present
      mv "${lecture_md}.tmp" "$lecture_md"
      printf '\n' >> "$lecture_md"
    fi
    printf '%s\n' "$footer_block" >> "$lecture_md"
  fi
done

log "Done."

Run the script

Make sure it is executable:

chmod +x codes/lab_01/generate_slides.sh

Run it:

./codes/lab_01/generate_slides.sh -v
# or dry-run first:
./codes/lab_01/generate_slides.sh -n -v

Breaking it down


Project layout


Expected folders

codes/lab_01/
  ├─ slides_template.html
  ├─ lecture_footer.md
  └─ generate_slides.sh   # we will look at this
jupyterbook/slides/
  ├─ slides00.md
  ├─ slides01.md
  ├─ ...
  ├─ (the script will write slidesXX.html here)
jupyterbooks/lectures/
  ├─ lecture00.md
  ├─ lecture01.md
  └─ ...

Creating the templates

mkdir -p codes/lab_01
ls -la codes/lab_01

Templates


HTML template snippet

The file codes/lab_01/slides_template.html must include (this is how reveal.js loads markdown):

...
<section 
  data-markdown="slidesXX.md" 
  data-separator="\n----\n" 
  data-separator-vertical="\n---\n">
</section>
...

codes/lab_01/lecture_footer.md:

<iframe src="/slides/slidesXX.html" width="100%" height="800px" style="border: none;"></iframe>

```{admonition} 🎬 View Slides
:class: tip

**[Open slides in full screen](/slides/slidesXX.html)** for the best viewing experience.
```

Scripting strategy


Inputs → Outputs


Why a script


Build the script


Create the file

cd codes/lab_01
touch generate_slides.sh
chmod +x generate_slides.sh

Define defaults

These are variables:

SLIDES_DIR="jupyterbook/slides"
LECTURES_DIR="jupyterbooks/lectures"
TEMPLATES_DIR="codes/lab_01"
OUTPUT_DIR="jupyterbook/slides"

Variables


Operations on variables

basename "/path/to/slides01.md"    # slides01.md
num="${md_base#slides}"            # remove prefix → 01.md
num="${num%.md}"                   # remove suffix → 01
echo "Number is $num"              # prints "Number is 01"

Loop over slide sources

shopt -s nullglob
for md in "$SLIDES_DIR"/slides*.md; do
  md_base="$(basename "$md")"     # slidesXX.md
  num="${md_base#slides}"         # XX.md
  num="${num%.md}"                # XX
  echo "Found $md_base with number $num"
done

Loops

shopt -s nullglob         # avoid errors if no matches
for item in list; do
  # commands using $item
done

What is a “list”?


1. Explicit values

for n in 1 2 3 4 5; do
  echo "Number: $n"
done

2. Brace expansion

for n in {01..05}; do
  echo "Slide $n"
done

3. “Globbing” over files

for f in slides/*.md; do
  echo "Found file: $f"
done

4. Command substitution

for user in $(cat users.txt); do
  echo "User: $user"
done

5. Array elements

arr=(red green blue)
for color in "${arr[@]}"; do
  echo "Color: $color"
done

6. Slices within arrays

arr=(one two three four five)
for item in "${arr[@]:1:3}"; do
  echo "Item: $item"
done

Generate slidesXX.html with sed

sed -e "s/slidesXX\.md/$md_base/g" \
    -e "s/slideXX\.md/$md_base/g" \
    "$TEMPLATES_DIR/slides_template.html" \
    > "$OUTPUT_DIR/slides${num}.html"

What is sed



Basic syntax

sed 's/pattern/replacement/' file.txt
sed 's/foo/bar/g' file.txt
sed 's/foo/bar/g' file.txt > new.txt

Common examples

# Replace all "cat" with "dog" in file
sed 's/cat/dog/g' animals.txt

# Replace only on specific lines (line 1 to 5)
sed '1,5 s/error/warning/g' log.txt

# Delete blank lines
sed '/^$/d' notes.txt

# Show only matching lines (like grep)
sed -n '/TODO/p' script.sh

Using variables and escaping

When using variables, wrap the expression in double quotes:

name="Alice"
sed "s/USER/$name/g" template.txt > output.txt

To match special characters, escape them:

sed 's/file\.txt/archive.txt/' list.txt

sed -i 's/old/new/g' file.txt

In macOS (BSD sed), avoid -i unless you supply a backup suffix:

sed -i '' 's/foo/bar/g' file.txt   # macOS
sed -i 's/foo/bar/g' file.txt      # Linux

Use sed in scripts for reliable, repeatable text transformations.


lecture_md="$LECTURES_DIR/lecture${num}.md"
footer_block="$(sed "s/XX/$num/g" "$TEMPLATES_DIR/lecture_footer.md")"

if grep -Fq "/$OUTPUT_DIR/slides${num}.html" "$lecture_md"; then
  echo "Footer already present in $(basename "$lecture_md")"
else
  printf '\n%s\n' "$footer_block" >> "$lecture_md"
  echo "Appended footer to $(basename "$lecture_md")"
fi

Run and verify


First a dry run

./codes/lab_01/generate_slides.sh -n -v

Execute

./codes/lab_01/generate_slides.sh -v
ls jupyterbook/slides/ | grep '^slides[0-9][0-9]\.html$'

Spot-check an HTML file

head -n 40 slides/slides00.html

tail -n +1 jupyterbooks/lectures/lecture00.md | sed -n '$-40,$p'

Key commands explained


pwd      # print working directory
ls -la   # list with details, including hidden files
cd path  # change directory
cd -     # jump back to previous directory

Globbing and patterns

ls jupyterbook/slides/slides*.md
# * matches any string
# ? matches a single char
# [0-9] matches a digit

sed substitutions

sed 's/OLD/NEW/g' input > output
# Escaping literal dots:
sed 's/slidesXX\.md/slides07.md/g' template.html > out.html

grep checks

grep -Fq "/slides/slides07.html" jupyterbooks/lectures/lecture07.md
# -F fixed string, -q quiet (exit code only)
echo $?  # 0 → found, 1 → not found

Appending text safely

printf '\n%s\n' "$footer_block" >> jupyterbooks/lectures/lecture07.md

Troubleshooting


Common issues


Re-run anytime

The script is idempotent for footers. It only appends when the link to the exact slidesXX.html is missing.


Recap


What we automated


Takeaway

Use Bash to codify repeatable edits:

Ready to integrate into your build process or CI.