Safe Workflows
Patterns for operations that need human confirmation before acting. Three layers of increasing robustness: glob loop, find -exec, and null-delimited xargs.
Safe Workflows
Patterns for operations that need human confirmation before acting. The principle: show what you found, prove it’s correct, then ask permission.
Interactive Delete with Validation
for f in tobe*.adoc; do
echo "Found: $f ($(wc -c < "$f") bytes)"
head -1 "$f"
read "reply?Delete $f? [y/N] "
[[ "$reply" == "y" ]] && rm "$f" && echo "Deleted." || echo "Kept."
done
# Output (real session):
# Found: tobeorganized-siem.adoc (21246 bytes)
# Now I have the full picture. The correct standard for your system is:
# Delete tobeorganized-siem.adoc? [y/N] y
# Deleted.
# Found: tobe-reviewed-agnostic-sys-docs-exploration.adoc (2757 bytes)
# = The Agnostic Engineer's Discovery Protocol
# Delete tobe-reviewed-agnostic-sys-docs-exploration.adoc? [y/N] y
# Deleted.
What each line does:
-
for f in tobe*.adoc— shell expands the glob, iterates each match -
wc -c < "$f"— byte count (input redirection avoids filename in output) -
head -1 "$f"— show first line so you can identify the file’s content -
read "reply?Delete $f? [y/N] "— zsh prompt syntax (?separates variable from prompt text) -
[[ "$reply" == "y" ]]— only exactyproceeds; anything else (Enter, n, typo) keeps the file -
&& rm "$f" && echo "Deleted."— short-circuit: rm only runs if reply is y, echo only if rm succeeds -
|| echo "Kept."— fires if any part of the && chain fails (including reply != y)
find . -maxdepth 1 -name 'tobe*.adoc' -type f -exec sh -c '
for f; do
echo "--- $f ---"
head -1 "$f"
wc -l < "$f"
printf "Delete? [y/N] "
read reply
[ "$reply" = "y" ] && rm "$f" && echo "Deleted." || echo "Kept."
done
' _ {} +
# find handles filenames with spaces, special characters
# sh -c '...' _ {} + passes all found files as arguments to the script
# _ is a placeholder for $0 (the script name)
find . -maxdepth 1 -name 'tobe*.adoc' -print0 | xargs -0 -n1 sh -c '
printf "%s (%s lines)\nFirst line: %s\nDelete? [y/N] " "$1" "$(wc -l < "$1")" "$(head -1 "$1")"
read reply
[ "$reply" = "y" ] && rm "$1" && echo "Deleted." || echo "Kept."
' _
# -print0 + xargs -0 = null-delimited = safe for ANY filename
# -n1 = one file per invocation so read works correctly
When to Use Which
| Layer | Use when | Limitation |
|---|---|---|
Glob loop |
Quick one-off at the CLI, filenames are predictable |
Glob only matches current directory pattern |
find -exec |
Need path traversal, type filters, or this goes in a script |
Slightly more verbose |
find + xargs -0 |
Filenames may contain spaces, quotes, or newlines |
Most verbose but bulletproof |
The Validation Pattern
Every safe workflow follows the same structure:
1. FIND — identify targets (glob, find, grep -rl)
2. SHOW — display enough context to confirm identity (head, wc, file)
3. ASK — prompt for confirmation (read, -p, -i)
4. ACT — execute only on explicit yes (&&)
5. REPORT — confirm what happened (echo, diff)
This is STD-008 (verify-before/change/verify-after) applied to interactive workflows.
Capture to File Before Executing
The progression: type at CLI → capture to /tmp/ → inspect → execute → promote.
# 1. WRITE — heredoc to temp file
# 'EOF' (single-quoted) prevents $variable expansion at write time
tee /tmp/safe-delete.sh << 'EOF'
#!/usr/bin/env bash
for f in tobe*.adoc; do
echo "Found: $f ($(wc -c < "$f") bytes)"
head -1 "$f"
read -p "Delete $f? [y/N] " reply
[[ "$reply" == "y" ]] && rm "$f" && echo "Deleted." || echo "Kept."
done
EOF
# 2. INSPECT — read it back, verify before executing
cat /tmp/safe-delete.sh
# 3. EXECUTE — only after visual confirmation
bash /tmp/safe-delete.sh
# 4. PROMOTE — if it worked, save permanently
# cp /tmp/safe-delete.sh ~/bin/safe-delete.sh && chmod +x ~/bin/safe-delete.sh
# WRONG — unquoted EOF expands variables at WRITE time
tee /tmp/broken.sh << EOF
echo "User is $USER" # writes: echo "User is evan" (baked in)
EOF
# CORRECT — single-quoted 'EOF' preserves variables for RUNTIME
tee /tmp/correct.sh << 'EOF'
echo "User is $USER" # writes literally: echo "User is $USER"
EOF
make 2>&1 | tee /tmp/build-output.log | grep -E 'WARN|ERROR'
# tee splits the stream: full output to file, filtered output to terminal
# Review later: cat /tmp/build-output.log
tee /tmp/xref-fix.sh << 'EOF'
#!/usr/bin/env bash
# Fix broken xrefs after directory rename
grep -rlP 'xref:codex/cli/' --include='*.adoc' docs/modules/ROOT/ | \
xargs sed -i \
-e 's|xref:codex/cli/grep\.adoc|xref:codex/grep/index.adoc|g' \
-e 's|xref:codex/cli/sed\.adoc|xref:codex/sed/index.adoc|g'
# Verify
grep -rnP 'xref:codex/cli/' --include='*.adoc' docs/modules/ROOT/
EOF
cat /tmp/xref-fix.sh # inspect
bash /tmp/xref-fix.sh # execute after review
Common Gotchas
# WRONG — colon is not a statement terminator
for f in *.adoc: do # zsh: no matches found: *.adoc:
# CORRECT — semicolon separates the list from do
for f in *.adoc; do
# WRONG — quotes prevent glob expansion
ls "*.adoc" # looks for literal file named "*.adoc"
# CORRECT — unquoted glob expands
ls *.adoc # shell expands to matching files
# WRONG — ^ and | are regex operators, not glob
ls ^tobe|*.adoc # | is a pipe, sends ls output to command "*.adoc"
# CORRECT — glob syntax for filtering
ls tobe*.adoc # glob: tobe followed by anything, ending in .adoc
# If you need regex-level filtering on filenames:
ls *.adoc | grep '^tobe'
find . -maxdepth 1 -regex './tobe.*\.adoc'
# zsh — prompt after ? inside the variable name
read "reply?Continue? [y/N] "
# bash — use -p flag for prompt
read -p "Continue? [y/N] " reply