- Published on
Modern Bash & Shell Scripting 2026 Complete Guide - Bash 5.2 · Zsh 5.9 · fish 4 · Nushell · Murex · ShellCheck · Bashly · just Deep Dive
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Prologue — Why talk about the shell again in 2026
- Chapter 1 · Bash 5.2 — the standard, from Brian Fox to Chet Ramey
- Chapter 2 · Zsh 5.9 — the macOS default, home of Oh My Zsh
- Chapter 3 · fish 4 — the user-friendly shell, reborn in Rust
- Chapter 4 · Nushell — structured-data pipelines
- Chapter 5 · Murex · xonsh · Elvish — third-path shells
- Chapter 6 · POSIX shell · dash · ksh · tcsh — legacy and minimalism
- Chapter 7 · Shell scripting strict mode — set -euo pipefail
- Chapter 8 · trap and mktemp — never forget cleanup
- Chapter 9 · Argument parsing — getopts and beyond
- Chapter 10 · Arrays and associative arrays — the core of bash 4+
- Chapter 11 · Functions, local, readonly, process substitution
- Chapter 12 · ShellCheck · shfmt · bashly — the modern shell workflow
- Chapter 13 · fzf · ripgrep · fd · bat · eza — modern Unix tools
- Chapter 14 · System monitoring — bottom · btop · htop · glances
- Chapter 15 · jq · yq · dasel · gron — structured data processing
- Chapter 16 · Parallel execution — xargs · parallel · rush
- Chapter 17 · gum · charm — pretty TUIs from the shell
- Chapter 18 · just · task · xc · mise — modern task runners
- Chapter 19 · Shell prompts — Starship · Oh My Posh · Powerline
- Chapter 20 · AI shells — sgpt · gptme · Warp · Amazon Q
- Chapter 21 · Terminal emulators — Ghostty · Wezterm · Alacritty · Kitty · iTerm2
- Chapter 22 · Korea/Japan ops culture — Coupang · LINE · Cybozu · Mercari
- Chapter 23 · Where the shell goes 2026–2028
- Chapter 24 · Shell scripting checklist (10 items)
- Chapter 25 · A catalog of seven real-world anti-patterns
- Chapter 26 · Conclusion — the shell still sits in the middle of infrastructure
- References
Prologue — Why talk about the shell again in 2026
June 1989. Brian Fox released the first version of the Bourne-Again SHell as part of the GNU Project. 37 years later, in May 2026, we still tap on the shell every day. Even in an age when AI writes our code, cd, ls, grep, git, kubectl, and ssh live on as muscle memory in our fingers.
But the landscape has clearly shifted.
- Bash 5.2 (released September 2022) is still the GNU standard, and 5.3 is due to arrive within 2026. Chet Ramey has maintained it since the early 1990s.
- Zsh 5.9 has been the macOS default since Catalina, and it commands a massive plugin ecosystem of Oh My Zsh, Prezto, and Antigen.
- fish 4 was fully rewritten in Rust in 2024. Start-up time, syntax highlighting, and autosuggestion all got faster and lighter on memory.
- Nushell 0.105+ shakes the very definition of a shell. Every command emits and consumes structured data (a table), and you write SQL-flavored pipelines like
ls | where size > 1mb | sort-by modified. - Murex — a shell with a type system. xonsh — a shell scripted in Python. Elvish — an expression-rich shell. All of them have carved out their own niche.
- And then the tooling. ShellCheck is the ESLint for shell scripts. shfmt is the formatter. bashly generates full-stack CLI apps from a YAML spec. gum lets shell scripts paint pretty TUIs. fzf, ripgrep, fd, eza, dust, bottom, just, mise — every one of them is a next-generation rewrite of GNU coreutils in Rust or Go.
- AI showed up too. shell_gpt (sgpt) turns natural language into shell commands, Warp Terminal floats AI suggestions, and Amazon Q Developer (formerly Fig) sharpens completion.
This article walks through those tools and patterns across 22 chapters. The shell itself (1–6), scripting best practices (7–12), modern CLI tools (13–17), build/task runners (18–19), AI and terminals (20), Korea/Japan ops culture (21), and finally the future (22).
The shell isn't going away. It's just that the shell is no longer merely an input line. We've moved from the era of "shell vs IDE" to the era of "structured shell + modern tools + AI."
Chapter 1 · Bash 5.2 — the standard, from Brian Fox to Chet Ramey
Bash is the GNU project's reference shell. Brian Fox wrote the first version in 1989, and since 1992 Chet Ramey (at Case Western Reserve University) has been almost the sole maintainer. As of May 2026 the stable version is 5.2.37 (patch release November 2025), with 5.3 announced for later in the year.
What landed in Bash 5.2
BASH_REMATCHis more reliable across regex matches.- The
globskipdotsshell option drops.and..from*expansion, killing a long-running gotcha. varredir_closecloses file descriptors safely when process substitution ends.${var@k}is a new parameter transformation that pulls out keys.wait -p VARstores the resulting PID into a variable.- Function tracing improvements make
set -T/trap ... DEBUGmore accurate.
What's coming in Bash 5.3
BASH_TRAPSIGexposes which signal the trap handled.- A curses-like mode is under discussion.
- Additional POSIX compatibility hardening.
Why are we still using Bash?
Three answers. 1) Ubiquity — Linux, macOS, WSL, BusyBox, Docker's alpine, almost everywhere. 2) CI/CD baseline — GitHub Actions, GitLab CI, Jenkins all default to bash. 3) 40 years of material — man bash, Stack Overflow, the ABS Guide — the cumulative knowledge dwarfs every alternative.
# Bash 5.2 features in action
shopt -s globskipdots # * no longer matches . or ..
echo *
# wait -p captures the background PID
sleep 5 &
wait -p PID $!
echo "the last background PID was $PID"
# New parameter transformation
declare -A map=([a]=1 [b]=2)
echo "${map[@]@k}" # list keys
The macOS trap — Bash 3.2.57
Here lies a major trap. The /bin/bash on macOS is Bash 3.2.57, frozen since 2007 because Apple refuses GPLv3. Anything 4.0+ (declare -A, mapfile, readarray, ${var,,} lowercasing, ** globstar) doesn't work. The fix: brew install bash to get 5.2, and never use #!/bin/bash — that grabs the 3.2 one. Use #!/usr/bin/env bash.
Chapter 2 · Zsh 5.9 — the macOS default, home of Oh My Zsh
Paul Falstad (Princeton) kicked off Zsh in 1990. The current stable in May 2026 is 5.9, with 5.10 imminent. Since Apple switched the macOS Catalina default shell from Bash 3.2 to Zsh 5.7 in 2019, more than half of developer laptops worldwide now run Zsh (Stack Overflow Developer Survey 2025).
What sets Zsh apart
- Global aliases —
alias -g G='| grep'lets you writecat foo G baranywhere. - Extended globbing —
**/*.go(.)matches only regular files,*(om[1,10])the 10 most recent. - Associative arrays are built in (Bash needs 4.0+).
- Multi-fanout redirection —
cmd > >(tee a) > >(tee b). - Rich prompt system —
PROMPT_SUBSTfor dynamic prompts. - An overwhelming completion system —
compinitknows nearly every command's options.
# Zsh extended globbing
setopt EXTENDED_GLOB
ls **/*.log~*backup* # every .log except backups
ls *(om[1,10]) # ten most recent files
ls *(/^F) # empty directories only
Oh My Zsh — the largest framework
Robby Russell started it in 2009. As of 2026 it has 170k GitHub stars. 200+ themes, 300+ plugins. git, kubectl, aws, docker, node, python plugins flip on completion and aliases with one line.
# ~/.zshrc example
plugins=(git kubectl aws docker fzf z)
ZSH_THEME="agnoster"
source $ZSH/oh-my-zsh.sh
Prezto / Zinit / Antidote — faster alternatives
Oh My Zsh is rich but can slow start-up. Faster options:
- Prezto — Sorin Ionescu, 2012. Modular, lightweight.
- Zinit — turbo mode asynchronously loads plugins, start-up around 50 ms.
- Antidote — newer generation, plain-text plugin file.
# Zinit turbo mode — async loading so the prompt isn't blocked
zinit wait lucid for \
OMZP::git \
OMZP::kubectl \
zsh-users/zsh-autosuggestions \
zdharma-continuum/fast-syntax-highlighting
Zsh's downsides
It is not 100% POSIX compatible. Some scripts behave differently from bash. So either annotate #!/usr/bin/env zsh explicitly or keep shared scripts in bash and use zsh only interactively — a common split.
Chapter 3 · fish 4 — the user-friendly shell, reborn in Rust
fish stands for "friendly interactive shell." Axel Liljencrantz launched it in 2005, and the 4.0 release in 2024 was a complete rewrite from C++ to Rust. May 2026 stable is 4.1, with start-up around 30–50 ms.
fish's signature — sensible defaults out of the box
- Autosuggestion — history-based suggestions are always on, displayed in gray and accepted with →.
- Syntax highlighting — red (broken command), green (valid), yellow (quoted) updates as you type.
- Abbreviations — stronger than aliases. They expand at input time, so history holds the real command.
- Rich option completion — it parses
manpages to learn options. - Web-based config —
fish_configopens a browser theme picker.
# fish syntax differs from bash. Variables, functions, and if are all clearer.
set -gx PATH $HOME/bin $PATH
function gco --description "git checkout"
git checkout $argv
end
# Abbreviations: expand at input time
abbr -a gc git commit
abbr -a gst git status
fish's downside — not POSIX
fish is not POSIX compatible. [ ], &&, || and other syntax differ. You cannot import .zshrc or .bashrc straight away. The usual compromise: keep shared scripts in bash, use fish for interactive shells.
What the 4.0 Rust rewrite delivered
- Start-up time roughly halved.
- Lower memory usage.
- The C++ memory-safety hazards went away.
- Lower barrier to entry for external contributors.
Chapter 4 · Nushell — structured-data pipelines
Nushell launched in 2019, started by Jonathan Turner and Yehuda Katz. It is written in Rust, and May 2026 stable is 0.105. The pitch is "every command exchanges data (tables)", inspired by PowerShell but fused with Unix philosophy, so it stays lighter and faster.
Signature — table-driven pipes
# Take a directory listing as a table, filter by size, sort, take 5
ls | where size > 1mb | sort-by modified | first 5
# JSON parses automatically
http get https://api.github.com/repos/nushell/nushell | get stargazers_count
# CSV → JSON in one line
open data.csv | to json | save data.json
Why it matters
In bash land you cobble together awk, cut, jq, xargs for text pipelines. Once parsing logic stacks up, readability dies. Nushell treats the table itself as a first-class object, so where, sort-by, group-by, select, each give you SQL-flavored expressions.
Limitations
- It does not run bash scripts as-is.
- External commands speak text, so you still convert with
from json/from csv. - Being 0.x, syntax shifts release to release — safer for interactive analysis than production scripting.
Who is it for?
- Data engineers — a delightful local shell for small data wrangling.
- Cloud operators —
kubectl get pods -o json | from json | where ...is natural.
Chapter 5 · Murex · xonsh · Elvish — third-path shells
Murex — a shell with a type system
A British developer, Andrew Stewart, builds it. May 2026 sits in the 6.x line. Variables carry types and JSON/YAML/TOML are first-class. A compromise between Nushell and bash compatibility.
# Declare a variable type
set: int counter = 0
set: string name = "world"
# JSON handling is first-class
open my.json -> [name age] -> format json
xonsh — a shell scripted in Python
Anthony Scopatz started it in 2015. Mix Python and shell in one file. Beloved by data scientists.
# .xonshrc — Python and shell interleaved
import os
echo @(os.getcwd())
# Shell output becomes a Python list
ls = $(ls -la).split('\n')
for line in ls:
print(line)
Elvish — the expressive shell
Qi Xiao (China) launched it in 2014, written in Go. Lambdas, closures, first-class functions live inside the shell. An early "structured shell" experiment that predated Nushell.
fn greet [name]{
echo "Hello, "$name
}
greet world
What they have in common
They trade POSIX compatibility for expressiveness. Using them for init scripts on production servers is risky, but as a personal laptop interactive shell or for data work they're attractive.
Chapter 6 · POSIX shell · dash · ksh · tcsh — legacy and minimalism
POSIX shell is the shell IEEE 1003.1 defines. All the shiny features of bash 4+ ([[ ]], (()), declare -A, <<<) are off the table. In exchange, it runs almost everywhere Unix runs.
dash (Debian Almquist SHell) is the minimal POSIX shell Debian adopted as /bin/sh. On Ubuntu and Debian, /bin/sh is dash. It starts 4–10x faster than bash, which keeps init scripts snappy.
# POSIX-compatible script template
#!/bin/sh
set -eu
# No [[ ]], only [ ]
if [ "$1" = "build" ]; then
echo "building..."
fi
# No arrays either — use positional parameters
set -- a b c
for x in "$@"; do echo "$x"; done
ksh (Korn Shell) was created by David Korn at Bell Labs in 1983. Once the standard on Solaris and AIX. ksh93 is the last major release; the ksh93u+m fork from 2020 keeps it alive. You'll only run into it on legacy AIX/Solaris systems.
tcsh is the C shell's descendant. Some BSDs used it as the default. Common in 1990s university Unix labs. By 2026 it is nearly unused — FreeBSD keeping it as root's default is about it.
When to use what
- Production init/cron scripts → POSIX
/bin/sh(dash/ash). Fast and dependable. - Developer laptop general scripts → bash 5.2. Rich features, sea of documentation.
- Interactive daily shell → zsh or fish.
- AIX/Solaris legacy → ksh.
Chapter 7 · Shell scripting strict mode — set -euo pipefail
Every script that opens with #!/usr/bin/env bash should put set -euo pipefail on the second line.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
What each option prevents
set -e(errexit) — exits immediately if a command returns non-zero. Does not apply insideif/while/||(deliberate exception).set -u(nounset) — undefined variables become errors, blocking silent typo failures.set -o pipefail— if any command in a pipeline fails, the whole pipeline fails. Default behavior only inspects the last command's exit code.IFS=$'\n\t'— restricts word splitting to newline/tab, safer for filenames with spaces.
The trap with set -e
set -e is not absolute. It does nothing inside cmd | other, cmd || true, if cmd; then .... So you need pipefail paired with it. set -e also interacts oddly with traps inside functions — when traps are involved, explicit if checks are safer.
Comparison — a well-formed script header
#!/usr/bin/env bash
# vim: set ft=bash ts=2 sw=2 :
set -Eeuo pipefail # -E propagates ERR traps into functions
IFS=$'\n\t'
# Debug mode: BASH_X=1 ./run.sh
if [[ "${BASH_X:-0}" == 1 ]]; then
set -x
fi
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
Chapter 8 · trap and mktemp — never forget cleanup
If a script creates temporary files, you must clean them up. A bare rm skips error paths. Use trap.
TMP=$(mktemp -d -t myapp.XXXXXX)
trap 'rm -rf "$TMP"' EXIT
# More elaborate: split by signal
cleanup() {
local exit_code=$?
rm -rf "$TMP"
if [[ $exit_code -ne 0 ]]; then
echo "script terminated abnormally (code=$exit_code)" >&2
fi
exit $exit_code
}
trap cleanup EXIT
trap 'echo "got INT"; exit 130' INT
trap 'echo "got TERM"; exit 143' TERM
mktemp best practices
- Always create a directory with
-dand place files inside — cleanup becomes one line. - Pass
-t prefix.XXXXXXfor easier debugging. - macOS
mktempflags differ from GNU. For cross-platform stick to the short formmktemp -d "tmp.XXXXXX".
Why not just use /tmp/myscript
Three reasons. 1) TOCTOU attack — someone pre-creating the same name to bypass permissions. 2) Collisions — two concurrent runs collide. 3) Cannot clean up safely — deleting another instance's files is dangerous.
Chapter 9 · Argument parsing — getopts and beyond
Start with getopts
It's a bash builtin, no external dependencies. Only single-character options, but that covers 99% of cases.
usage() {
cat <<'USAGE'
Usage: deploy.sh [-e env] [-n name] [-d]
-e env deployment environment (dev/staging/prod, required)
-n name service name (default: $(basename $PWD))
-d dry-run mode
-h this help
USAGE
}
DRY_RUN=0
NAME="$(basename "$PWD")"
while getopts ":e:n:dh" opt; do
case $opt in
e) ENV="$OPTARG" ;;
n) NAME="$OPTARG" ;;
d) DRY_RUN=1 ;;
h) usage; exit 0 ;;
\?) echo "unknown option: -$OPTARG" >&2; usage; exit 2 ;;
:) echo "option -$OPTARG needs an argument" >&2; usage; exit 2 ;;
esac
done
shift $((OPTIND - 1))
: "${ENV:?ENV is required (-e)}"
For long options — roll your own
# Handle long options like --help, --env=dev manually
while [[ $# -gt 0 ]]; do
case $1 in
--env=*) ENV="${1#*=}"; shift ;;
--env) ENV="$2"; shift 2 ;;
--help|-h) usage; exit 0 ;;
--) shift; break ;;
-*) echo "unknown option: $1" >&2; exit 2 ;;
*) ARGS+=("$1"); shift ;;
esac
done
Alternatives — bashly / docopt.sh
For complex CLIs, generate code with bashly (chapter 12) or auto-build a parser from your help text with docopt.sh.
Chapter 10 · Arrays and associative arrays — the core of bash 4+
Indexed arrays
arr=("a" "b" "c")
echo "${arr[0]}" # a
echo "${arr[@]}" # every element
echo "${#arr[@]}" # element count
arr+=("d") # append
unset 'arr[1]' # drop index 1 (mind sparse arrays after this)
Associative arrays — bash 4.0+, standard in zsh, broken on macOS bash 3.2
declare -A users
users[alice]=admin
users[bob]=guest
users[carol]=admin
for u in "${!users[@]}"; do
echo "$u: ${users[$u]}"
done
# Key existence check
[[ -v users[alice] ]] && echo "alice exists"
Passing arrays to functions — bash's eternal trap
# Wrong — the array flattens into one argument
print_arr() { for x in "$@"; do echo "$x"; done; }
arr=(a "b c" d)
print_arr "${arr[@]}" # Correct — quoted + @ expansion passes element-by-element
# Safer — nameref (bash 4.3+)
print_by_ref() {
local -n ref=$1
for x in "${ref[@]}"; do echo "$x"; done
}
print_by_ref arr
Chapter 11 · Functions, local, readonly, process substitution
Function guidelines
# Functions always use local. Otherwise globals leak.
greet() {
local name=$1
local greeting=${2:-"Hello"}
echo "$greeting, $name!"
}
# Return values go to stdout; exit codes are 0 / non-zero
add() {
local a=$1 b=$2
echo $((a + b))
}
sum=$(add 3 4)
readonly for constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly TIMEOUT_SECONDS=30
readonly -a ENVS=(dev staging prod)
Process substitution — <() and >()
A trick for what pipes cannot express. Avoids temporary files by feeding file descriptors directly.
# diff between two commands
diff <(sort file1) <(sort file2)
# Tee one output into multiple downstream commands
program | tee >(grep ERROR > err.log) >(grep WARN > warn.log) > full.log
# Pass content through an fd to a command that expects a file
psql -f <(echo "SELECT * FROM users WHERE id=$ID")
Chapter 12 · ShellCheck · shfmt · bashly — the modern shell workflow
ShellCheck — the ESLint for shell
Vidar Holen (Norway) launched it in 2012. Written in Haskell. May 2026 sits around 0.10. The standard practice: run ShellCheck on every PR.
# In CI
shellcheck script.sh
# Frequent findings
# SC2086: $var is unquoted — word-splitting hazard
# SC2046: $(...) result is unquoted
# SC2155: declare/local combined with command substitution loses the exit code
# SC2034: variable declared but never used
shfmt — the shell formatter
Daniel Martí (mvdan) maintains this Go tool. It sits next to shellcheck. gofmt-style consistent indentation and ordering for shell scripts.
# Indent two spaces, indent case patterns, handle stdin redirection
shfmt -i 2 -ci -sr -d script.sh
# pre-commit hook example
shfmt -l -w . # auto-format every .sh file
bashly — full-stack CLI in bash
Built by Danny Ben Shitrit. Define the command tree in YAML and bashly emits a complete bash CLI — argument parsing, help, version, completion.
# src/bashly.yml
name: mycli
help: my CLI tool
version: 1.0.0
commands:
- name: deploy
help: deploy to an environment
args:
- name: env
required: true
allowed: [dev, staging, prod]
flags:
- long: --dry-run
short: -d
help: dry-run mode
bashly generate
./mycli deploy prod --dry-run
Chapter 13 · fzf · ripgrep · fd · bat · eza — modern Unix tools
fzf — the fuzzy finder
Junegunn Choi (Korea, Kakao) started it in 2013. Written in Go. May 2026 sits around 0.55. One of the most influential open-source projects ever shipped by a Korean developer.
# fzf basics
ls | fzf
history | fzf
git log --oneline | fzf | awk '{print $1}' | xargs git show
# Key bindings (Ctrl+R for history, Ctrl+T for files, Alt+C for directories)
source /usr/share/fzf/key-bindings.bash
ripgrep (rg) — fast grep
Andrew Gallant (BurntSushi) built it. Written in Rust. 5–10x faster than grep, and it respects .gitignore by default.
# Basic usage
rg "TODO" --type rust
rg -i "error" -C 2 # case-insensitive, 2 lines of context
rg --files-with-matches "Apache"
fd — find replacement
David Peter (sharkdp) ships this Rust tool. More intuitive than find and faster.
fd "\.rs$" # every Rust file
fd -e py -t f # Python files only
fd -H "node_modules" # include hidden
bat — cat with syntax
Also by sharkdp. cat plus syntax highlighting and git diff display.
bat README.md
bat -p src/main.rs # plain mode, syntax only
eza — successor to exa, ls replacement
A fork of exa maintained by ogham. Actively maintained since 2024.
eza -l --git --icons
eza --tree --level 2
Why use all of this?
GNU coreutils ship a 50-year-old interface. Options aren't intuitive, output lacks color, .gitignore is invisible. These tools provide sensible defaults of the 2010s and beyond — colorized output, indexing, ignore-file awareness, JSON output.
Chapter 14 · System monitoring — bottom · btop · htop · glances
htop — Hisham Muhammad's late-1990s classic. Color + mouse + tree view. Still the most widely installed.
btop — Aristocratos's C++ successor. Richer visuals, GPU/disk/network graphs, mouse navigation.
bottom (btm) — ClementTsang's Rust implementation. Cross-platform with JSON-style config.
glances — Nicolas Hennion's Python project. Has a web API / Prometheus exporter for monitoring integration.
Comparison
| Tool | Language | Strength | System overhead |
|---|---|---|---|
| htop | C | classic, stable | lowest |
| btop | C++ | flashy, GPU | medium |
| bottom | Rust | cross-platform, config rich | medium |
| glances | Python | web API, exporter | a bit higher |
# bottom config sample (~/.config/bottom/bottom.toml)
[flags]
hide_avg_cpu = false
mem_as_value = true
network_use_binary_prefix = true
Chapter 15 · jq · yq · dasel · gron — structured data processing
jq — the JSON standard
Stephen Dolan started it in 2012. A functional JSON query language. Available everywhere.
curl -s api.github.com/users/torvalds | jq '.public_repos'
kubectl get pods -o json | jq '.items[] | .metadata.name'
cat data.json | jq -r '.users[] | select(.age > 30) | .name'
yq — jq for YAML
Mike Farah's Go implementation (mikefarah/yq) is the standard. Inherits jq syntax almost verbatim.
yq '.spec.replicas = 3' deployment.yaml
yq '.services | keys' docker-compose.yml
dasel — cross-format
Treats JSON, YAML, TOML, XML, CSV with the same query language.
dasel -f config.yaml '.database.host'
dasel put -f config.json '.version' -v '2.0'
gron — make JSON greppable
By Tom Hudson (TomNomNom). Flattens JSON into one-line key=value statements.
gron https://api.github.com/users/torvalds | grep public_repos
# json.public_repos = 7;
Each tool slots naturally into a shell pipeline. That's the point — the shell's ability to deal with JSON/YAML leapt forward in the 2020s.
Chapter 16 · Parallel execution — xargs · parallel · rush
xargs -P — the simplest parallelism
# 8 concurrent gzips
ls *.log | xargs -n 1 -P 8 gzip
# null-terminated input (safe for tricky filenames)
find . -name '*.log' -print0 | xargs -0 -n 1 -P 8 gzip
GNU parallel — Ole Tange's richer engine
parallel -j 8 gzip ::: *.log
parallel --bar 'convert {} {.}.png' ::: *.jpg
parallel --eta wget {} ::: $(cat urls.txt)
GNU parallel has tricky quoting, but supports ETA, progress bars, retry, and remote SSH-distributed execution.
rush — shenwei356's (China) Go implementation, easier quoting
rush 'echo Hello, {}' ::: a b c
rush -j 8 'gzip {}' ::: $(ls *.log)
When to use what
- Simple parallelism →
xargs -P. Fast and everywhere. - Progress bars / retries needed →
parallel. - Quoting headaches →
rush.
Chapter 17 · gum · charm — pretty TUIs from the shell
Charm.sh, run by Toby Padilla and Christian Rocha, is a collection of Go libraries and tools. They turn shell scripts into pretty interactive UIs.
# User input (text, multi-select, confirm)
NAME=$(gum input --placeholder "Your name?")
ENV=$(gum choose dev staging prod)
gum confirm "Really deploy?" && deploy.sh "$ENV"
# Spinner
gum spin --spinner dot --title "building..." -- ./build.sh
# Colored output
gum style --foreground 212 --bold "success!"
# Markdown rendering
gum format -- "# Title" "**bold**"
Why it matters
A bash script's interactive UI usually ends at read. gum lifts that into the modern era — color, spinner, multi-select, file picker. Great for CI/CD review tools, demos, and setup wizards.
Charm's other tools
- glow — markdown rendering in the terminal.
- vhs — scripted terminal GIF recording.
- bubble tea — Go TUI framework. lazygit, k9s belong to the same family.
Chapter 18 · just · task · xc · mise — modern task runners
just — Casey Rodarmor's modern Makefile
May 2026 sits at 1.40. Written in Rust. Keeps Makefile's good parts and drops the gotchas.
# justfile
default: build test
build:
cargo build --release
test:
cargo test
deploy env="dev":
@echo "Deploying to {{env}}"
./scripts/deploy.sh {{env}}
# Call bash directly
notes:
#!/usr/bin/env bash
set -euo pipefail
grep -r "TODO" src/
Why just beats Makefile
- No tabs-vs-spaces drama.
- Consistent variable expansion (Make's
$$vs$confusion is gone). - Clearer dependency syntax (
recipe-name: dep1 dep2). - No
.PHONYceremony — running commands is the default.
task (taskfile.dev) — YAML-based
# Taskfile.yml
version: '3'
tasks:
build:
cmds:
- go build ./...
test:
deps: [build]
cmds:
- go test ./...
xc — task runner that lives in markdown
By Joe Brockwill. Define tasks as code blocks in a README.md.
## build
```bash
cargo build --release
```
## test
Requires: build
```bash
cargo test
```
mise (formerly rtx) — version manager + task runner
Jordan Pittman's Rust tool. Began as an asdf successor; now also ships task running.
# .mise.toml
[tools]
node = "20"
python = "3.12"
[tasks.build]
run = "npm run build"
Selection criteria
- Simple command bundles → just. Lightest and most intuitive.
- Complex dependency graphs → task (taskfile).
- Version pinning too → mise.
- Self-documenting → xc.
Chapter 19 · Shell prompts — Starship · Oh My Posh · Powerline
Starship — universal, fast, Rust-built prompt
Matan Kushner and Tim Sosa started it in 2019. Supports bash, zsh, fish, PowerShell, Nushell, ion, elvish, tcsh, xonsh, cmd. Configured by a single TOML file.
# ~/.config/starship.toml
add_newline = false
format = "$directory$git_branch$git_status$character"
[directory]
truncation_length = 3
truncate_to_repo = true
[git_branch]
symbol = " "
[character]
success_symbol = "[➜](bold green)"
error_symbol = "[➜](bold red)"
# bash
eval "$(starship init bash)"
# zsh
eval "$(starship init zsh)"
# fish
starship init fish | source
Oh My Posh — cross-shell, Windows-friendly
Jan De Dobbeleer's work. Originally PowerShell-only, now everywhere. Popular with Windows users. 100+ themes.
Powerline — legacy
Written in Python. Pioneered the first "pretty prompt" generation. Effectively unused in 2026 — Starship took over almost every slot.
Why Starship became standard
- Shell-independent — one config file gives the same prompt anywhere. 2) Fast — Rust-built with background caching. 3) The right information — git branch, language version, Kubernetes context, AWS profile, all auto-detected.
Chapter 20 · AI shells — sgpt · gptme · Warp · Amazon Q
shell_gpt (sgpt) — TheR1D
Python-based. Calls the OpenAI API from the shell.
sgpt "find all .log files modified today"
# Output: find . -name "*.log" -mtime -1
sgpt --shell "compress all png in this dir"
# (Enter to execute, e to edit)
sgpt --code "fibonacci in rust"
gptme — Bjorn Holm
Same concept but supports multi-turn conversations and tool calls (read/write/run files). Local LLMs also plug in.
Warp Terminal — the AI-first terminal
Run by Zach Lloyd since 2022. Linux support landed in 2024 and stable in 2025. AI command suggestions, AI explain command, notebooks. Free and Pro plans exist.
# Warp's # command
# how do I rebase from main?
→ Warp suggests the git rebase command and you Enter to run
Amazon Q Developer (formerly Fig)
Amazon acquired Fig in 2024 and merged it into the Amazon Q Developer CLI. Completion got stronger and natural-language command generation arrived.
Aiken · Codex CLI
Anthropic Claude Code, GitHub Copilot CLI, OpenAI Codex CLI and similar tools claimed their seat as "agents" that execute shell commands directly. The shell is now an interface for agents, not only humans.
Two models for shell + AI
- Suggest mode — AI proposes a command; the human confirms (sgpt, Warp).
- Agent mode — AI runs the shell directly, observes results, and chooses the next move (Claude Code, Codex CLI).
Agent mode is risky, so containerized or sandboxed execution is the safer pattern.
Chapter 21 · Terminal emulators — Ghostty · Wezterm · Alacritty · Kitty · iTerm2
Ghostty — Mitchell Hashimoto's new project
The HashiCorp co-founder (Vagrant, Packer, Terraform) Mitchell Hashimoto launched Ghostty in 2024, with 1.0 stable in 2025 and 1.2 by May 2026. Written in Zig with GPU acceleration, native macOS plus Linux. The signatures: "the defaults are excellent" and "native macOS integration."
# ~/.config/ghostty/config
font-family = "JetBrains Mono"
font-size = 14
theme = "Catppuccin Mocha"
window-padding-x = 8
window-padding-y = 8
Wezterm — Wez Furlong
Written in Rust. Configured in Lua. Tabs/panes, ligatures, embedded SSH client.
local wezterm = require 'wezterm'
return {
font = wezterm.font 'JetBrains Mono',
font_size = 14,
color_scheme = 'Dracula',
hide_tab_bar_if_only_one_tab = true,
}
Alacritty — Joe Wilm
Rust-built. Among the fastest terminals. TOML config. No tabs/panes (use with tmux).
Kitty — Kovid Goyal
Python plus GPU. Strong image protocol (can display PNG directly).
iTerm2 — George Nachman
The macOS classic. Profiles, hotkeys, split panes, search, broadcast — feature dense. Still a macOS standard in 2026.
Hyper · Tabby
Electron-based. Low adoption — heavy with weak GPU acceleration.
Selection criteria
- macOS lightweight → Ghostty or iTerm2.
- Cross-platform lightweight → Alacritty (+tmux) or Kitty.
- Rich configuration → Wezterm.
- AI integration → Warp.
Chapter 22 · Korea/Japan ops culture — Coupang · LINE · Cybozu · Mercari
Korea — Coupang shell ops
Coupang's infrastructure ops team handles massive traffic and, since the early 2020s, has standardized on bash + Ansible + Terraform. Shell best practices (strict mode, ShellCheck CI gate) have been adopted as PR rules internally, as their Korean conference talks (if-kakao, AWS Summit Seoul) describe.
Korea — Kakao and the origin of fzf
The fzf you met in chapter 13 is the work of Junegunn Choi, a former Kakao engineer. He first built fzf inside Kakao and later open-sourced it on GitHub, where it became a global standard. It counts among the most influential pieces of open source ever shipped by a Korean developer.
Japan — LINE shell operations
LINE runs a huge fleet of microservices out of Tokyo. Posts from the LINE Engineering blog covering internal shell-script conventions in the late 2010s are frequently cited in both Korea and Japan — strict mode, mktemp, trap, and ShellCheck are all mandatory.
Japan — Cybozu's operational tooling
Cybozu's Kintone team built ops tools in a bash + Ruby + Go combo for years. Internal tools like kintone-cli written in bash are documented on Cybozu Developer Network.
Japan — Mercari's SRE shell culture
Mercari, running GCP-based microservices, has publicly described (at SRE NEXT, SREcon) a policy of minimizing shell scripting and replacing it with Go CLIs. Yet they still hold bash as the standard for boot, debugging, and emergency ops.
Shared patterns
All three companies share: 1) mandatory set -euo pipefail, 2) ShellCheck as a CI gate, 3) rewriting scripts over 1,000 lines in Go/Python, 4) standardizing on #!/usr/bin/env bash.
Chapter 23 · Where the shell goes 2026–2028
Outlook 1 — Bash 5.3 to 6.0
5.3 arrives within 2026, and 6.0 likely lands around 2027–2028. No big shifts — Bash's philosophy is "don't break compatibility." Internal cleanup, performance, and error messages keep improving.
Outlook 2 — fish 4's Rust effect
Since the 2024 Rust rewrite, start-up time and memory have dropped sharply. 2026–2028 should bring more external contributors and faster delivery of new features (structured data, etc.).
Outlook 3 — Nushell crossing the threshold
Entering production scripting is still hard, but it is solidifying its place as an interactive shell for data engineers and cloud operators. Once 1.0 ships (predicted 2027) adoption will accelerate.
Outlook 4 — AI integration standardization
Agent-mode shell usage will go mainstream. Along with it, sandboxing, permission limits, and command logging will become built-in features of the shell. Agents like Claude Code and Codex CLI treat the shell as a "second user."
Outlook 5 — Rust rewrites of core tools
ripgrep, fd, eza, bat, bottom, just, mise, Starship — nearly every modern tool is already Rust. By 2028 we'll likely see the "Unix-core-tool generation change to Rust" mostly complete.
Outlook 6 — POSIX's place
POSIX shell isn't going anywhere. It survives as init script, Docker alpine, embedded Linux, and the lowest common denominator of CI matrices. So portable scripts should still ship #!/bin/sh as the best practice.
Chapter 24 · Shell scripting checklist (10 items)
Ten things to verify every time you write a shell script.
- shebang —
#!/usr/bin/env bashor, when portable,#!/bin/sh. - strict mode —
set -Eeuo pipefailon the second line. - IFS —
IFS=$'\n\t'for safety on filenames with spaces. - mktemp — always
mktemp -dfor temporaries withtrap '...' EXITto clean up. - Quoting —
$varis almost always quoted as"$var".${arr[@]}becomes"${arr[@]}". - Exit codes — function results to stdout, success is 0, failure is non-zero.
- shellcheck — required in CI. Suppress warnings explicitly with
# shellcheck disable=.... - shfmt — formatting consistency. Run as a pre-commit hook.
- Error messages — send to stderr (
>&2) and exit with a specific code. - Documentation — top comment block with usage, arguments, dependencies, examples.
Chapter 25 · A catalog of seven real-world anti-patterns
Anti-pattern 1 — unquoted variables
# Dangerous
rm $file # explodes if $file has spaces or globs
# Safe
rm -- "$file"
Anti-pattern 2 — relying on set -e without pipefail
In curl ... | jq .x, if curl fails but jq succeeds on empty input, the pipeline looks successful.
Anti-pattern 3 — not checking cd
# Dangerous
cd /tmp/build
rm -rf * # if cd fails, you blow up the current directory
# Safe
cd /tmp/build || exit 1
rm -rf -- ./*
Anti-pattern 4 — mixing [ ] and [[ ]]
Bash uses [[ ]] (a keyword). Use [ ] only for POSIX compatibility. Do not mix.
Anti-pattern 5 — backticks (`cmd`)
Use $(cmd). Nestable, quoting-safe, more readable.
Anti-pattern 6 — eval
Never feed user input to eval. There is nearly always another way (arrays, functions, bash -c "$(...)").
Anti-pattern 7 — a monstrous single script
Over 1,000 lines? Consider rewriting in Go/Python. The shell's strength is glue — wiring other tools together. Business logic belongs in a real language.
Chapter 26 · Conclusion — the shell still sits in the middle of infrastructure
37 years ago when Brian Fox first released bash, he was building "a free Bourne shell replacement" as part of GNU. That single decision now lives at the innermost layer of nearly every computer.
The 2026 shell landscape summarized:
- Bash 5.2 is the standard — works everywhere, the scripting default.
- Zsh 5.9 is macOS default — interactive only.
- fish 4 is reborn in Rust — friendly interactive.
- Nushell is the structured-data shell — for data engineers and cloud operators.
- ShellCheck + shfmt + bashly are the heart of the modern workflow.
- fzf, ripgrep, fd, bat, eza, just, mise, Starship are the next-generation tools.
- gum brings pretty TUIs to the shell.
- sgpt, Warp, Claude Code ship AI integration.
- Ghostty, Wezterm, Alacritty, Kitty are the next-gen terminals.
The shell isn't going away. It grows richer, safer, and more expressive. Our job is to pick the right tools, write robustly with strict mode and shellcheck, and have the courage to leave the shell when scripts exceed 1,000 lines.
The shell lives at the center of operations. As long as operations exist, the shell does too.
References
- GNU Bash Manual — https://www.gnu.org/software/bash/manual/
- Bash 5.2 Release Notes — https://lists.gnu.org/archive/html/bug-bash/2022-09/msg00214.html
- Zsh Documentation — https://zsh.sourceforge.io/Doc/
- Oh My Zsh — https://ohmyz.sh/
- fish shell — https://fishshell.com/
- Nushell Book — https://www.nushell.sh/book/
- Murex — https://murex.rocks/
- xonsh — https://xon.sh/
- Elvish — https://elv.sh/
- ShellCheck — https://www.shellcheck.net/
- shfmt (mvdan) — https://github.com/mvdan/sh
- bashly — https://bashly.dannyb.co/
- gum (Charm) — https://github.com/charmbracelet/gum
- fzf — https://github.com/junegunn/fzf
- ripgrep — https://github.com/BurntSushi/ripgrep
- fd — https://github.com/sharkdp/fd
- bat — https://github.com/sharkdp/bat
- eza — https://github.com/eza-community/eza
- bottom — https://github.com/ClementTsang/bottom
- just — https://github.com/casey/just
- taskfile.dev — https://taskfile.dev/
- mise — https://mise.jdx.dev/
- Starship — https://starship.rs/
- Oh My Posh — https://ohmyposh.dev/
- Warp — https://www.warp.dev/
- Ghostty — https://ghostty.org/
- Wezterm — https://wezfurlong.org/wezterm/
- Alacritty — https://alacritty.org/
- POSIX Shell Standard — https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
- Google Shell Style Guide — https://google.github.io/styleguide/shellguide.html