Skip to content
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

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_REMATCH is more reliable across regex matches.
  • The globskipdots shell option drops . and .. from * expansion, killing a long-running gotcha.
  • varredir_close closes file descriptors safely when process substitution ends.
  • ${var@k} is a new parameter transformation that pulls out keys.
  • wait -p VAR stores the resulting PID into a variable.
  • Function tracing improvements make set -T/trap ... DEBUG more accurate.

What's coming in Bash 5.3

  • BASH_TRAPSIG exposes 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 materialman 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 aliasesalias -g G='| grep' lets you write cat foo G bar anywhere.
  • 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 redirectioncmd > >(tee a) > >(tee b).
  • Rich prompt systemPROMPT_SUBST for dynamic prompts.
  • An overwhelming completion systemcompinit knows 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 man pages to learn options.
  • Web-based configfish_config opens 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 inside if/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 -d and place files inside — cleanup becomes one line.
  • Pass -t prefix.XXXXXX for easier debugging.
  • macOS mktemp flags differ from GNU. For cross-platform stick to the short form mktemp -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

ToolLanguageStrengthSystem overhead
htopCclassic, stablelowest
btopC++flashy, GPUmedium
bottomRustcross-platform, config richmedium
glancesPythonweb API, exportera 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 .PHONY ceremony — 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

  1. 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

  1. Suggest mode — AI proposes a command; the human confirms (sgpt, Warp).
  2. 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.

  1. shebang#!/usr/bin/env bash or, when portable, #!/bin/sh.
  2. strict modeset -Eeuo pipefail on the second line.
  3. IFSIFS=$'\n\t' for safety on filenames with spaces.
  4. mktemp — always mktemp -d for temporaries with trap '...' EXIT to clean up.
  5. Quoting$var is almost always quoted as "$var". ${arr[@]} becomes "${arr[@]}".
  6. Exit codes — function results to stdout, success is 0, failure is non-zero.
  7. shellcheck — required in CI. Suppress warnings explicitly with # shellcheck disable=....
  8. shfmt — formatting consistency. Run as a pre-commit hook.
  9. Error messages — send to stderr (>&2) and exit with a specific code.
  10. 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

  1. GNU Bash Manual — https://www.gnu.org/software/bash/manual/
  2. Bash 5.2 Release Notes — https://lists.gnu.org/archive/html/bug-bash/2022-09/msg00214.html
  3. Zsh Documentation — https://zsh.sourceforge.io/Doc/
  4. Oh My Zsh — https://ohmyz.sh/
  5. fish shell — https://fishshell.com/
  6. Nushell Book — https://www.nushell.sh/book/
  7. Murex — https://murex.rocks/
  8. xonsh — https://xon.sh/
  9. Elvish — https://elv.sh/
  10. ShellCheck — https://www.shellcheck.net/
  11. shfmt (mvdan) — https://github.com/mvdan/sh
  12. bashly — https://bashly.dannyb.co/
  13. gum (Charm) — https://github.com/charmbracelet/gum
  14. fzf — https://github.com/junegunn/fzf
  15. ripgrep — https://github.com/BurntSushi/ripgrep
  16. fd — https://github.com/sharkdp/fd
  17. bat — https://github.com/sharkdp/bat
  18. eza — https://github.com/eza-community/eza
  19. bottom — https://github.com/ClementTsang/bottom
  20. just — https://github.com/casey/just
  21. taskfile.dev — https://taskfile.dev/
  22. mise — https://mise.jdx.dev/
  23. Starship — https://starship.rs/
  24. Oh My Posh — https://ohmyposh.dev/
  25. Warp — https://www.warp.dev/
  26. Ghostty — https://ghostty.org/
  27. Wezterm — https://wezfurlong.org/wezterm/
  28. Alacritty — https://alacritty.org/
  29. POSIX Shell Standard — https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
  30. Google Shell Style Guide — https://google.github.io/styleguide/shellguide.html