Shell norms: pick the dialect by where the script runs (strict POSIX for portable ones, bash for a gate), quote every expansion, and keep the error path explicit. Use when editing shell scripts.
Installs into .claude/skills of the current project.
Are you the author of Refactor Shell?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/univers42-refactor-shell-claude-deal-with-the-devil)
---
name: refactor-shell
description: >
Shell norms: pick the dialect by where the script runs (strict POSIX for portable ones,
bash for a gate), quote every expansion, and keep the error path explicit. Use when
editing shell scripts.
paths:
- "**/*.sh"
- "**/*.bash"
user-invocable: false
metadata:
stage: rule
since: "1.0.0"
---
# Shell Refactoring
## Pick the dialect by where the script runs
- **Portable scripts** (an installer, anything run by `/bin/sh` on a host you do not
control): strict POSIX, the section below.
- **Gates and tools** (this kit's `tools/`, `bin/`, `tests/`, a project's CI helpers):
bash is allowed. Shebang `#!/usr/bin/env bash`, then `set -uo pipefail`. Leave `-e`
off where a failing command is data (a gate that found a problem must still report
it); a script that should stop at the first error adds it. Arrays, `[[ ]]` and
`local` are fine. A sourced library sets no options at all (`tools/README.md`).
## Strict POSIX compliance (portable scripts)
- No bashisms — no [[]], no arrays, no (( )), no ${var/pat/rep}
- Shebang: #!/bin/sh
- No unset variable access — set -u compatible
- Test with dash, not just bash
## Both dialects
- Quote every variable expansion: "$var" not $var
- Use command -v over which
- printf over echo for anything non-trivial
## Structure
- Max 25 lines per function — keep them short
- Functions at top, execution at bottom
- Local variables via local keyword or subshell isolation
- Cleanup via trap — every temp file cleaned on EXIT
## After refactoring
- `shellcheck`: zero warnings; it reads the dialect from the shebang
(`shellcheck -s sh` to force POSIX on a script without one)
- `shfmt -d -i 2`: no diff
- Runs correctly under every shell you target, not just your default
## Shell-specific ladder extensions
- Rung 2: shell builtins over external commands (`${#var}` over `wc -c`, `${var%.*}` over `basename`).
- Rung 3: an awk one-liner over a Python script for text processing.
- Rung 4: already have `jq`? Use it for JSON — don't `sed`/`grep`.
- Rung 5: pipeline over temp file. Always.
- No function wrapper around a single command.
## Shell performance guardrails
- Ladder says "builtin" but:
- shell loop over lines? A single `awk`/`sed` instead — one process beats N fork+execs.
- `$(cat file)`? Use `< file` redirection.
- `grep | awk | sed` pipeline? Usually one `awk` does all three.
- Ladder says "one-liner" but:
- command substitution in a `while` loop? Forks per iteration — process in bulk.
- Minimize subshells: `$()` forks, variable assignment doesn't.
- Minimize pipe stages: each is a fork + FD pair.
- Heredoc over `echo` piped to a command.
- `exec` for the final command in a script (no useless parent shell lingering).