Bash¶
Default on most Linux systems.
Scripts – standard
.sh.Features – readline, globbing, job control, arrays.
Extensible – functions, aliases.
greet() { echo "Hello, $1!"; }
greet worldZsh¶
Default on macOS.
Completion – advanced globbing (
**/*.py).Correction – auto-fixes typos.
Prompts – themes, colors.
Plugins – oh-my-zsh, prezto.
PROMPT='%F{green}%n@%m%f:%F{blue}%~%f $(git_prompt_info)%f 'Fish¶
Friendly Interactive Shell.
Syntax – uses
set, noexport.Autosuggest – live history hints.
Errors – human-readable.
Config UI –
fish_config.
set greeting "Hello, fish!"
echo $greetingPowerShell¶
Cross-platform, object-based.
Objects – pipelines pass data.
Cmdlets –
Get-Process,Get-Service.Language – loops, try/catch.
Integration – all OSes.
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5Summary¶
| Shell | Use | Strength |
|---|---|---|
| Bash | Scripts | POSIX, standard |
| Zsh | Interactive | Completion, themes |
| Fish | Interactive | Autosuggest, simplicity |
| PowerShell | DevOps | Object 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 dirFiles¶
touch f.txt # new file
rm f.txt # delete
rm -r dir # remove dir
cp a b # copy
mv a b # move/renamePermissions¶
chmod 644 file.txtrw- 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.txtScroll: Shift + Arrow, search: Ctrl+R.
Redirection¶
ls -l > out.txt # write
echo hi >> out.txt # append
1> stdout 2> stderr &> bothhistory | 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.gitLet’s explore the structure of the repository:
Overview of the course page¶
The course materials are in
jupyterbook/lectures/andjupyterbook/slides/The software
jupyter-book(https://jupyterbook .org/) builds the course webpage from these (text) sources It generates the static HTML page that is then hosted on GitHub Pages (https://
luca -heltai .github .io /sspa/)
Problem statement¶
jupyterbookdoes not natively support slidesjupyterbookrenders slides content as a single long html page (take a look at intro)We would like to provide also an embedded (and interactive slideshow) in each lecture page
Solution¶
reveal.js(https://revealjs .com/) can render markdown slides as interactive slideshows
Problem:
We need to create a landing page for each slideshow (e.g.,
slides01.html) thatreveal.jscan use to render the slides from the markdown content (e.g.,slides01.md)
Technical issue¶
jupyterbookgenerates its pages in a temporary folder during the build processwe need to copy the generated
slidesXX.htmlthere afterjupyterbookhas runwe want this to be automatic
Look at the Makefile, and see how the slides are copied after the book is built:
make build
make copyGoal of today’s lab¶
Updates slide landing pages automatically.
Create slides (
slidesXX.html) from content (slidesXX.md).Embed slides in lecture notes (
lectureXX.md).Avoid manual errors
Allow easy updates to templates, styles, etc.
What¶
Inputs
Template HTML (from a
reveal.jsexample):codes/lab_01/slides_template.htmlFooter template (to add at the end of each lecture page):
codes/lab_01/lecture_footer.mdSlide sources (the only content I want to write):
jupyterbook/slides/slidesXX.md
Outputs
Landing pages:
jupyterbooks/slides/slidesXX.htmlFooter appended to:
jupyterbooks/lectures/lectureXX.md
Mapping:
slides00.md → slides00.html → referenced in lecture00.md
slides01.md → slides01.html → referenced in lecture01.md
...How¶
Find all
jupyterbook/slides/slides*.md.Extract
XX.Create
slides/slidesXX.htmlby replacingslidesXX.mdin the HTML template with the real filename (sed).Build footer by replacing
XXinlecture_footer.md.If
lectureXX.mddoes not reference/slides/slidesXX.html, append the footer.
Script: codes/lab_01/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.shRun it:
./codes/lab_01/generate_slides.sh -v
# or dry-run first:
./codes/lab_01/generate_slides.sh -n -vBreaking it down¶
Shell navigation and file ops
Globbing and pattern matching
sedfor templatinggrepfor checksSafe scripting patterns
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_01mkdir -pcreates parents as needed.Verify:
ls -la codes/lab_01Templates¶
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>
...XXis a placeholder we will replace.\nis a newline (in regex).
Footer template¶
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.
```Again,
XXis a placeholder.
Scripting strategy¶
Inputs → Outputs¶
Input: each
jupyterbook/slides/slidesXX.mdOutput A:
jupyterbook/slides/slidesXX.htmlfrom HTML template by replacingslidesXX.mdOutput B: ensure
jupyterbook/lectures/lectureXX.mdends with a footer referencing/slides/slidesXX.html
Why a script¶
Repeatable for any number of slides
Avoid manual errors
Keep lectures and slides in sync
Easy to update templates/styles later
Build the script¶
Create the file¶
cd codes/lab_01
touch generate_slides.sh
chmod +x generate_slides.shtouchcreates an empty file.chmod +xmakes it executable.
Define defaults¶
These are variables:
SLIDES_DIR="jupyterbook/slides"
LECTURES_DIR="jupyterbooks/lectures"
TEMPLATES_DIR="codes/lab_01"
OUTPUT_DIR="jupyterbook/slides"Keep paths configurable.
We’ll add flags to override.
Variables¶
Assign with
=, no spaces.Reference with
$VARor${VAR}.Quote variables:
"$VAR"to avoid word-splitting.
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"The
${VAR#prefix}and${VAR%suffix}syntax removes parts.Useful for extracting tokens from filenames.
echoprints to stdout and expands variables.
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"
doneGlobbing picks only matching files.
Parameter expansion extracts the number.
Loops¶
shopt -s nullglob # avoid errors if no matches
for item in list; do
# commands using $item
doneIterates over each item in the list.
Use
shopt -s nullglobto avoid errors if no matches.
What is a “list”?
1. Explicit values¶
for n in 1 2 3 4 5; do
echo "Number: $n"
done2. Brace expansion¶
for n in {01..05}; do
echo "Slide $n"
done3. “Globbing” over files¶
for f in slides/*.md; do
echo "Found file: $f"
done4. Command substitution¶
for user in $(cat users.txt); do
echo "User: $user"
done5. Array elements¶
arr=(red green blue)
for color in "${arr[@]}"; do
echo "Color: $color"
done6. Slices within arrays¶
arr=(one two three four five)
for item in "${arr[@]:1:3}"; do
echo "Item: $item"
doneGenerate 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"-ecan stack multiple substitutions.We guard both
slidesXX.mdand a possibleslideXX.mdtypo.Always quote variables.
What is sed¶
sed= stream editorProcesses text line by line, applying editing commands.
Useful for find-and-replace, insertion, deletion, or pattern-based edits.
Reads from:
standard input (
echo "text" | sed ...)or files (
sed ... file.txt)
By default, prints the modified text to standard output.
Basic syntax¶
sed 's/pattern/replacement/' file.txts= substitute commandReplaces the first match of
patternper line.Add
gto replace all matches in each line:
sed 's/foo/bar/g' file.txtOutput goes to the terminal. Use redirection to save it:
sed 's/foo/bar/g' file.txt > new.txtCommon 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/pattern/limits action to lines that match.
Using variables and escaping¶
When using variables, wrap the expression in double quotes:
name="Alice"
sed "s/USER/$name/g" template.txt > output.txtTo match special characters, escape them:
sed 's/file\.txt/archive.txt/' list.txtThe option
-iedits files in place (careful!):
sed -i 's/old/new/g' file.txtIn 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 # LinuxUse sed in scripts for reliable, repeatable text transformations.
Check and append footer¶
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")"
figrep -Fqdoes a quiet fixed-string check.If not found, append the concrete footer.
Run and verify¶
First a dry run¶
./codes/lab_01/generate_slides.sh -n -vConfirms which files would be written.
Execute¶
./codes/lab_01/generate_slides.sh -vCheck outputs:
ls jupyterbook/slides/ | grep '^slides[0-9][0-9]\.html$'Spot-check an HTML file¶
head -n 40 slides/slides00.htmlConfirm it references
slides00.mdin thedata-markdownattribute.
Spot-check a lecture footer¶
tail -n +1 jupyterbooks/lectures/lecture00.md | sed -n '$-40,$p'Ensure the iframe and the “Open slides in full screen” link show
/slides/slides00.html.
Key commands explained¶
Navigation¶
pwd # print working directory
ls -la # list with details, including hidden files
cd path # change directory
cd - # jump back to previous directoryGlobbing and patterns¶
ls jupyterbook/slides/slides*.md
# * matches any string
# ? matches a single char
# [0-9] matches a digitUse quotes around variables to avoid word-splitting.
sed substitutions¶
sed 's/OLD/NEW/g' input > output
# Escaping literal dots:
sed 's/slidesXX\.md/slides07.md/g' template.html > out.htmlgmeans “replace all occurrences in a line.”
grep checks¶
grep -Fq "/slides/slides07.html" jupyterbooks/lectures/lecture07.md
# -F fixed string, -q quiet (exit code only)
echo $? # 0 → found, 1 → not foundAppending text safely¶
printf '\n%s\n' "$footer_block" >> jupyterbooks/lectures/lecture07.mdprintfpreserves newlines and avoids echo portability issues.
Troubleshooting¶
Common issues¶
Wrong paths: use
-vand echo variables.No matches: verify filenames are
slidesXX.mdwith zero-padded numbers.macOS
sed: we used portable syntax, no-iin place.Permissions:
chmod +x generate_slides.sh.
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¶
HTML landing pages from a template
Lecture footers that embed and link to the slides
Safe checks to avoid duplicates
Takeaway¶
Use Bash to codify repeatable edits:
glob files → extract tokens
sedfor templatinggrepto check stateappend only when needed
Ready to integrate into your build process or CI.