90 of 90 examples
Footgun: in bash the loop for name in $@ word-splits "carol smith"
into two names unless every expansion is quoted just so. Here $ARGS
is a list of String values, never a re-lexed token soup, so a name
with spaces cannot fracture.
set -euo pipefail
if [ "$#" -eq 0 ]; then
set -- world
fi
for name in "$@"; do
echo "hello, $name"
done
let names = if !{is-empty $ARGS} {
return [world]
} else {
return $ARGS
}
for $names { |n| echo "hello, $n" }
Footgun: on a b a b, uniq drops only adjacent repeats and returns the
input unchanged; sort -u returns a b with the order destroyed; one
slipped character — awk '!++seen[$0]' — prints nothing. All three exit 0
in silence, so the awk below is the hand-rolled escape hatch. Here the set
of seen lines is the accumulator and has is the test, not ++'s old value.
set -euo pipefail
awk '!seen[$0]++'
fold-lines { |seen line|
if !{has $seen $line} { return $seen } else {
echo $line
return [...$seen, $line: true]
}
} [:]
Footgun: with no *.JPEG match, bash leaves the glob unexpanded, so the
loop runs once with f set to the literal string *.JPEG and mv fails
on a file that isn't there — you need shopt -s nullglob to remember.
Here glob returns [] on no match, and the is-empty guard makes
"nothing to do" a clean exit.
set -euo pipefail
dir="$1"; from="$2"; to="$3"
for f in "$dir"/*."$from"; do
mv "$f" "${f%."$from"}.$to"
done
let [folder, from, to] = $ARGS
let targets = glob "$folder/*.$from"
if !{is-empty $targets} {
echo "no *.$from files in $folder"
exit 0
}
for $targets { |old|
let stem = basename $old ".$from"
let new = "/$stem.$to"
echo "$old -> $new"
mv $old $new
}
Footgun: find -printf '%s\t%p\n' | sort -rn | head trusts filenames to
hold no newline — one that does forges an extra line sort mis-ranks —
while a tab in a name shifts size and path apart for any later field-cut.
Here each file-info record is a typed value; sorting and slicing operate
on records, not delimited text.
set -euo pipefail
root="${1:-.}"
find "$root" -type f -printf '%s\t%p\n' | sort -rn | head
let [root] = $ARGS
let stats = map { |p|
return [path: $p, size: !{file-info $p}[size]]
} !{filter { |p| is-file $p } !{glob "$root/**/*"}}
let biggest = take 10 !{reverse !{sort-list-by { |s| return $s[size] } $stats}}
for $biggest { |s|
echo "$s[size]\t$s[path]"
}
Footgun: the find | awk -F'\t' | sort idiom keys its size buckets by a string field that a tab in a filename splits apart. Here the bucket map is keyed by the actual extension value and aggregated with a fold — nothing is stringly typed.
set -euo pipefail
root="${1:-.}"
find "$root" -type f -printf '%s\t%f\n' \
| awk -F'\t' '{n=split($2,a,".");s[a[n]]+=$1} END {for (k in s) print s[k],k}' \
| sort -rn
let [root] = $ARGS
let files = filter { |p| is-file $p } !{glob "$root/**/*"}
let ext-of = { |p|
let name = basename $p
if !{re-match '.\.' $name} { return !{re-replace '^.*\.' '' $name} } else { return '' }
}
let totals = fold { |acc p|
let key = !{ext-of $p}
let bytes = !{file-info $p}[size]
let prev = !{get $acc $key 0}
return [...$acc, $key: $[$prev + $bytes]]
} [:] $files
let rows = map { |k| return [ext: $k, size: $totals[$k]] } !{keys $totals}
let sorted = reverse !{sort-list-by { |r| return $r[size] } $rows}
for $sorted { |r|
let label = if !{equal $r[ext] ''} { return '(none)' } else { return ".$r[ext]" }
echo "$r[size]\t$label"
}
Footgun: the obvious reader for key=value config is source app.env, which
executes it — LOG=$(rm -rf "$HOME/tmp") deletes on read, path=… clobbers
the script's own variable, WIN=C:\Users\me loses backslashes. Hand-parsing
costs read -r, || [[ -n $line ]], ${pairs[@]+…} and a jq fold (jq's
$ARGS.named keeps the first dup key) — every time. ral only ever parses it.
set -euo pipefail
[[ $# -eq 1 ]] || { printf 'usage: kv-to-json.sh <file>\n' >&2; exit 1; }
path="$1"
[[ -f "$path" ]] || { printf 'kv-to-json.sh: %s: not a file\n' "$path" >&2; exit 1; }
pairs=()
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" == *=* ]] || continue
key="${line%%=*}"
val="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
val="${val#"${val%%[![:space:]]*}"}"
val="${val%"${val##*[![:space:]]}"}"
[[ -n "$key" && "$key" != \#* ]] || continue
if [[ "$val" == '"'*'"' || "$val" == "'"*"'" ]]; then
val="${val:1:${#val}-2}"
fi
pairs+=("$key" "$val")
done < "$path"
# `--arg`/$ARGS.named keeps the first of a repeated key; `add` keeps the last.
jq -nSc '[range(0; ($ARGS.positional | length); 2)
| {($ARGS.positional[.]): $ARGS.positional[. + 1]}] | add // {}' \
--args ${pairs[@]+"${pairs[@]}"}
let [path] = $ARGS
let lines = if !{is-file $path} { return !{from-lines-list $path} } else {
fail [status: 1, message: "kv-to-json: : not a file"]
}
let quoted = #'^"((?s:.*))"$|^'((?s:.*))'$'#
let trim = { |s| re-replace-all #'^\s+|\s+$'# '' $s }
let unquote = { |v|
if !{re-match $quoted $v} { return !{re-replace $quoted '${1}${2}' $v} } else { return $v }
}
let settings = fold { |acc line|
let key = !{trim !{re-find-match #'^[^=]*'# $line}}
let value = !{trim !{re-replace #'^[^=]*='# '' $line}}
return !{union $acc [$key: !{unquote $value}]}
} [:] !{filter { |l| re-match #'^\s*[^#=\s][^=]*='# $l } $lines}
to-line !{to-json $settings}
Footgun: awk's -F',' splits on every comma, so one quoted field with an
embedded comma shifts every later column, and the columns survive only as
$col[...] index arithmetic. Here from-csv is a real RFC-4180 parse
into records, so a quoted comma stays put and columns are keys by name.
set -euo pipefail
path="$1"; group_col="$2"; value_col="$3"
awk -F',' -v g="$group_col" -v v="$value_col" '
NR==1 { for (i=1;i<=NF;i++) col[$i]=i; next }
{ k=$col[g]; sum[k]+=$col[v]; n[k]++ }
END { for (k in sum) printf "%s\t%s\t%d\n", k, sum[k]/n[k], n[k] }
' "$path"
let [path, group_col, value_col] = $ARGS
let rows = from-csv < $path
let buckets = fold { |acc r|
let k = $r[$group_col]
let v = float $r[$value_col]
let prev = !{get $acc $k [count: 0, total: 0.0]}
return [...$acc, $k: [count: $[$prev[count] + 1], total: $[$prev[total] + $v]]]
} [:] $rows
echo "$group_col\tavg($value_col)\tn"
for !{keys $buckets} { |k|
let b = $buckets[$k]
let count = float $b[count]
let avg = $[$b[total] / $count]
echo "$k\t$avg\t$b[count]"
}
Footgun: jq '.count+=1' state.json > state.json truncates the file
before jq reads it — you must hand-roll tmp-then-rename for atomicity,
and remember it every time. In ral > is rename-on-close: a reader
sees the old file or the new, never a torn write.
set -euo pipefail
path="$1"
stamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
tmp="$(mktemp)"
jq --arg t "$stamp" '.count += 1 | .history += [$t]' "$path" > "$tmp"
mv "$tmp" "$path"
echo "tick $(jq .count "$path") at $stamp"
let [path] = $ARGS
let state = if !{is-file $path} {
return !{from-json < $path}
} else {
return [count: 0, history: []]
}
let stamp = !{date -u +%Y-%m-%dT%H:%M:%SZ | from-string}
let next = [
...$state,
count: $[$state[count] + 1],
history: [...$state[history], $stamp],
]
to-json $next > $path
echo "tick $next[count] at $stamp"
Footgun: xargs -P8 gives parallelism by handing N children one pipe, and
each flushes its stdout buffer wherever the buffer filled — mid-line. Once
the tree is big enough to need a second child, lines come back spliced —
half a hash onto half a path, a dozen per 6000 files — and wc -l still
counts 6000. par distributes and collects values: no stream to tear.
set -euo pipefail
root="${1:-.}"
jobs="${2:-8}"
find "$root" -type f -print0 \
| xargs -0 -P "$jobs" sha256sum -- \
| LC_ALL=C sort -k2
let [root, ...rest] = $ARGS
let workers = if !{is-empty $rest} { return 8 } else { return !{int $rest[0]} }
let walk = { |dir|
let by-type = group-by { |e| return $e[type] } !{list-dir $dir}
let here = map { |e| return "$dir/$e[name]" } !{get $by-type 'file' []}
let below = flat-map { |e| walk "$dir/$e[name]" } !{get $by-type 'dir' []}
return [...$here, ...$below]
}
let manifest = par { |p|
return [hash: !{words !{sha256sum -- $p | from-string}}[0], path: $p]
} !{sort-list !{walk $root}} $workers
for $manifest { |r| echo "$r[hash] $r[path]" }
Footgun: xargs -P8 curl -o out/$(basename {}) swallows every per-URL
failure — a 404 vanishes into the exit-status noise — and two URLs with
the same basename clobber one file. Here par returns a structured
result per input, so failures land in the manifest and "what broke?" is
one filter.
set -euo pipefail
list_file="$1"; out_dir="$2"
mkdir -p "$out_dir"
xargs -P 8 -I{} curl -fsSL --max-time 15 -o "$out_dir/$(basename {})" {} < "$list_file"
let [list_file, out_dir] = $ARGS
let urls = from-lines-list $list_file
mkdir -p $out_dir
let fetch = { |url|
let dst = "$out_dir/"
try {
curl -fsSL --max-time 15 $url > $dst
return [url: $url, path: $dst, ok: true, status: 0]
} { |err|
return [url: $url, path: '', ok: false, status: $err[status]]
}
}
let results = par $fetch $urls 8
to-json $results > "$out_dir/manifest.json"
let ok = filter { |r| return $r[ok] } $results
let bad = filter { |r| return $[not $r[ok]] } $results
echo "fetched: ok, failed"
for $bad { |r|
warn " status=$r[status] $r[url]"
}
Footgun: tail -f a b c interleaves its ==> file <== banners with the
lines they label once two files write at once; the &-and-sed fix needs a
manual pid array plus a trap to clean up. Here watch LABEL { … }
line-frames each stream, so every line emerges whole and prefixed.
set -euo pipefail
if [ "$#" -eq 0 ]; then
echo 'usage: tail-multi.sh FILE...' >&2
exit 2
fi
pids=()
for f in "$@"; do
tail -f "$f" | sed "s|^|[$(basename "$f")] |" &
pids+=($!)
done
trap 'kill "${pids[@]}" 2>/dev/null' EXIT
wait
if !{is-empty $ARGS} {
warn 'usage: tail-multi.ral FILE...'
exit 2
}
let handles = map { |f|
return !{watch !{basename $f} { tail -f $f }}
} $ARGS
for $handles { |h| await $h }
Footgun: bash has no in-language sandbox — you reach for chroot + setuid
+ seccomp, or bwrap, each a separate process-scoped layer where one wrong
bind-mount silently leaves a hole. Here grant attenuates authority
in-language: deny-by-default, one expression around the code.
set -euo pipefail
sandbox="$(mktemp -d)"
trap 'rm -rf "$sandbox"' EXIT
echo 'outside the sandbox:'
echo ok > "$sandbox/scratch" && echo ' allowed write inside /tmp'
echo
echo 'inside a bubblewrap sandbox (rw only the scratch dir, no net, no exec):'
bwrap \
--ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 \
--bind "$sandbox" "$sandbox" \
--unshare-all --die-with-parent \
/bin/sh -c '
echo ok > "'"$sandbox"'/inside" && echo " allowed write inside sandbox"
echo no > /tmp/escape && echo " allowed write outside sandbox"
cat /etc/hosts >/dev/null 2>&1 && echo " allowed read /etc/hosts"
curl http://example.com 2>/dev/null && echo " allowed exec curl"
'
let sandbox = temp-dir
let try-op = { |label body|
let outcome = try { !$body ; return 'allowed' } { |_| return 'blocked' }
echo " $outcome $label"
}
echo 'outside the grant block:'
try-op 'write inside /tmp' { to-string 'ok' > "$sandbox/scratch" }
try-op 'read /etc/hosts' { let _ = !{from-string < /etc/hosts} ; return () }
echo ''
echo 'inside grant [exec: [:], fs: [read+write only the sandbox]]:'
grant [exec: [:], fs: [read: [$sandbox], write: [$sandbox]]] {
try-op 'write inside sandbox' { to-string 'ok' > "$sandbox/inside" }
try-op 'write outside sandbox' { to-string 'no' > "/tmp/escape" }
try-op 'read /etc/hosts' { let _ = !{from-string < /etc/hosts} ; return () }
try-op 'exec curl' { curl http://example.com 2> /dev/null }
}
rm -rf $sandbox
Footgun: ls -t | tail -n +N | while read -r f parses ls output as
text — a backup whose name holds a newline splits into two lines, and
read trims surrounding whitespace, so a wrong or nonexistent path is
handed to rm. Here entries are sorted by their mtime value and each
doomed path stays one atom through rm.
set -euo pipefail
dir="$1"
keep="$2"
cd "$dir"
ls -t | tail -n +$((keep + 1)) | while read -r f; do
echo "removing $f"
rm "$f"
done
let [folder, keep] = $ARGS
let backups = filter { |p| is-file $p } !{glob "$folder/*"}
let newest-first = reverse !{sort-list-by { |p| return !{file-info $p}[mtime] } $backups}
for !{drop !{int $keep} $newest-first} { |p|
echo "removing $p"
rm $p
}
Footgun: bash extracts the extension with ${f##*.}, which for a
file that has no dot returns the entire name — so an extensionless
file is renamed with a garbage ".name" suffix. Here ext inspects
the split parts and yields '' when there is no extension.
set -euo pipefail
dir=$1; prefix=$2
i=1
for f in "$dir"/*; do
[ -f "$f" ] || continue
ext=${f##*.}
printf -v seq '%04d' "$i"
mv -- "$f" "$dir/$prefix-$seq.$ext"
i=$((i + 1))
done
let [root, prefix] = $ARGS
let pad = { |n|
let go = { |s| if $[!{length $s} >= 4] { return $s } else { return !{go "0$s"} } }
return !{go !{str $n}}
}
let ext = { |name|
let parts = re-split '\.' $name
if $[!{length $parts} > 1] { return "." } else { return '' }
}
let files = sort-list !{filter { |p| is-file $p } !{glob "$root/*"}}
for !{enumerate $files} { |e|
let src = $e[item]
let name = !{file-info $src}[name]
let num = !{pad $[$e[index] + 1]}
let dst = "$root/$(prefix)-$num"
mv $src $dst
echo "$src -> $dst"
}
Footgun: bash has no CSV codec. The waiver list is read with
IFS=, read -r link _, so a quoted field holding a comma
("Q3, final.pdf") splits at that comma, its waiver matches no link,
and the link is reported broken anyway — then printf writes that same
name back unquoted, as two fields. from-csv/to-csv quote for you.
set -euo pipefail
root="$1"
waivers="$2"
waived=()
{
read -r _
while IFS=, read -r link _; do
waived+=("$link")
done
} < "$waivers"
rows=()
while IFS= read -r -d '' link; do
if [ -e "$link" ]; then continue; fi
for w in ${waived[@]+"${waived[@]}"}; do
if [ "$w" = "$link" ]; then continue 2; fi
done
rows+=("$link,$(readlink -- "$link")")
done < <(find "$root" -path '*/.*' -prune -o -type l -print0 | LC_ALL=C sort -z)
if [ "${#rows[@]}" -gt 0 ]; then
printf 'link,target\n'
printf '%s\n' "${rows[@]}"
fi
let [root, waivers] = $ARGS
let waived = map { |r| return $r[link] } !{from-csv < $waivers}
let links = filter { |p| is-link $p } !{glob "$root/**/*"}
let broken = filter { |p|
$[not !{succeeds { resolve-path $p }} && not !{contains $waived $p}]
} $links
to-csv !{map { |p| return [link: $p, target: !{file-info $p}[target]] } $broken}
Footgun: echo "$new" > VERSION truncates the file to zero before
writing, so a crash — or a concurrent reader — can catch VERSION empty
or half-written. In ral > is rename-on-close: the new contents are
staged and swapped atomically, so a reader sees the old string or the
new one, never a torn file.
set -euo pipefail
path="$1"
IFS=. read -r major minor patch < "$path"
new="$major.$minor.$((patch + 1))"
echo "$new" > "$path"
echo "$new"
let [path] = $ARGS
let [major, minor, pat] = re-split '\.' !{from-line < $path}
let next = "$major.$minor."
to-string $next > $path
echo $next
Footgun: bash expr $1 $2 $3 and $(( $* )) re-parse the arguments as code —
* globs against the cwd, and $(( )) will happily evaluate an argument like
x[$(reboot)]. Here operands go through int (a real failure on non-numbers)
and each operator is matched explicitly; nothing is re-lexed or eval'd.
set -euo pipefail
echo "$(( $* ))"
let [first, ...rest] = $ARGS
let step = { |a op b|
if !{equal $op '+'} { return $[$a + $b] }
elsif !{equal $op '-'} { return $[$a - $b] }
elsif !{equal $op 'x'} { return $[$a * $b] }
elsif !{equal $op '*'} { return $[$a * $b] }
elsif !{equal $op '/'} { return $[$a / $b] }
elsif !{equal $op '%'} { return $[$a % $b] }
else { fail [status: 2, message: "unknown operator: $op"] }
}
let go = { |acc toks|
if !{is-empty $toks} { return $acc } else {
let [op, n, ...more] = $toks
return !{go !{step $acc $op !{int $n}} $more}
}
}
echo !{go !{int $first} $rest}
Footgun: for f in $(git diff --name-only base...HEAD) word-splits on
the spaces inside a path, turning "src/my file.c" into two bogus
entries. A pipeline reads each name as one line, so a path with
spaces stays a single String — no re-lexing, no lost files.
set -euo pipefail
repo="$1"
base="$2"
cd "$repo"
for f in $(git diff --name-only "$base...HEAD"); do
echo "$f"
done
let [repo, base] = $ARGS
let files = within [dir: $repo] {
stream-to-list !{git diff --name-only "$base...HEAD" | from-lines}
}
for $files { |f| echo $f }
Footgun: while read -r want file mangles real sha256sums manifests — a
binary-mode line (hash *a.txt, what sha256sum -b and shasum -b write)
keeps the * in the name, so an intact file reports FAILED, and a manifest
with no final newline loses its last entry unverified. Here the lines are a
list and the hash/name split is explicit, so neither happens.
set -euo pipefail
manifest="$1"
base="$(dirname -- "$manifest")"
total=0
bad=0
while read -r want file; do
total=$((total + 1))
if got="$(sha256sum -- "$base/$file" | cut -d' ' -f1)" && [ "$want" = "$got" ]; then
echo "OK $file"
else
echo "FAILED $file"
bad=$((bad + 1))
fi
done < "$manifest"
if [ "$bad" -gt 0 ]; then
echo "$bad of $total entries failed" >&2
exit 1
fi
echo "$total entries verified"
let [manifest] = $ARGS
let base = dirname $manifest
let entries = map { |line|
return [
want: !{re-find-match '^[0-9a-f]+' $line},
file: !{re-replace '^[0-9a-f]+[ *]+' '' $line},
]
} !{from-lines-list $manifest}
let checked = par { |[want: want, file: file]|
let got = try {
return !{words !{sha256sum -- "$base/$file" | from-string}}[0]
} { |_| return '' }
return [file: $file, ok: !{equal $want $got}]
} $entries $NPROC
for $checked { |[file: file, ok: ok]|
echo " $file"
}
let total = length $checked
let bad = length !{filter { |[ok: ok]| return $[not $ok] } $checked}
if $[$bad > 0] {
fail [status: 1, message: "$bad of $total entries failed"]
} else {
echo "$total entries verified"
}
Footgun: while … < <(find … | sort -z) discards that pipeline's status, find's
with it: one unreadable subfolder makes find exit 1, yet the sweep prints its
tally and exits 0 with the skipped subtree still dirty. Bash must spool the walk
to a mktemp file and check it before any rm, and recall -mindepth 1 and printf's
one-NUL empty list every time. Here the walk is one expression, raising first.
set -euo pipefail
root="$(cd -- "$1" && pwd -P)"
list="$(mktemp)"
trap 'rm -f -- "$list"' EXIT
find "$root" -mindepth 1 -name '.*' -prune -o -type f -empty -print0 \
| LC_ALL=C sort -z > "$list"
count=0
folders=()
while IFS= read -r -d '' f; do
printf 'removing %s\n' "$f"
rm -- "$f"
count=$((count + 1))
folders+=("$(dirname -- "$f")")
done < "$list"
touched=()
if ((count > 0)); then
while IFS= read -r -d '' d; do
touched+=("$d")
done < <(printf '%s\0' "${folders[@]}" | LC_ALL=C sort -zu)
fi
printf 'removed %d empty files in %d folders\n' "$count" "${#touched[@]}"
for d in ${touched[@]+"${touched[@]}"}; do
printf ' %s\n' "$d"
done
let [root] = $ARGS
let dir = !{resolve-path $root}
let found = filter { |p| is-file $p } !{glob "$dir/**/*"}
let files = filter { |p| equal $p !{resolve-path $p} } $found
let empties = filter { |p| file-empty $p } $files
for $empties { |p|
echo "removing $p"
rm $p
}
let folders = sort-list !{nub !{map { |p| re-replace '/[^/]+$' '' $p } $empties}}
echo "removed empty files in folders"
for $folders { |d| echo " $d" }
Footgun: awk has no median, so ordering is delegated to sort -n — and
the two tools disagree about numbers. On a column holding 1e3, awk
reads 1000 while sort -n reads 1, so the mean comes out right while
min, max and the median position are silently wrong. Here every field
is parsed once by float, and sort-list orders those very values.
set -euo pipefail
file="$1"
field="$2"
idx="$(awk -F, -v want="$field" \
'NR == 1 { for (i = 1; i <= NF; i++) if ($i == want) { print i; exit } }' "$file")"
if [ -z "$idx" ]; then
printf '%s has no field %s\n' "$file" "$field" >&2
exit 1
fi
tail -n +2 -- "$file" | cut -d, -f"$idx" | LC_ALL=C sort -n | awk '
{ a[NR] = $1; s += $1 }
END {
if (NR == 0) { print "no data rows" > "/dev/stderr"; exit 1 }
printf "mean\t%s\n", s / NR
printf "median\t%s\n", a[int((NR + 1) / 2)]
printf "min\t%s\n", a[1]
printf "max\t%s\n", a[NR]
}'
let [path, field] = $ARGS
let rows = from-csv < $path
if !{is-empty $rows} { fail [status: 1, message: "$path has no data rows"] }
if $[not !{has $rows[0] $field}] { fail [status: 1, message: "$path has no field $field"] }
let nums = map { |r| return !{float $r[$field]} } $rows
let sorted = sort-list $nums
let total = fold { |acc x| return $[$acc + $x] } 0.0 $nums
echo "mean\t"
echo "median\t"
echo "min\t$sorted[0]"
echo "max\t"
Footgun: comm -12 silently emits garbage unless BOTH inputs are
pre-sorted, and the sort that satisfies it discards the original line
order and drags in locale collation. Here membership is a set lookup,
so lines match unsorted and the first file's order is preserved.
set -euo pipefail
comm -12 <(sort "$1") <(sort "$2")
let [a, b] = $ARGS
let seen = fold { |m l| return [...$m, $l: true] } [:] !{from-lines-list $b}
for !{from-lines-list $a} { |l| if !{has $seen $l} { echo $l } }
Footgun: jq applies its filter once per input value in a stream, so the
answer's arity need not be one. -e turns zero inputs (an empty config
file) into exit 4, but nothing catches two: a file holding two JSON
objects prints two merged configs and exits 0. Here from-json decodes
a single value or raises, so both files are a hard error.
set -euo pipefail
path="$1"
defaults='{"host":"localhost","port":8080,"tls":{"enabled":false,"ca":""}}'
unknown="$(jq -rce --argjson d "$defaults" '(keys - ($d | keys)) | join(", ")' -- "$path")"
if [ -n "$unknown" ]; then
printf 'unknown config keys: %s\n' "$unknown" >&2
exit 1
fi
jq -ce --argjson d "$defaults" '$d + .' -- "$path"
let [path] = $ARGS
let default-config = to-string #'{"host": "localhost", "port": 8080, "tls": {"enabled": false, "ca": ""}}'# | from-json
let config = from-json < $path
let unknown = keys !{difference $config $default-config}
if !{is-empty $unknown} {
to-json !{union $default-config $config}
} else {
fail [status: 1, message: "unknown config keys: "]
}
Footgun: cp -u is GNU-only (macOS /bin/cp: "illegal option -- u") and
exits 0 whether it copied or skipped, so a script that reports hand-rolls
[[ $src -nt $dst ]] — and bash's file tests are total: a missing or
unreadable source answers false, printing "up to date" and exiting 0 with
the destination left stale. Here a stat that fails raises its errno.
set -euo pipefail
src="$1"
dst="$2"
if [[ "$src" -nt "$dst" ]]; then
cp -- "$src" "$dst"
echo "copied $src -> $dst ($(wc -c <"$src" | tr -d ' ') bytes)"
else
echo "up to date: $dst"
fi
let [from, dest] = $ARGS
let info = !{file-info !{resolve-path $from}}
let existing = if !{exists $dest} {
return `just !{file-info !{resolve-path $dest}}
} else {
return `none
}
let stale = case $existing [
`none: { |_| return true },
`just: { |d| return $[$info[mtime] > $d[mtime]] }
]
if $stale {
cp $from $dest
echo "copied $from -> $dest ($info[size] bytes)"
} else {
echo "up to date: $dest"
}
Footgun: find -printf '%f\n' | sort | uniq -c counts lines, so a single
filename containing a newline is tallied as two separate files under two
bogus extensions. Here each file is one glob value and group-by buckets
it by an extension derived from the whole basename, dotless files in (none).
set -euo pipefail
root="$1"
find "$root" -type f -printf '%f\n' \
| awk -F. '{ print (NF > 1 ? $NF : "(none)") }' \
| sort \
| uniq -c \
| sort -rn
let [root] = $ARGS
let files = filter { |p| is-file $p } !{glob "$root/**/*"}
let buckets = group-by { |p|
let parts = re-split '\.' !{file-info $p}[name]
if $[!{length $parts} > 1] { return !{last $parts} } else { return '(none)' }
} $files
let rows = map { |k| return [ext: $k, n: !{length $buckets[$k]}] } !{keys $buckets}
for !{reverse !{sort-list-by { |r| return $r[n] } $rows}} { |r|
echo "$r[n]\t$r[ext]"
}
Footgun: grep -c exits 1 on zero matches, so under set -e the count
has to be written n=$(grep -c -- "$pat" "$f") || n=0 — and that clause
also catches grep's exit 2, so count-matches zzz have.txt gone.txt counts
the missing file as zero, drops it from the report, and exits 0. Here the
count is length of a filtered list, and an unreadable file raises.
set -euo pipefail
pattern=$1
shift
total=0
for f in "$@"; do
n=$(grep -c -- "$pattern" "$f") || n=0
if (( n > 0 )); then
echo "$f:$n"
total=$(( total + n ))
fi
done
echo "total:$total"
let [pat, ...files] = $ARGS
let counted = filter { |c| return $[$c[n] > 0] } !{map { |f|
return [file: $f, n: !{length !{filter { |l| re-match $pat $l } !{from-lines-list $f}}}]
} $files}
for $counted { |c| echo "$c[file]:$c[n]" }
let total = sum !{map { |c| return $c[n] } $counted}
echo "total:$total"
Footgun: bash has no CSV parser, so the idiom is awk -F, — to which a
quote is just a byte. On bolt,"in stock, loose",100 the fields shift;
on a quoted "100" awk's > degrades to a string compare and "100" > 10
is false. Both silently drop every row and exit 0. Here from-csv gives
real records and float is an explicit coercion that fails on a non-number.
set -euo pipefail
if [ "$#" -ne 3 ]; then
echo "usage: ${0##*/} FILE COLUMN THRESHOLD" >&2
exit 2
fi
file=$1 column=$2 threshold=$3
awk -F, -v want="$column" -v t="$threshold" '
NR == 1 {
for (i = 1; i <= NF; i++) { col[$i] = i }
if (!(want in col)) {
print "no such column: " want > "/dev/stderr"
exit 1
}
c = col[want]
print
next
}
$c > t
' < "$file"
let [path, field, threshold] = $ARGS
let limit = !{float $threshold}
let rows = !{from-csv < $path}
to-csv !{filter { |r| return $[!{float $r[$field]} > $limit] } $rows}
Footgun: cut -f takes a set of fields, not a sequence — asking for
3,1 silently returns columns 1,3, and 1,1 returns one column instead of
two. Exit 0 both times, no diagnostic. Here the requested names are a
list mapped over each record, so the projection comes back in the order
asked for, repeats included.
set -euo pipefail
file=$1
shift
IFS=, read -r -a header < "$file"
fields=()
for want in "$@"; do
found=
for i in "${!header[@]}"; do
if [ "${header[$i]}" = "$want" ]; then found=$((i + 1)); break; fi
done
if [ -z "$found" ]; then
printf 'no such column: %s\n' "$want" >&2
exit 1
fi
fields+=("$found")
done
list=$(IFS=,; printf '%s' "${fields[*]}")
cut -d, -f"$list" -- "$file"
let [path, ...cols] = $ARGS
let rows = from-csv < $path
let unknown = flat-map { |r| filter { |c| return $[not !{has $r $c}] } $cols } !{take 1 $rows}
if $[not !{is-empty $unknown}] {
fail [status: 1, message: "no such column: "]
}
echo !{intercalate ',' $cols}
for $rows { |r| echo !{intercalate ',' !{map { |c| return $r[$c] } $cols}} }
Footgun: sort -t, -k3,3 -rn names the key by ordinal, so a column
inserted upstream sorts a different field and still exits 0; and -n
is data-blind, silently reading n/a or an empty cell as 0. Here the
key is float of a named column: a column that moved is still found,
a column that is gone and a cell that is not a number both fail loudly.
set -euo pipefail
file=$1 col=$2
{
IFS= read -r header
printf '%s\n' "$header"
sort -t, -k"$col","$col" -rn
} < "$file"
let [path, field] = $ARGS
let rows = from-csv < $path
if !{is-empty $rows} { fail [status: 1, message: "$path has no data rows"] }
if $[not !{has $rows[0] $field}] { fail [status: 1, message: "$path has no column $field"] }
to-csv !{reverse !{sort-list-by { |r| return !{float $r[$field]} } $rows}}
Footgun: the jq pipeline splits every line on a bare ,, so a
quoted field containing a comma or newline is torn apart and every
later column shifts. from-csv is a real RFC-4180 reader — quoted
commas, embedded newlines, and short rows are all handled — yielding
a record per row keyed by the header.
set -euo pipefail
jq -R -s -c '
split("\n") | map(select(length > 0)) |
(.[0] | split(",")) as $h |
.[1:] | map(split(",") | [$h, .] | transpose | map({(.[0]): .[1]}) | add)
' "$1"
let [path] = $ARGS
let rows = from-csv < $path
to-json $rows
Footgun: awk -F, is not a CSV reader: on "Doe, Jane",42,Bristol it finds
four fields under a three-column header, so the row shifts, Bristol falls
off the end and the quote bytes stay, at exit 0. Correctness costs a hand-rolled
RFC-4180 scanner, LC_ALL=C for the column sort, and a temp file so a refusal
leaves no half TSV — every time. Here from-csv reads; the check runs first.
set -euo pipefail
export LC_ALL=C
if [ "$#" -ne 1 ]; then
printf 'usage: %s FILE\n' "${0##*/}" >&2
exit 2
fi
if [ -d "$1" ] || [ ! -r "$1" ]; then
printf '%s: not a readable file\n' "$1" >&2
exit 1
fi
out=$(mktemp) || exit 1
trap 'rm -f -- "$out"' EXIT
path="$1" awk '
function die(msg) { printf "%s: %s\n", ENVIRON["path"], msg > "/dev/stderr"; bad = 1; exit 1 }
function cut(rec, f, i, c, n, cur, q, fresh) {
n = 0; cur = ""; q = 0; fresh = 1
for (i = 1; i <= length(rec); i++) {
c = substr(rec, i, 1)
if (q) {
if (c != "\"") { cur = cur c }
else if (substr(rec, i + 1, 1) == "\"") { cur = cur "\""; i++ }
else { q = 0 }
}
else if (c == "\"" && fresh) { q = 1; fresh = 0 }
else if (c == ",") { f[++n] = cur; cur = ""; fresh = 1 }
else { cur = cur c; fresh = 0 }
}
f[++n] = cur
open = q
return n
}
function untsv(s) { return s ~ /[\t\n\r]/ }
{
line = $0
sub(/\r$/, "", line)
rec = open ? rec "\n" line : line
nf = cut(rec, f)
if (open) { next }
if (rec == "") { next }
if (!seen) {
seen = 1
ncol = nf
for (i = 1; i <= ncol; i++) { name[i] = f[i]; ord[i] = i }
for (i = 2; i <= ncol; i++) {
k = ord[i]
for (j = i - 1; j >= 1 && name[ord[j]] > name[k]; j--) { ord[j + 1] = ord[j] }
ord[j + 1] = k
}
head = ""
for (i = 1; i <= ncol; i++) {
if (i > 1 && name[ord[i]] == name[ord[i - 1]]) { die("duplicate header column \"" name[ord[i]] "\"") }
if (untsv(name[ord[i]])) { die("a column name or field holds a tab, carriage return, or newline, which TSV cannot escape") }
head = head (i > 1 ? "\t" : "") name[ord[i]]
}
next
}
body = ""
for (i = 1; i <= ncol; i++) {
cell = (ord[i] <= nf) ? f[ord[i]] : ""
if (untsv(cell)) { die("a column name or field holds a tab, carriage return, or newline, which TSV cannot escape") }
body = body (i > 1 ? "\t" : "") cell
}
if (!rows++) { print head }
print body
}
END {
if (bad) { exit 1 }
if (open) { die("unterminated quoted field") }
if (!rows) { die("no data rows to convert") }
}
' < "$1" > "$out"
cat -- "$out"
if $[!{length $ARGS} != 1] {
warn 'usage: csv-to-tsv.ral FILE'
exit 2
}
let [path] = $ARGS
let rows = from-csv < $path
if !{is-empty $rows} {
fail [status: 1, message: "$path: no data rows to convert"]
}
let cols = keys $rows[0]
let cells = flat-map { |r| return !{values $r} } $rows
if !{re-match '[\t\n\r]' !{intercalate '' [...$cols, ...$cells]}} {
fail [status: 1, message: "$path: a column name or field holds a tab, carriage return, or newline, which TSV cannot escape"]
}
to-line !{intercalate "\t" $cols}
for $rows { |r| to-line !{intercalate "\t" !{values $r}} }
Footgun: the bash idiom dies under set -e twice — diff exits 1 when
the files differ, and grep -c exits 1 on a zero count — so identical
files, or a file with nothing added, abort the script. Here a count is
length of a filtered list; no exit status ever enters the reckoning.
set -euo pipefail
d=$(diff "$1" "$2")
echo "added: $(echo "$d" | grep -c '^>')"
echo "removed: $(echo "$d" | grep -c '^<')"
let [old, new] = $ARGS
let in-old = fold { |m l| return [...$m, $l: true] } [:] !{from-lines-list $old}
let in-new = fold { |m l| return [...$m, $l: true] } [:] !{from-lines-list $new}
let added = filter { |l| $[not !{has $in-old $l}] } !{from-lines-list $new}
let removed = filter { |l| $[not !{has $in-new $l}] } !{from-lines-list $old}
echo "added: "
echo "removed: "
Footgun: diff -rq A B reports in human prose — "Only in A: x",
"Files A/x and B/x differ" — so a name holding ": ", " and ", or a
newline derails the case/read parse, and "Only in A: sub" hides a
whole subtree behind one line. Here each tree is a map from relative
path to content hash; set algebra and value compare need no parsing.
set -euo pipefail
a="$1"; b="$2"
diff -rq "$a" "$b" | while read -r line; do
case "$line" in
"Only in $a"*) echo "- ${line#Only in $a: }" ;;
"Only in $b"*) echo "+ ${line#Only in $b: }" ;;
Files\ *\ differ) rel="${line#Files $a/}"; echo "~ ${rel%% and *}" ;;
esac
done
let [a, b] = $ARGS
let scan = { |root|
within [dir: $root] {
fold { |m p|
if !{is-file $p} { return [...$m, $p: !{words !{sha256sum $p | from-string}}[0]] } else { return $m }
} [:] !{glob '**/*'}
}
}
let old = scan $a
let new = scan $b
for !{keys !{difference $new $old}} { |p| echo "+ $p" }
for !{keys !{difference $old $new}} { |p| echo "- $p" }
for !{filter { |p| return $[not !{equal $old[$p] $new[$p]}] } !{keys !{intersection $old $new}}} { |p| echo "~ $p" }
Footgun: df -P | awk '$5+0 > t {print $6}' reads the mount point as a
single field, so a mount path containing a space (a removable volume, an
SMB share) is truncated at its first word. Here the capacity comes from
a fixed early column and the mount tail is rejoined intact, so a spaced
path survives whole.
set -euo pipefail
threshold="$1"
df -P | awk -v t="$threshold" 'NR > 1 && (0 + $5) > t {
print $5 "\t" $6
}'
let [pct] = $ARGS
let threshold = int $pct
let rows = stream-to-list !{stream-drop 1 !{df -P | from-lines}}
let stats = map { |line|
let cols = words $line
return [
use: !{int !{string-replace '%' '' $cols[4]}},
mount: !{intercalate ' ' !{drop 5 $cols}},
]
} $rows
for !{filter { |r| return $[$r[use] > $threshold] } $stats} { |r|
echo "$r[use]%\t$r[mount]"
}
Footgun: the obvious tr -d '\r' < "$f" > "$f.tmp" && mv "$f.tmp" "$f" exits 0 while
a 755 script comes back 644, a 640 secret comes back world-readable, a bashrc
symlink becomes a detached file with the dotfiles copy still CRLF, and an existing
notes.txt.tmp is destroyed. Bash needs mktemp+trap, cp -p, readlink -f, LC_ALL=C
and --, every time; ral's > keeps the mode and resolves the link by construction.
set -euo pipefail
export LC_ALL=C
tmp=
trap 'if [[ -n $tmp ]]; then rm -f -- "$tmp"; fi' EXIT
for arg in "$@"; do
path="$(readlink -f -- "$arg")"
tmp="$(mktemp -- "$path.XXXXXX")"
cp -p -- "$path" "$tmp"
tr -d '\r' < "$path" > "$tmp"
removed=$(( $(wc -c < "$path") - $(wc -c < "$tmp") ))
if (( removed == 0 )); then
rm -f -- "$tmp"
tmp=
continue
fi
mv -- "$tmp" "$path"
tmp=
printf '%s: %d CRs removed\n' "$arg" "$removed"
done
let scan = { |path|
let body = from-string < $path
let clean = re-replace-all '\r' '' $body
return [text: $clean, removed: $[!{length $body} - !{length $clean}]]
}
for $ARGS { |path|
let [text: text, removed: removed] = !{$scan $path}
if $[$removed > 0] {
to-string $text > $path
echo "$path: $removed CRs removed"
}
}
Footgun: the bash hand-rolls the JSON with printf, so a value
holding a ", a backslash, or a newline produces malformed output.
Here $ENV is a typed map: a selected key that is missing fails
loudly, and to-json escapes every value — quotes, newlines, and all.
set -euo pipefail
printf '{'
sep=""
for name in "$@"; do
printf '%s"%s":"%s"' "$sep" "$name" "${!name}"
sep=","
done
printf '}\n'
let selected = fold { |acc name|
return !{union $acc [$name: $ENV[$name]]}
} [:] $ARGS
to-json $selected
Footgun: grep -oE … | sort -u exits 1 when a file holds no address, so
under set -e a clean file is reported as a crash; and forgetting -E
makes + and {2,} literal, matching nothing. Here matches come back
as a list — empty is empty, not a failure — and nub dedups in order.
set -euo pipefail
file=$1
grep -oE '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' "$file" | sort -u
let [path] = $ARGS
let text = from-string < $path
let re = '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
for !{nub !{re-find-matches $re $text}} { |addr| echo $addr }
Footgun: cut cannot reorder — -f3,1 emits fields in file order, not
the order you asked — and an out-of-range field is silently blank.
Here columns are indexed explicitly, so 3 1 really means 3 then 1,
and a missing column fails loudly instead of vanishing into a blank.
set -euo pipefail
file=$1
delim=$2
shift 2
fields=$(IFS=,; echo "$*")
cut -d"$delim" -f"$fields" "$file"
let [path, delim, ...cols] = $ARGS
let idxs = map { |c| return $[!{int $c} - 1] } $cols
for !{from-lines-list $path} { |line|
let cells = re-split $delim $line
echo !{intercalate $delim !{map { |i| return $cells[$i] } $idxs}}
}
Footgun: epoch mtime has no portable spelling — GNU stat wants -c %Y, BSD
-f %m — so a careful script probes for the flag and feeds the tool's text
into (( )). Worse, done < <(find …) hides find's exit status from
set -euo pipefail: an unreadable directory prints no rows and exits 0.
Here one list-dir yields mtime as an Int, and a bad directory raises.
set -euo pipefail
dir="$1"
now="$(date +%s)"
if stat -c %Y . >/dev/null 2>&1; then
mtime_of() { stat -c %Y -- "$1"; }
else
mtime_of() { stat -f %m -- "$1"; }
fi
while IFS= read -r -d '' f; do
mtime="$(mtime_of "$f")"
printf '%d\t%s\n' "$(( (now - mtime) / 86400 ))" "$f"
done < <(find "$dir" -maxdepth 1 -type f -print0 | sort -z)
let [root] = $ARGS
let now = int !{date +%s | from-line}
let files = filter { |e| equal $e[type] 'file' } !{list-dir $root}
for $files { |[name: name, mtime: mtime]|
echo "\t$root/$name"
}
Footgun: sha256sum … | sort | awk '{print $2}' splits each line on
whitespace, so a path containing a space is truncated at its first
blank and the wrong files get grouped. Here each hash pairs with its
whole path as a value; group-by buckets on the hash, never on a
re-split line.
set -euo pipefail
root="$1"
find "$root" -type f -exec sha256sum {} + \
| sort \
| awk '{
hash = $1
path = $2
if (hash == prev) {
if (!shown) { print prevpath; shown = 1 }
print path
} else {
shown = 0
}
prev = hash
prevpath = path
}'
let [root] = $ARGS
let files = filter { |p| is-file $p } !{glob "$root/**/*"}
let hashed = par { |p|
return [hash: !{words !{sha256sum $p | from-string}}[0], path: $p]
} $files $NPROC
let buckets = group-by { |r| return $r[hash] } $hashed
let dupes = filter { |h| $[!{length $buckets[$h]} > 1] } !{keys $buckets}
for $dupes { |h|
let set = $buckets[$h]
echo "$h ( copies)"
for $set { |r| echo " $r[path]" }
}
Footgun: [ -z "$(find "$d" -type f -print -quit)" ] calls a directory empty
whenever the inner find cannot read it — a mode-000 subdirectory full of
files gets its parent printed as empty, exit status still 0, because set -e
is exempt inside a condition's command substitution. Here list-dir raises
on an unreadable directory, so it is reported as undecided, never as empty.
set -euo pipefail
root="$1"
while IFS= read -r -d '' d; do
if [ -z "$(find "$d" -type f -print -quit)" ]; then
printf '%s\n' "$d"
fi
done < <(find "$root" -type d -print0)
let [root] = $ARGS
let scan = { |dir|
let listing = try { return `ok !{list-dir $dir} } { |_| return `denied () }
case $listing [
`denied: { |_| return [verdict: `unknown, rows: [[path: $dir, verdict: `unknown]]] },
`ok: { |items|
let by-type = group-by { |e| return $e[type] } $items
let kids = map { |e| scan "$dir/$e[name]" } !{get $by-type 'dir' []}
let evidence = if !{is-empty !{get $by-type 'file' []}} {
return !{map { |k| return $k[verdict] } $kids}
} else {
return [`files]
}
let verdict = if !{contains $evidence `files} { return `files }
elsif !{contains $evidence `unknown} { return `unknown }
else { return `empty }
return [
verdict: $verdict,
rows: [[path: $dir, verdict: $verdict], ...!{flat-map { |k| return $k[rows] } $kids}],
]
}
]
}
let rows = !{scan $root}[rows]
let undecided = filter { |r| return !{equal $r[verdict] `unknown} } $rows
if $[not !{is-empty $undecided}] {
for $undecided { |r| warn "cannot read, so cannot judge: $r[path]" }
let count = length $undecided
fail [status: 1, message: "$count directories could not be read"]
}
for !{filter { |r| return !{equal $r[verdict] `empty} } $rows} { |r| echo $r[path] }
Footgun: while … done < <(find …) discards find's exit status — an
unreadable subdirectory makes find warn and exit 1, yet the script exits 0
with a plausible count, so flatten-dir src out && rm -rf src deletes files
that were never copied. Piping into the loop catches the status but scopes
the counters to a subshell. Here glob fails, and fold threads a value.
set -euo pipefail
src="$1"
dst="$2"
mkdir -p -- "$dst"
copied=0
renamed=0
while IFS= read -r -d '' p; do
name="${p##*/}"
dest="$dst/$name"
n=0
while [ -e "$dest" ]; do
n=$((n + 1))
dest="$dst/$n-$name"
done
cp -- "$p" "$dest"
copied=$((copied + 1))
if [ "$n" -gt 0 ]; then renamed=$((renamed + 1)); fi
done < <(find "$src" -type f -print0 | LC_ALL=C sort -z)
echo "flattened $copied files into $dst ($renamed renamed)"
let [root, dst] = $ARGS
mkdir -p $dst
let files = filter { |p| is-file $p } !{glob "$root/**/*"}
let tally = fold { |acc p|
let name = !{file-info $p}[name]
let n = get $acc[seen] $name 0
let dest = if $[$n == 0] { return "$dst/$name" } else { return "$dst/$(n)-$name" }
cp $p $dest
return [
seen: [...$acc[seen], $name: $[$n + 1]],
renamed: $[$acc[renamed] + !{if $[$n == 0] { return 0 } else { return 1 }}],
]
} [seen: [:], renamed: 0] $files
echo "flattened" !{length $files} "files into $dst ($tally[renamed] renamed)"
Footgun: uniq -c counts only adjacent runs and nothing checks its
input is grouped — reached through a pipeline sorted by another key,
every count silently reads 1 — and the sort that satisfies it discards
input order and hands equal counts to locale collation. Here the tally
is a map keyed by the line, and ties break by codepoint in every locale.
set -euo pipefail
sort | uniq -c | sort -rn |
awk '{ n = $1; sub(/^[[:blank:]]*[0-9]+[[:blank:]]/, ""); print n "\t" $0 }'
let counts = group-by $id !{stream-to-list !{from-lines}}
let tally = map { |l| return [line: $l, n: !{length $counts[$l]}] } !{keys $counts}
for !{reverse !{sort-list-by { |r| return $r[n] } $tally}} { |r|
echo "$r[n]\t$r[line]"
}
Footgun: git shortlog -sn with no revision reads the commit list
from stdin whenever stdout is not a terminal — so in a script or a
pipeline it silently hangs or emits nothing. Here we tally git log
ourselves: a real list of [name, count] records, no tty-dependent
behaviour and nothing to re-parse from padded columns.
set -euo pipefail
repo="$1"
git -C "$repo" shortlog -sn
let [repo] = $ARGS
let names = within [dir: $repo] {
stream-to-list !{git log --format=%an | from-lines}
}
let counts = group-by $id $names
let rows = map { |k| return [name: $k, n: !{length $counts[$k]}] } !{keys $counts}
for !{reverse !{sort-list-by { |r| return $r[n] } $rows}} { |r|
echo "$r[n]\t$r[name]"
}
Footgun: for b in $(git branch --merged main) sees the * current
marker as a word, then the shell glob-expands the bare * against the
cwd — and it lists main itself, so a delete loop can wipe the wrong
refs. --format gives bare names; we filter main out explicitly.
set -euo pipefail
repo="$1"
cd "$repo"
for b in $(git branch --merged main); do
[ "$b" = "main" ] && continue
echo "$b"
done
let [repo] = $ARGS
let merged = within [dir: $repo] {
stream-to-list !{git branch --merged main '--format=%(refname:short)' | from-lines}
}
for !{filter { |b| return $[not !{equal $b 'main'}] } $merged} { |b| echo $b }
Footgun: a pattern that starts with - (say -v or -i) is parsed by
grep as an option, silently inverting or altering the search — argument
injection, fixable only with -e "$pattern" --. Here the pattern only
ever reaches re-match as data; it can never become a flag.
set -euo pipefail
pattern=$1
n=$2
file=$3
grep -C "$n" "$pattern" "$file"
let [pat, n, path] = $ARGS
let radius = int $n
let indexed = enumerate !{from-lines-list $path}
let hits = filter { |e| re-match $pat $e[item] } $indexed
for $hits { |h|
let lo = if $[$h[index] - $radius < 0] { return 0 } else { return $[$h[index] - $radius] }
let hi = $[$h[index] + $radius + 1]
for !{take $[$hi - $lo] !{drop $lo $indexed}} { |e|
echo ":$e[item]"
}
echo '--'
}
Footgun: (( count * 40 / max )) in bash treats an empty or non-numeric
count as 0, so a malformed line draws an empty bar and slips by unnoticed.
Here int converts the count and fails loudly on anything that is not
a number, so bad data stops the chart instead of quietly distorting it.
set -euo pipefail
max=1
while read -r _ count; do
(( count > max )) && max=$count
done < "$1"
while read -r cat count; do
width=$(( count * 40 / max ))
bar=$(printf '%*s' "$width" '' | tr ' ' '#')
printf '%s\t%s %d\n' "$cat" "$bar" "$count"
done < "$1"
let [path] = $ARGS
let rows = map { |l|
let parts = words $l
return [cat: $parts[0], n: !{int $parts[1]}]
} !{from-lines-list $path}
let max = fold { |m r| if $[$r[n] > $m] { $r[n] } else { $m } } 1 $rows
for $rows { |r|
let width = $[$r[n] * 40 / $max]
let bar = fold { |acc _| "$acc#" } '' !{range 0 $width}
echo "$r[cat]\t$bar $r[n]"
}
Footgun: the obvious script pairs each URL with its status by interpolating the
URL into curl's --write-out mini-language, so ?path=C:\newest.log reports the
URL cut at C:, a stray ewest.log row, and a class exx no server served, at
exit 0. Correct bash keeps data out of -w, holds each row against set -e, and
orders per-job files by index, every time. A ral worker returns a url/code record.
set -euo pipefail
if [ "$#" -eq 0 ]; then
printf 'usage: %s URL...\n' "${0##*/}" >&2
exit 2
fi
urls=("$@")
max_jobs="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
case "$max_jobs" in '' | 0 | *[!0-9]*) max_jobs=4 ;; esac
dir="$(mktemp -d)"
trap 'rm -rf -- "$dir"' EXIT
probe() {
local out="$1" url="$2" code rc=0
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 -- "$url")" || rc=$?
if [ -z "$code" ]; then
printf 'no status from curl for %s (exit %d)\n' "$url" "$rc" >&2
code=000
fi
printf '%s\t%s\0' "$code" "$url" > "$out"
}
i=0
while [ "$i" -lt "${#urls[@]}" ]; do
probe "$dir/row.$i" "${urls[$i]}" &
i=$((i + 1))
if [ "$((i % max_jobs))" -eq 0 ]; then wait; fi
done
wait
i=0
while [ "$i" -lt "${#urls[@]}" ]; do
if [ ! -s "$dir/row.$i" ]; then
printf 'no row for %s\n' "${urls[$i]}" >&2
exit 3
fi
cat -- "$dir/row.$i" >> "$dir/report"
i=$((i + 1))
done
codes=()
dead=()
while IFS=$'\t' read -r -d '' code url; do
printf '%s %s\n' "$code" "$url"
codes+=("$code")
if [ "$code" = 000 ]; then dead+=("$url"); fi
done < "$dir/report"
printf '%s\n' "${codes[@]}" |
awk '{ n[$1 == "000" ? "unreachable" : substr($1, 1, 1) "xx"]++ }
END { for (c in n) print c ": " n[c] }' | LC_ALL=C sort
if [ "${#dead[@]}" -gt 0 ]; then
msg="${dead[0]}"
i=1
while [ "$i" -lt "${#dead[@]}" ]; do
msg="$msg, ${dead[$i]}"
i=$((i + 1))
done
printf 'unreachable: %s\n' "$msg" >&2
exit 1
fi
if !{is-empty $ARGS} {
fail [status: 2, message: 'usage: http-status URL...']
} else {
return ()
}
let probe = { |u|
let code = try {
curl -s -o /dev/null -w '%{http_code}' --max-time 15 -- $u | from-string
} { |_| return '000' }
return [url: $u, code: $code]
}
let results = par $probe $ARGS $NPROC
for $results { |r| echo "$r[code] $r[url]" }
let classes = group-by { |r|
if !{equal $r[code] '000'} { return 'unreachable' } else { return "xx" }
} $results
for !{keys $classes} { |c| echo "$c: " }
let dead = filter { |r| equal $r[code] '000' } $results
if !{is-empty $dead} { return () } else {
fail [status: 1, message: "unreachable: "]
}
Footgun: join -t, demands both inputs pre-sorted on the key and
silently drops matches when they are not, keyed by column position.
Here one table is indexed by the named key into a map, so the join is
a hash lookup — no pre-sort, no lost rows.
set -euo pipefail
left=$1 right=$2 key=$3
join -t, -1 "$key" -2 "$key" \
<(sort -t, -k"$key" "$left") \
<(sort -t, -k"$key" "$right")
let [lpath, rpath, key] = $ARGS
let parse = { |p|
let [hdr, ...rows] = !{from-lines-list $p}
let header = re-split ',' $hdr
map { |line|
fold { |m c| let k = $c[0]; return [...$m, $k: $c[1]] } [:] !{zip $header !{re-split ',' $line}}
} $rows
}
let index = fold { |m r| let k = $r[$key]; return [...$m, $k: $r] } [:] !{parse $rpath}
let joined = flat-map { |l|
let k = $l[$key]
if !{has $index $k} { return [!{union $l $index[$k]}] } else { return [] }
} !{parse $lpath}
to-csv $joined
Footgun: bash can only hand jq a program string, so --arg parameterises
the wanted value but the field name is still spliced into jq syntax: the
ordinary key a.b becomes the nested path .a.b and silently selects the
wrong elements, while user-name parses as subtraction and jq refuses to
compile. Here it indexes a record: there is no program text to splice into.
set -euo pipefail
file="$1"
field="$2"
want="$3"
jq --arg want "$want" "[.[] | select(.$field == \$want)]" -- "$file"
let [path, field, want] = $ARGS
let rows = from-json < $path
let matching = { |row|
if !{has $row $field} { return !{equal !{str $row[$field]} $want} }
else { return false }
}
to-json !{filter $matching $rows}
Footgun: the bash builds jq's program by splicing the path in —
jq ".$2" — so a path holding a quote or bracket becomes jq code,
not a lookup. Here the path is data: we split it and descend the
decoded value key by key, and a missing key fails loudly rather
than printing null.
set -euo pipefail
jq ".$2" "$1"
let [path, dotted] = $ARGS
let doc = from-json < $path
let leaf = fold { |node key| return $node[$key] } $doc !{re-split '\.' $dotted}
to-json $leaf
Footgun: jq -r 'keys[]' on a JSON array prints 0, 1, 2 — indices,
not keys — so a list-shaped file quietly reports three keys it has
none of, and a key holding a newline lands as two keys on stdout.
Here keys is typed Map → [Str]: an array or scalar fails loudly,
and each key stays one value all the way through the comparison.
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
keyfiles=()
i=0
for f in "$@"; do
jq -r 'keys[]' -- "$f" | LC_ALL=C sort > "$tmp/$i"
keyfiles+=("$tmp/$i")
i=$((i + 1))
done
LC_ALL=C sort -u -- "${keyfiles[@]}" > "$tmp/all"
i=0
for f in "$@"; do
printf '%s\n' "$f"
while IFS= read -r k; do
printf ' %s\n' "$k"
done < "$tmp/$i"
missing="$(LC_ALL=C comm -23 "$tmp/all" "$tmp/$i" | paste -sd, -)"
if [ -n "$missing" ]; then
printf ' missing: %s\n' "$missing"
fi
i=$((i + 1))
done
let docs = map { |p| return [file: $p, keys: !{keys !{from-json < $p}}] } $ARGS
let every-key = sort-list !{nub !{flat-map { |d| return $d[keys] } $docs}}
for $docs { |d|
echo $d[file]
for $d[keys] { |k| echo " $k" }
let missing = filter { |k| return $[not !{contains $d[keys] $k}] } $every-key
if $[not !{is-empty $missing}] {
let named = !{intercalate ',' $missing}
echo " missing: $named"
}
}
Footgun: jq -r '.[].field' prints the literal null for every
object that lacks the field, so a typo or a hole in the data blends
into the output as just one more line. Indexing by the field name
fails on the first object that is missing it — the mistake surfaces
instead of hiding.
set -euo pipefail
jq -r ".[].$2" "$1"
let [path, field] = $ARGS
let rows = from-json < $path
for $rows { |row| echo !{str $row[$field]} }
Footgun: jq -S . treats its input as a stream, so a file that is
accidentally two concatenated objects ({...}{...} from a botched write
or double-append) prints as two documents and passes CI unnoticed.
from-json demands exactly one JSON value — trailing content is a hard
error — and to-json re-emits the canonical compact, key-sorted form.
set -euo pipefail
jq -S . "$1"
let [path] = $ARGS
let doc = from-json < $path
to-json $doc
Footgun: when $root has no subdirectories the glob "$root"/*/
never expands (nullglob is off), so du runs on the literal string
$root/*/ and the script dies with a confusing error. Here glob
returns an empty list and the loop simply does nothing.
set -euo pipefail
root=$1; n=$2
du -sk "$root"/*/ | sort -rn | head -n "$n"
let [root, n] = $ARGS
let sized = map { |d|
let total = sum !{map { |p|
return !{file-info $p}[size]
} !{filter { |p| is-file $p } !{glob "$d/**/*"}}}
return [dir: $d, size: $total]
} !{filter { |p| is-dir $p } !{glob "$root/*"}}
let ranked = take !{int $n} !{reverse !{sort-list-by { |s| return $s[size] } $sized}}
for $ranked { |s| echo "$s[size]\t$s[dir]" }
Footgun: find … | while read -r f still splits a filename containing
a newline, and ${f##*.} on a dotless path returns whole directory
components as the "extension". Here glob yields a real list of
paths and the extension is taken from the basename alone, so nothing
is re-lexed and no directory leaks into a bucket.
set -euo pipefail
root="$1"
find "$root" -type f | while read -r f; do
ext="${f##*.}"
n="$(grep -c . "$f" || true)"
printf '%s %s\n' "$ext" "$n"
done | awk '{ s[$1] += $2 } END { for (k in s) print s[k], k }' | sort -rn
let [root] = $ARGS
let ext-of = { |p|
let name = basename $p
if !{re-match '.\.' $name} { return !{re-replace '^.*\.' '' $name} } else { return '(none)' }
}
let nonblank = { |p|
length !{filter { |l| return !{re-match '\S' $l} } !{from-lines-list $p}}
}
let files = filter { |p| return !{is-file $p} } !{glob "$root/**/*"}
let by-ext = group-by $ext-of $files
let rows = map { |k|
return [ext: $k, loc: !{sum !{map $nonblank $by-ext[$k]}}]
} !{keys $by-ext}
for !{reverse !{sort-list-by { |r| return $r[loc] } $rows}} { |r|
echo "$r[loc]\t$r[ext]"
}
Footgun: ps -Ao comm= prints paths, so grep -i Slack counts a watcher
under slack-helpers/ that pkill -i Slack never signals, pkill kills an
unnamed Slackbot, pgrep -i Slack | wc -l still reports 0 alive, exit 0:
count, kill and check are three matchers. Hardening means a pid census in
awk, killed by pid, recalled every time; ral counts and kills one typed list.
set -euo pipefail
export LC_ALL=C
if [ "$#" -eq 0 ]; then
printf 'usage: mac-memory-reap.sh APP...\n' >&2
exit 2
fi
free_pct() {
memory_pressure |
awk -F': *' '/^System-wide memory free percentage/ { gsub(/%/, "", $2); print $2 + 0 }'
}
census() { ps -Aco pid=,rss=,comm=; }
matching() {
NEEDLE="$1" awk '
{
comm = $0
sub(/^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+/, "", comm)
if (tolower(comm) == tolower(ENVIRON["NEEDLE"])) { print $1, $2 }
}
'
}
alive_of() {
PIDS="$1" awk '
BEGIN {
n = split(ENVIRON["PIDS"], want, " ")
for (i = 1; i <= n; i++) { target[want[i]] = 1 }
}
($1 in target) { n_alive += 1 }
END { print n_alive + 0 }
'
}
before="$(free_pct)"
printf 'free before: %s%%\n' "$before"
snapshot="$(census)"
apps=("$@")
pids_of=()
doomed=()
seen=""
total_kb=0
for app in "$@"; do
procs=0
kb=0
pids=""
while read -r pid rss; do
[ -n "$pid" ] || continue
pids="$pids $pid"
procs=$((procs + 1))
kb=$((kb + rss))
case " $seen " in
*" $pid "*) ;;
*)
seen="$seen $pid"
doomed[${#doomed[@]}]="$pid"
total_kb=$((total_kb + rss))
;;
esac
done <<<"$(printf '%s\n' "$snapshot" | matching "$app")"
pids_of[${#pids_of[@]}]="$pids"
printf '%s: %d procs, %d MB\n' "$app" "$procs" "$((kb / 1024))"
done
printf 'reaped %d MB total\n' "$((total_kb / 1024))"
if [ "${#doomed[@]}" -gt 0 ]; then
kill -TERM -- "${doomed[@]}" || :
fi
sleep 1
snapshot="$(census)"
i=0
while [ "$i" -lt "${#apps[@]}" ]; do
printf '%s: %s still alive\n' "${apps[$i]}" \
"$(printf '%s\n' "$snapshot" | alive_of "${pids_of[$i]}")"
i=$((i + 1))
done
printf 'free after: %s%%\n' "$(free_pct)"
if !{is-empty $ARGS} {
warn 'usage: mac-memory-reap.ral APP...'
exit 2
}
let census = {
return !{map { |l|
let [pid, rss] = take 2 !{words $l}
return [
pid: !{int $pid},
rss: !{int $rss},
comm: !{re-replace '^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+' '' $l},
]
} !{lines !{ps -Aco pid=,rss=,comm=}}}
}
let free-pct = {
let field = re-find-match 'free percentage: [0-9]+' !{memory_pressure}
return !{int !{re-find-match '[0-9]+' $field}}
}
let matching = { |snapshot app|
let needle = lower $app
return !{filter { |p| equal !{lower $p[comm]} $needle } $snapshot}
}
let before = !$free-pct
echo "free before: $before%"
let snapshot = !$census
let counted = !{map { |app|
let procs = matching $snapshot $app
return [app: $app, procs: $procs, kb: !{sum !{map { |p| $p[rss] } $procs}}]
} $ARGS}
for $counted { |c| echo "$c[app]: procs, MB" }
let doomed = !{nub !{flat-map { |c| $c[procs] } $counted}}
echo "reaped MB total"
for $doomed { |p| attempt { kill -TERM !{str $p[pid]} } }
sleep 1
let survivors = !{map { |p| $p[pid] } !$census}
for $counted { |c|
let want = !{map { |p| $p[pid] } $c[procs]}
echo "$c[app]: still alive"
}
let after = !$free-pct
echo "free after: $after%"
Footgun: ps -Aco … -m | head -n 7 plus the recommended set -euo
pipefail — head exits, ps takes SIGPIPE, pipefail surfaces 141, set
-e aborts. With 700-odd processes to list, the .sh dies before its first
printf: no output, nothing on stderr, 19 runs in 20. Here the capture is
one value and take 6 a list operation, so no pipe shuts early.
set -euo pipefail
ram_gb=$(( $(sysctl -n hw.memsize) / 1073741824 ))
read -r _ m1 m5 m15 _ <<<"$(sysctl -n vm.loadavg)"
read -r _ _ _ total _ _ used _ <<<"$(sysctl vm.swapusage)"
swap_total="${total%M}"
swap_used="${used%M}"
free_pct="$(memory_pressure | tail -n 1 | sed -E 's/^.*: ([0-9]+)%$/\1/')"
hogs="$(ps -Aco pid,pmem,rss,comm -m | head -n 7 | tail -n +2 | awk '{
name = $4
for (i = 5; i <= NF; i++) name = name " " $i
printf "hog %s %d %s\n", $2, $3 / 1024, name
}')"
printf 'ram-gb %s\n' "$ram_gb"
printf 'load %s %s %s\n' "$m1" "$m5" "$m15"
printf 'swap-mb %s used of %s\n' "$swap_used" "$swap_total"
printf 'free-pct %s\n' "$free_pct"
printf '%s\n' "$hogs"
let [m1, m5, m15] = re-find-matches '[0-9]+\.[0-9]+' !{sysctl -n vm.loadavg}
let [swap-total, swap-used, _] = re-find-matches '[0-9]+\.[0-9]+' !{sysctl vm.swapusage}
let hog = { |row|
let cols = words $row
return [
pmem: $cols[1],
rss-mb: $[!{int $cols[2]} / 1024],
name: !{intercalate ' ' !{drop 3 $cols}},
]
}
let vitals = [
ram-gb: $[!{int !{sysctl -n hw.memsize}} / 1073741824],
load: [m1: $m1, m5: $m5, m15: $m15],
swap-mb: [used: $swap-used, total: $swap-total],
free-pct: !{int !{re-find-match '[0-9]+' !{last !{lines !{memory_pressure}}}}},
hogs: !{map $hog !{take 6 !{drop 1 !{lines !{ps -Aco pid,pmem,rss,comm -m}}}}},
]
echo "ram-gb $vitals[ram-gb]"
echo "load $vitals[load][m1] $vitals[load][m5] $vitals[load][m15]"
echo "swap-mb $vitals[swap-mb][used] used of $vitals[swap-mb][total]"
echo "free-pct $vitals[free-pct]"
for $vitals[hogs] { |h| echo "hog $h[pmem] $h[rss-mb] $h[name]" }
Footgun: cp $(find src -name '*.ext') dst word-splits the substitution
on any space in a path, and if nothing matches, cp is called with just
the destination and fails obscurely. Here glob returns [] on no
match and every matched path stays one atom into cp.
set -euo pipefail
src="$1"
dst="$2"
ext="$3"
mkdir -p "$dst"
cp $(find "$src" -name "*.$ext") "$dst"
let [src, dst, ext] = $ARGS
mkdir -p $dst
for !{glob "$src/**/*.$ext"} { |p|
cp $p $dst
}
Footgun: to test a deploy offline in bash you shadow curl with a shell
function or a PATH shim — global, and forgotten between tests, so a real curl
later slips through. within [handlers: [curl: …]] scopes the mock to one
block and restores it at the brace; self-masking keeps a forwarding handler
from recursing.
set -euo pipefail
deploy() {
local host="$1" resp
resp="$(curl -s -X POST "https://$host/api/deploy")"
if [ "$(jq -r .status <<<"$resp")" = ok ]; then
echo "deployed to $host (rev $(jq -r .rev <<<"$resp"))"
else
echo "deploy to $host rejected" >&2; return 1
fi
}
# Mock: shadow curl for the test. This override is global from here on.
curl() { echo '{"status":"ok","rev":"abc123"}'; }
deploy prod.example.com
deploy staging.example.com
unset -f curl # easy to forget!
let deploy = { |host|
let resp = curl -s -X POST "https://$host/api/deploy" | from-json
if !{equal $resp[status] 'ok'} { echo "deployed to $host (rev $resp[rev])" }
else { fail [status: 1, message: "deploy to $host rejected"] }
}
within [
handlers: [
curl: { |args|
echo #'{"status": "ok", "rev": "abc123"}'#
}
]
] {
deploy 'prod.example.com'
deploy 'staging.example.com'
}
Footgun: jq --arg want 8080 'select(.[$f]==$want)' prints nothing, exits 0:
--arg binds a string, == is type-strict, and --argjson rejects admin.
Correct needs |tostring, has($f), and a slurped pre-pass — jq's status
reports only the last record, so one non-object line errors on stderr and
still exits 0. Here one has does both, and omitting it raises, not lies.
set -euo pipefail
if [ "$#" -ne 3 ]; then
printf 'usage: %s <file.jsonl> <field> <value>\n' "${0##*/}" >&2
exit 2
fi
file=$1
field=$2
want=$3
if [ ! -f "$file" ] || [ ! -r "$file" ]; then
printf '%s: %s: not a readable regular file\n' "${0##*/}" "$file" >&2
exit 1
fi
if ! jq -e -s 'all(type == "object")' -- "$file" > /dev/null; then
printf '%s: %s: not valid JSON Lines of objects\n' "${0##*/}" "$file" >&2
exit 1
fi
jq -c --arg field "$field" --arg want "$want" \
'select(has($field) and (.[$field] | tostring) == $want)' -- "$file"
let [path, field, want] = $ARGS
let matches = { |r|
if !{has $r $field} { return !{equal !{str $r[$field]} $want} }
else { return false }
}
let records = stream-to-list !{from-jsonl < $path}
to-jsonl !{filter $matches $records}
Footgun: find -printf '%T@ %p' | sort -n | tail -1 | cut recovers the
newest path positionally from one text line, so a filename containing a
newline is split across lines and comes back truncated. Here mtime is an
Int key and the file is the value that sort-list-by carries whole.
set -euo pipefail
root="$1"
find "$root" -type f -printf '%T@ %p\n' \
| sort -n \
| tail -1 \
| cut -d' ' -f2-
let [root] = $ARGS
let files = filter { |p| is-file $p } !{glob "$root/**/*"}
if !{is-empty $files} {
echo "no files under $root"
exit 0
}
echo !{last !{sort-list-by { |p| return !{file-info $p}[mtime] } $files}}
Footgun: xargs -P4 -I{} reads its input as text, not as paths:
"it's here.txt" aborts the run mid-way with xargs: unterminated quote,
and back\slash.txt reaches gzip as backslash.txt. A failing child also
lumps into one xargs exit status naming no file. par maps a block over
path values, four at a time, one result per file in input order.
set -euo pipefail
printf '%s\n' "$@" | xargs -P4 -I{} gzip -kf -- {}
echo "gzipped $# files"
let outcome = par { |f|
return [path: $f, ok: !{succeeds { gzip -kf -- $f }}]
} $ARGS 4
let by = group-by { |r|
if $r[ok] { return 'gzipped' } else { return 'failed' }
} $outcome
let gzipped = get $by 'gzipped' []
let failed = map { |r| return $r[path] } !{get $by 'failed' []}
if $[!{length $failed} > 0] {
fail [
status: 1,
message: "gzipped files; failed: ",
]
} else {
echo "gzipped files"
}
Footgun: the awk !seen[$0]++ dedup preserves empty PATH entries, and
an empty entry — a stray '::' or a leading/trailing ':' — means the
current directory to the shell, quietly putting the cwd on your PATH.
Here the split is explicit, empties are filtered out, and nub keeps
the first occurrence of each directory.
set -euo pipefail
echo "$PATH" | awk -v RS=: '!seen[$0]++ {
out = out (out ? ":" : "") $0
} END { print out }'
let parts = re-split ':' $ENV[PATH]
let clean = nub !{filter { |p| return $[not !{is-empty $p}] } $parts}
echo !{intercalate ':' $clean}
Footgun: for h in $hosts; do ping -c1 $h & done; wait throws away the
host→result mapping — you get a jumble of ping output and wait yields
only the last job's exit code. par returns one up/down record per
host, in input order, however the pings interleave.
set -euo pipefail
for h in "$@"; do
if ping -c1 -W1 "$h" >/dev/null 2>&1; then
echo "up $h"
else
echo "down $h"
fi &
done
wait
let results = par { |h|
return [host: $h, up: !{succeeds { ping -c1 -W1 $h }}]
} $ARGS $NPROC
for $results { |r|
let mark = if $r[up] { return 'up ' } else { return 'down' }
echo "$mark $r[host]"
}
Footgun: awk's cell[r,c] += $v coerces a non-numeric field to 0 — an n/a
vanishes from its total and an empty field manufactures a 0 cell, while the
script still exits 0. A non-integral total is then concatenated into the
output line through CONVFMT %.6g, so 1234567.89 prints as 1.23457e+06.
Here float refuses such a field before a single row is printed.
set -euo pipefail
file=$1 rowkey=$2 colkey=$3 valcol=$4
awk -F, -v rk="$rowkey" -v ck="$colkey" -v vc="$valcol" '
NR == 1 { for (i = 1; i <= NF; i++) idx[$i] = i; next }
{
r = $idx[rk]; c = $idx[ck]
if (!(r in rseen)) { rseen[r]; rowv[++nr] = r }
if (!(c in cseen)) { cseen[c]; colv[++nc] = c }
cell[r SUBSEP c] += $idx[vc]
}
END {
line = rk
for (j = 1; j <= nc; j++) line = line "\t" colv[j]
print line
for (i = 1; i <= nr; i++) {
line = rowv[i]
for (j = 1; j <= nc; j++) {
k = rowv[i] SUBSEP colv[j]
line = line "\t" (k in cell ? cell[k] : "")
}
print line
}
}' "$file"
let [path, rowkey, colkey, valcol] = $ARGS
let rows = from-csv < $path
let row-keys = nub !{map { |r| return $r[$rowkey] } $rows}
let col-keys = nub !{map { |r| return $r[$colkey] } $rows}
let by-row = group-by { |r| return $r[$rowkey] } $rows
let header = !{intercalate "\t" [$rowkey, ...$col-keys]}
let body = map { |rk|
let by-col = group-by { |r| return $r[$colkey] } $by-row[$rk]
let cells = map { |c|
if !{has $by-col $c} {
return !{str !{fold { |acc r| return $[$acc + !{float $r[$valcol]}] } 0.0 $by-col[$c]}}
} else {
return ''
}
} $col-keys
return !{intercalate "\t" [$rk, ...$cells]}
} $row-keys
echo !{intercalate "\n" [$header, ...$body]}
Footgun: bash polls with grep '"state": "ready"', which breaks on a
reformatted line, reordered keys, or a nested field, and can report
"not ready" forever. from-json decodes to a typed value and reads the
field structurally, so the match can't be fooled by layout.
set -euo pipefail
url=$1; field=$2; target=$3; tries=$4
for ((i=0; i<tries; i++)); do
if curl -fsS --max-time 10 "$url" | grep -q "\"$field\": *\"$target\""; then
echo "$field reached $target"
exit 0
fi
sleep 2
done
echo "gave up waiting for $field=$target" >&2
exit 1
let [url, field, target, tries] = $ARGS
let probe = { |left|
let doc = curl -fsS --max-time 10 $url | from-json
if !{equal !{str $doc[$field]} $target} {
echo "$field reached $target"
} elsif $[$left <= 0] {
warn "gave up waiting for $field=$target"
exit 1
} else {
sleep 2
probe $[$left - 1]
}
}
probe !{int $tries}
Footgun: for f in $(find ...) word-splits the results, so a stale
file named old report.log becomes the two arguments old and
report.log — and rm deletes whatever those happen to name. Here
every matched path is one String, passed to rm intact.
set -euo pipefail
dir=$1; days=$2
for f in $(find "$dir" -type f -mtime +"$days"); do
rm -- "$f"
echo "removed $f"
done
let [root, days] = $ARGS
let cutoff = $[!{int !{date +%s | from-line}} - !{int $days} * 86400]
let old = filter { |f|
let m = !{file-info $f}[mtime]
return $[$m < $cutoff]
} !{filter { |p| is-file $p } !{glob "$root/**/*"}}
for $old { |f|
rm $f
echo "removed $f"
}
Footgun: printf '%b' "${v//%/\\x}", the pure-bash urldecode idiom, feeds
the data through printf's escape interpreter: C%3A\temp decodes to
C:<TAB>emp, a%2 to a plus byte 0x02, and %00 to a NUL that $( )
then drops — all silent. Here an escape run becomes a byte list that
from-string decodes; the data never reaches a format string.
set -euo pipefail
query="$1"
urldecode() {
local s="${1//+/ }"
printf '%b' "${s//%/\\x}"
}
IFS='&' read -ra pairs <<< "$query"
for p in "${pairs[@]}"; do
IFS='=' read -r key val <<< "$p"
printf '%s=%s\n' "$(urldecode "$key")" "$(urldecode "${val-}")"
done
let [query] = $ARGS
let digits = re-find-matches '.' '0123456789abcdef'
let nybble = { |c|
let hit = first { |d| equal $d[item] !{lower $c} } !{enumerate $digits}
return !{option-or [index: 0, item: ''] $hit}[index]
}
let byte = { |pair|
fold { |acc c| return $[$acc * 16 + !{nybble $c}] } 0 !{re-find-matches '.' $pair}
}
let decode = { |s|
let part = { |tok|
if !{re-match '^%' $tok} {
if !{re-match '^(?:%[0-9A-Fa-f]{2})+$' $tok} {
let ns = map $byte !{re-find-matches '[0-9A-Fa-f]{2}' $tok}
ints-to-bytes $ns | from-string
} else { fail [status: 2, message: "malformed percent-escape in '$s'"] }
} else { return $tok }
}
let toks = re-find-matches '(?:%[0-9A-Fa-f]{2})+|[^%]+|%' !{re-replace-all '\+' ' ' $s}
intercalate '' !{map $part $toks}
}
let fields = map { |p|
let [k, ...rest] = re-split '=' $p
return [key: !{decode $k}, value: !{decode !{intercalate '=' $rest}}]
} !{re-split '&' $query}
for $fields { |f| echo "$f[key]=$f[value]" }
Footgun: a bash "fastest mirror" race backgrounds every curl and takes
wait -n, but the losing downloads keep running and burning bandwidth
until the script exits, and reaping their PIDs is manual and racy.
race returns the first handle to finish and cancels the rest for you.
set -euo pipefail
path=$1; shift
tmp=$(mktemp -d)
pids=()
for m in "$@"; do
( curl -fsSL --max-time 20 "$m/$path" > "$tmp/out" ) &
pids+=("$!")
done
wait -n
kill "${pids[@]}" 2>/dev/null || true
cat "$tmp/out"
rm -rf "$tmp"
let [path, ...mirrors] = $ARGS
let handles = map { |m| return !{spawn { curl -fsSL --max-time 20 "$m/$path" }} } $mirrors
let winner = race $handles
echo $winner[stdout]
Footgun: tr -dc set < /dev/urandom | head -c len gets the length right, but
head closing the pipe kills tr with SIGPIPE — exit 141, which set -o
pipefail reports as a failed run even though the password is fine. Here
shuf draws exactly len uniform indices into a typed charset and the
producer ends on its own, so there is no early pipe close to trip on.
set -euo pipefail
len="$1"
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$len"; echo
let [len] = $ARGS
let charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let hi = $[!{length $charset} - 1]
let picks = stream-to-list !{shuf -i "0-$hi" -n !{int $len} -r | from-lines}
echo !{intercalate '' !{map { |x| return !{slice $charset !{int $x} 1} } $picks}}
Footgun: \S is a GNU sed extension; BSD/macOS sed reads the backslash
as literal, so the pattern matches only =S and a real token=abc123
streams through unmasked with no error at all. ral's regex engine is
bundled and identical on every host: nothing leaks by dialect.
set -euo pipefail
sed -E 's/(token|password|secret)=\S+/\1=***/gI'
map-lines { |l| re-replace-all '(?i)(token|password|secret)=\S+' '${1}=***' $l }
Footgun: the loop can only end in a hard-coded exit 1. if curl …; then
exit 0; fi succeeds when curl fails — no branch ran — so the obvious
status=$? after it reads 0 after five failures, and a caller of the
wrapper cannot tell 22 (HTTP 4xx) from 28 (timeout) from 6 (DNS).
retry re-raises the last error verbatim: curl's status becomes ral's.
set -euo pipefail
url="$1"
for attempt in 1 2 3 4 5; do
if curl -fsS --max-time 10 -- "$url"; then
exit 0
fi
if ((attempt < 5)); then
sleep 2
fi
done
echo "giving up after 5 attempts" >&2
exit 1
let [url] = $ARGS
let fetch = { curl -fsS --max-time 10 -- $url }
try { !$fetch } { |_| retry 4 { sleep 2 ; !$fetch } }
Footgun: if unshare -rn curl … reads the enforcer's own failure as the
denial it meant to impose: a missing unshare, or curl's 22 on a mistyped
URL, prints "network denied" and pins the stale vendored versions, exit 0.
Bash needs a probe, rc=$? not if, only 6/7 admitted, no < <(…), every
time. A grant refuses to spawn where it cannot confine: nothing to check.
set -euo pipefail
registry="$1"
lockfile="$2"
work="$(mktemp -d)"
trap 'rm -rf -- "$work"' EXIT
if ! unshare -rn true; then
echo 'cannot deny the network: unshare -rn is unavailable here' >&2
exit 1
fi
rc=0
unshare -rn curl -fsS -o "$work/fetched.csv" -- "$registry/latest" || rc=$?
case "$rc" in
0)
echo 'resolved from the registry'
deps="$work/fetched.csv"
;;
6 | 7)
echo 'network denied — resolved from the vendored lockfile'
deps="$lockfile"
;;
*)
printf 'resolve step failed with status %s\n' "$rc" >&2
exit "$rc"
;;
esac
tail -n +2 -- "$deps" > "$work/body.csv"
LC_ALL=C sort -t, -k1,1 -- "$work/body.csv" > "$work/sorted.csv"
pinned=0
while IFS=, read -r name version; do
printf ' %s %s\n' "$name" "$version"
pinned=$(( pinned + 1 ))
done < "$work/sorted.csv"
printf '%s deps pinned\n' "$pinned"
let [registry, lockfile] = $ARGS
let no-network = [6, 7]
let resolved = grant [net: false] {
return !{try {
let remote = !{curl -fsS "$registry/latest" | from-csv}
echo 'resolved from the registry'
return $remote
} { |err|
if !{contains $no-network $err[status]} {
echo 'network denied — resolved from the vendored lockfile'
return !{from-csv < $lockfile}
} else { fail $err }
}}
}
for !{sort-list-by { |d| return $d[name] } $resolved} { |d|
echo " $d[name] $d[version]"
}
echo " deps pinned"
Footgun: bash locates its own directory by side effect — the incantation
ROOT="$(cd "$(dirname "$0")" && pwd)" cd's into $0's dir in a subshell
purely so pwd prints it absolute, guards on &&, and still never
resolves a symlinked $0. Here $SCRIPT is the running file; resolve-path
canonicalises it (absolute, symlinks followed) and dirname takes the dir.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
echo "$ROOT/self-locate.sh"
let here = dirname !{resolve-path $SCRIPT}
echo "$here/self-locate.sh"
Footgun: tr [:upper:] reads LC_CTYPE and sed's a-z range reads
LC_COLLATE, so the same titles file slugs differently per locale: under
en_US.ISO8859-1 tr folds a byte inside a UTF-8 sequence and sed's
widened range then admits it, so "Éclair Ñoño" slugs to bytes that are not
valid UTF-8. Here lower/re-replace-all fold Strings in-process.
set -euo pipefail
path="$1"
slugify() {
printf '%s\n' "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g'
}
slugs=()
while IFS= read -r title; do
[ -n "$title" ] || continue
slug="$(slugify "$title")"
printf '%s\n' "$slug"
slugs+=("$slug")
done < "$path"
if [ "${#slugs[@]}" -gt 0 ]; then
printf '%s\n' "${slugs[@]}" | sort | uniq -d | while IFS= read -r s; do
printf 'clash: %s\n' "$s"
done
fi
let [path] = $ARGS
let slug = { |title|
let dashed = re-replace-all '[^a-z0-9]+' '-' !{lower $title}
return !{re-replace-all '^-+|-+$' '' $dashed}
}
let titles = filter { |t| return $[!{length $t} > 0] } !{lines !{from-string < $path}}
let slugs = map $slug $titles
for $slugs { |s| echo $s }
let buckets = group-by $id $slugs
for !{filter { |s| return $[!{length $buckets[$s]} > 1] } !{keys $buckets}} { |s|
echo "clash: $s"
}
Footgun: split -l N -d numbers chunks with a fixed two-digit suffix. BSD
split (macOS) dies on the 101st chunk ("split: too many files", exit 65) with
part00..part99 already written, aborting half-done and leaving the debris;
GNU split silently widens, so correctness depends on which split is installed.
Here the suffix width is derived from the chunk count: no capacity to exhaust.
set -euo pipefail
shopt -s nullglob
input="$1"
n="$2"
split -l "$n" -d -- "$input" "$input.part"
for part in "$input".part*; do
echo "$part"
done
let [input, n] = $ARGS
let per-chunk = int $n
let chunks = group-by { |e| return !{str $[$e[index] / $per-chunk]} } !{enumerate !{from-lines-list $input}}
let count = length $chunks
let digits = length !{str $[$count - 1]}
let width = if $[$digits > 2] { return $digits } else { return 2 }
let zeros = intercalate '' !{map { |_| return '0' } !{range 0 $width}}
let suffix = { |i|
let s = "$zeros"
return !{slice $s $[!{length $s} - $width] $width}
}
for !{range 0 $count} { |i|
let part = "$input.part"
let body = intercalate "\n" !{map { |e| return $e[item] } $chunks[!{str $i}]}
to-string "$body\n" > $part
echo $part
}
Footgun: grep -Ev '^[[:space:]]*(#|$)' exits 1 when nothing survives, so
under set -euo pipefail an all-comments config aborts the script; the usual
|| true repair then also swallows grep's exit 2 — a missing or unreadable
file prints nothing and exits 0, so callers read the failure as "empty
config". Here an empty result is a list, and an unreadable path still fails.
set -euo pipefail
grep -Ev '^[[:space:]]*(#|$)' -- "$1" || true
let [path] = $ARGS
let kept = filter { |l| re-match '^\s*[^#\s]' $l } !{from-lines-list $path}
for $kept { |l| echo $l }
Footgun: awk -F, is not a CSV parser. A quoted field holding a comma —
"Smith, Jane" — shifts every later field left: that row prints Boston
under role, lead under amount, and drops the amount, silently, status 0.
Here from-csv decodes RFC 4180, so the quoted comma stays one field and the
columns are picked by name out of the typed records.
set -euo pipefail
file="$1"
shift
awk -F, -v want="$*" '
NR == 1 {
for (i = 1; i <= NF; i++) where[$i] = i
n = split(want, names, " ")
line = names[1]
for (j = 2; j <= n; j++) line = line "\t" names[j]
print line
next
}
{
line = $(where[names[1]])
for (j = 2; j <= n; j++) line = line "\t" $(where[names[j]])
print line
}
' < "$file" | column -t -s "$(printf '\t')"
let [path, ...cols] = $ARGS
let rows = from-csv < $path
let widest = { |ns| fold { |m n| if $[$n > $m] { return $n } else { return $m } } 0 $ns }
let spaces = { |n| intercalate '' !{map { |_| return ' ' } !{range 0 $n}} }
let widths = map { |c|
widest [!{length $c}, ...!{map { |r| return !{length $r[$c]} } $rows}]
} $cols
let render = { |cells|
let padded = map { |i|
return "$cells[$i]"
} !{range 0 !{length $cols}}
echo !{re-replace-all ' +$' '' !{intercalate ' ' $padded}}
}
render $cols
for $rows { |r| render !{map { |c| return $r[$c] } $cols} }
Footgun: filling placeholders one at a time — out=${out//"$ph"/$val} in a
loop — re-scans every value it just inserted, so NAME='${SHELL} user' pulls
the shell path into the output; and $(cat tpl) strips all trailing newlines.
Here the template is split on the placeholder regex once and rejoined with the
values verbatim: no value is re-scanned, and the bytes read are the bytes out.
set -euo pipefail
tpl="$(cat -- "$1")"
placeholders="$(grep -o -e '\${[A-Z_][A-Z0-9_]*}' -- "$1" | sort -u || true)"
out="$tpl"
while IFS= read -r ph; do
[ -n "$ph" ] || continue
name="${ph:2:${#ph}-3}"
out="${out//"$ph"/${!name-}}"
done <<<"$placeholders"
printf '%s\n' "$out"
let [path] = $ARGS
let pat = '\$\{[A-Z_][A-Z0-9_]*\}'
let tpl = from-string < $path
let values = map { |ph|
get $ENV !{slice $ph 2 $[!{length $ph} - 3]} ''
} !{re-find-matches $pat $tpl}
let [lead, ...gaps] = re-split $pat $tpl
let woven = flat-map { |[val, gap]| return [$val, $gap] } !{zip $values $gaps}
to-string !{intercalate '' [$lead, ...$woven]}
Footgun: timeout 2 ./run-tests returns 124 for the deadline, and 124 is
also what the harness returns when 124 tests fail — the careful rc=$?
comparison then reports a timeout for a suite that ran to completion, and
--preserve-status only moves the collision to 143. Racing the job against
a sleeper makes the deadline a value, `expired, that no exit code can forge.
set -euo pipefail
secs="$1"
dir="$2"
cd -- "$dir"
rc=0
timeout --kill-after=5 -- "$secs" ./run-tests || rc=$?
if ((rc == 0)); then
echo "tests passed"
elif ((rc == 124)); then
echo "tests exceeded the ${secs}s deadline" >&2
exit 1
else
echo "tests failed (exit $rc)" >&2
exit 1
fi
let [secs, target] = $ARGS
let job = watch "tests" { within [dir: $target] { ./run-tests } ; return `finished }
let alarm = spawn { sleep !{int $secs} ; return `expired }
let outcome = try { return !{race [$job, $alarm]}[value] } { |e| return `failed $e[status] }
case $outcome [
`finished: { |_| echo "tests passed" },
`expired: { |_|
cancel $job
warn "tests exceeded the $(secs)s deadline"
exit 1
},
`failed: { |st|
warn "tests failed (exit $st)"
exit 1
},
]
Footgun: grep -rn 'TODO\|FIXME' . exits 1 when nothing matches, so
under set -euo pipefail an empty scan aborts the whole script; it also
descends into .git. Here the file set is an explicit glob that skips
dotfiles, a match-free file is simply empty, and the line number is a
real Int — no exit-status surprise.
set -euo pipefail
root="$1"
grep -rn 'TODO\|FIXME' "$root"
let [root] = $ARGS
let files = filter { |p| return !{is-file $p} } !{glob "$root/**/*"}
for $files { |f|
let hits = filter { |e| return !{re-match 'TODO|FIXME' $e[item]} } !{enumerate !{from-lines-list $f}}
for $hits { |e| echo "$f:: $e[item]" }
}
Footgun: sort … | head -n N under set -euo pipefail — head leaves once
it has N lines, sort dies of SIGPIPE, and pipefail makes 141 the script's
status: right answer, failing exit, only once the input outgrows a pipe
buffer. sort -rn on a non-numeric column scores every key 0 and exits 0.
Here take bounds the list with no pipe, and float refuses that column.
set -euo pipefail
file="$1" name="$2" n="$3"
idx="$(awk -F, -v want="$name" 'NR == 1 { for (i = 1; i <= NF; i++) if ($i == want) { print i; exit } }' "$file")"
if [ -z "$idx" ]; then
echo "$file: no column named $name" >&2
exit 1
fi
head -n 1 -- "$file"
tail -n +2 -- "$file" | sort -t, -k"$idx","$idx" -rn | head -n "$n"
let [path, field, n] = $ARGS
let rows = !{from-csv < $path}
let ranked = reverse !{sort-list-by { |r| return !{float $r[$field]} } $rows}
to-csv !{take !{int $n} $ranked}
Footgun: for rel in $(cat manifest) word-splits the file on every
run of whitespace, so a path like my dir/notes.txt fractures into
two entries and the wrong skeleton appears. Here each manifest line
is one String, whitespace and all.
set -euo pipefail
root=$1; manifest=$2
for rel in $(cat "$manifest"); do
case "$rel" in
*/) mkdir -p "$root/$rel" ;;
*) mkdir -p "$(dirname "$root/$rel")"; touch "$root/$rel" ;;
esac
done
let [root, manifest] = $ARGS
for !{from-lines-list $manifest} { |rel|
let full = "$root/$rel"
if !{re-match '/$' $rel} {
mkdir -p $full
} else {
mkdir -p !{dirname $full}
touch $full
}
}
Footgun: the while read -r line idiom trims via $IFS as a side effect,
but silently drops a final line with no trailing newline — the last
record of many hand-edited files just disappears. Here from-lines-list
reads every line, and the trim is an explicit anchored regex.
set -euo pipefail
file=$1
while read -r line; do
echo "$line"
done < "$file"
let [path] = $ARGS
for !{from-lines-list $path} { |l| echo !{re-replace-all '^\s+|\s+$' '' $l} }
Footgun: comm -23 needs both inputs pre-sorted or it silently drops
real differences, and the sort it demands reorders the output and
imposes locale collation. Here it is a set difference: lines of A whose
value is not a key of B, in A's own order, no sorting required.
set -euo pipefail
comm -23 <(sort "$1") <(sort "$2")
let [a, b] = $ARGS
let seen = fold { |m l| return [...$m, $l: true] } [:] !{from-lines-list $b}
for !{from-lines-list $a} { |l| if $[not !{has $seen $l}] { echo $l } }
Footgun: until nc -z -w 1 …; do <deadline check>; sleep 1; done gives up on
time only if the probe returns. Against a host that drops the SYN (192.0.2.1)
-w bounds an idle connection, not connect — -G does that — so a 5s budget
runs 75s. Then the deadline needs re-testing before the sleep as well, and the
bare ((…)) guarding lest a false one exit under set -e. A race just holds.
set -euo pipefail
host="$1"
port="$2"
secs="$3"
deadline=$((SECONDS + secs))
while ((SECONDS < deadline)); do
if nc -z -G 1 -w 1 -- "$host" "$port" 2>/dev/null; then
echo "$host:$port is up"
exit 0
fi
((SECONDS < deadline)) || break
sleep 1
done
printf '%s:%s never came up within %ss\n' "$host" "$port" "$secs" >&2
exit 1
let [target, port, secs] = $ARGS
let probe = { |interval|
if !{succeeds { nc -z -G 1 -w 1 -- $target $port 2> /dev/null }} { return `up }
else { sleep $interval ; probe $interval }
}
let waiter = spawn { probe 1 }
let alarm = spawn { sleep !{int $secs} ; return `late }
case !{race [$waiter, $alarm]}[value] [
`up: { |_| echo "$target:$port is up" },
`late: { |_|
warn "$target:$port never came up within $(secs)s"
exit 1
},
]
Footgun: make -C a & make -C b & wait interleaves both builds' lines
raw, so one target's message lands spliced mid-line of another and you
can't tell which build spoke — or which failed. watch "label" line-
frames each job's stdout, prefixing every whole line with its dir.
set -euo pipefail
pids=()
for dir in "$@"; do
make -C "$dir" &
pids+=("$!")
done
for pid in "${pids[@]}"; do
wait "$pid"
done
let jobs = map { |d| return !{watch $d { make -C $d }} } $ARGS
for $jobs { |h| await $h }
Footgun: tr's [:alpha:] and case classes are locale-defined, so the
report depends on the ambient locale: under LC_ALL=C — a cron job, a bare
container — "naïve" splits into "na" and "ve" and "café" loses its tail,
so one file yields different words and counts than under a UTF-8 locale.
Here a word is a \p{L}+ match folded by lower, and no locale is read.
set -euo pipefail
tr -cs '[:alpha:]' '\n' < "$1" \
| tr '[:upper:]' '[:lower:]' \
| sort \
| uniq -c \
| sort -rn \
| head -n "$2"
let [path, n] = $ARGS
let counted = group-by $id !{map $lower !{re-find-matches #'\p{L}+'# !{from-string < $path}}}
let ranked = reverse !{sort-list-by { |w| return !{length $counted[$w]} } !{keys $counted}}
for !{take !{int $n} $ranked} { |w| echo "\t$w" }
Footgun: fold -w counts bytes, not characters, so any accented or CJK
text (multi-byte UTF-8) wraps several columns early and ragged. Here the
budget is spent in length, which counts Unicode scalars, and words
splits on real whitespace — so the wrap lands where you can see it.
set -euo pipefail
width=$1
file=$2
fold -s -w "$width" "$file"
let [width, path] = $ARGS
let w = int $width
for !{from-lines-list $path} { |line|
let ws = words $line
if !{is-empty $ws} { echo $line } else {
let packed = fold { |acc word|
if !{equal $acc[cur] ''} { return [out: $acc[out], cur: $word] }
elsif $[!{length $acc[cur]} + 1 + !{length $word} <= $w] {
return [out: $acc[out], cur: "$acc[cur] $word"]
} else {
return [out: [...$acc[out], $acc[cur]], cur: $word]
}
} [out: [], cur: ''] $ws
for [...$packed[out], $packed[cur]] { |l| echo $l }
}
}
No example matches that.