Agent skill
shell-majo
POSIX shell scripting standards for Mark's workflow.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/shell-majo
Metadata
Additional technical details for this skill
- author
- Mark Joshwel <mark@joshwel.co>
- version
- 2026.2.2
SKILL.md
POSIX Shell Scripting Standards (Mark)
Goal: Write portable, defensive shell scripts using pure POSIX sh (not bash) unless a specific Bash feature is strictly required.
When to Use This Skill
- Writing new shell scripts (
.shfiles) - Modifying existing shell scripts
- Choosing between POSIX sh and Bash
- Adding error handling to scripts
- Creating command-line tools
- Writing automation scripts
When NOT to Use This Skill
- Script requires Bash-specific features (arrays, process substitution, extglob)
- Target environment guarantees Bash availability AND requires Bash features
- Complex data structures needed that can't be flattened to strings
- Writing Python/JS scripts instead (prefer those for complex logic)
Process
- Check script requirements - Determine if POSIX sh is sufficient or Bash is truly needed
- Set shebang - Use
#!/bin/shfor POSIX,#!/bin/bashonly if enumerated exception applies - Add license header - Include full Unlicense header (see File Header section)
- Enable error handling - Add
set -eat the top - Define exit codes - Document exit code ranges in comments
- Use proper naming - UPPER_CASE for env vars/constants, lower_case for local vars
- Quote all variables - Prevent word splitting:
"$var"not$var - Use printf not echo - For portable output
- Test with shellcheck - Run
shellcheck script.sh - Update AGENTS.md - Document any project-specific shell patterns
Constraints
- ALWAYS default to POSIX sh for all scripts
- ONLY use Bash if you can explicitly enumerate which Bash-specific feature is strictly required and impossible in POSIX sh
- Valid Bash exceptions: arrays (indexed/associative), extglob patterns, process substitution, controlled Bash environments
- "Cleaner" or "fewer lines" is NOT sufficient justification for Bash
- Scripts must be compatible with
/bin/shon any POSIX-compliant system - Use
#!/bin/sh- Never use#!/bin/bashor#!/usr/bin/env bashwithout enumerated exception
Shell scripting standards following pure POSIX sh (not bash).
Core Principle
Default to POSIX sh for all scripts unless you can explicitly enumerate which Bash-specific feature is strictly required and impossible to replicate in POSIX sh.
Treat complexity as a cost, not a feature. Choose Bash only when:
- You need arrays (indexed or associative) for data structures that cannot be reasonably flattened to strings/positional params
- You require glob matching with extglob patterns
- You need process substitution for feeding command output as a file descriptor
- You're scripting exclusively for Bash environments (NixOS activation scripts, Git hooks on controlled systems)
If the justification is merely "it's cleaner" or "fewer lines," that is not sufficient — POSIX sh is the correct choice.
Scripts must be compatible with /bin/sh on any POSIX-compliant system unless one of the above exceptions applies.
Shebang
Always use:
#!/bin/sh
Never use #!/bin/bash or #!/usr/bin/env bash.
File Header
All shell scripts must include the full Unlicense header:
#!/bin/sh
# script_name: brief description
# --------------------------------
# by mark <mark@joshwel.co>
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>
Variables
Naming
Use UPPER_CASE for environment variables and constants:
# defaults
SURPLUS_CMD_DEFAULT="surplus -td"
LOCATION_TIMEOUT=50
# environment overrides with defaults
SURPLUS_CMD="${SURPLUS_CMD:-$SURPLUS_CMD_DEFAULT}"
LOCATION_CMD="${LOCATION_CMD:-$LOCATION_CMD_DEFAULT}"
Local Variables
Use lower_case for local/script variables:
status=0
bridge_failures=0
Variable Expansion
Always quote variables to prevent word splitting:
# Good
mkdir -p "$SPOW_CACHE_DIR"
cat "$SPOW_NETLC_OUT" >>"$SPOW_SESH_OUT"
# Bad (unquoted)
mkdir -p $SPOW_CACHE_DIR
Use ${var:-default} for defaults:
LOCATION_TIMEOUT="${LOCATION_TIMEOUT:-50}"
Error Handling
Message Format
Use the same format as dev-standards-majo (see that skill for full details):
program: level: message
Examples:
printf "s+ow: error: surplus is not installed\n" >&2
printf "s+ow: warn: using fallback location\n" >&2
printf "s+ow: info: starting location fetch\n" >&2
Follow-up notes for additional context:
printf "s+ow: error: location fetch failed\n" >&2
printf "... note: timeout was %d seconds\n" "$LOCATION_TIMEOUT" >&2
Exit on Error
Use set -e at the top of scripts:
#!/bin/sh
set -e
Command Checking
Check if commands exist before using:
if ! command -v "$SURPLUS_EXE" >/dev/null 2>&1; then
printf "s+ow: error: surplus is not installed\n" >&2
exit 2
fi
Exit Codes
Use the grouping convention from dev-standards-majo:
| Range | Category |
|---|---|
0 |
Success |
1-9 |
Usage errors (bad args, missing env) |
10-19 |
Input errors |
20-29 |
File/IO errors |
255 |
Runtime error |
Example header:
# exit codes:
# 0 - success
# 1 - bad command usage
# 2 - missing dependency
# 20 - file not found
Control Structures
Conditionals
Use POSIX test syntax:
if [ "$LOCATION_PRIORITISE_NETWORK" = "n" ]; then
LOCATION_PRIORITISE_NETWORK=""
fi
# Check if variable is set
if [ -n "$SPOW_PRIVATE" ]; then
SPOW_SESH_OUT="/dev/null"
fi
# Check if file exists and is non-empty
if [ -s "$SPOW_NETLC_OUT" ]; then
printf "net"
fi
Loops
Use POSIX-compliant loops:
# While loop with counter
while [ "$LOCATION_TIMEOUT" -gt 0 ]; do
# ...
LOCATION_TIMEOUT=$((LOCATION_TIMEOUT - 1))
done
# For loop over arguments
for arg in "$@"; do
if [ "$arg" = "--search-here" ]; then
search_here=true
fi
done
Functions
Define functions without the function keyword:
locate() {
# spawn termux-location processes
(
$LOCATION_CMD -p "network" >"$SPOW_NETLC_OUT"
if [ -s "$SPOW_NETLC_OUT" ]; then
printf "net"
fi
) &
tl_net_pid="$!"
}
Process Management
Background Processes
Spawn background processes in subshells:
(
$LOCATION_CMD -p "network" >"$SPOW_NETLC_OUT"
if [ -s "$SPOW_NETLC_OUT" ]; then
printf "net" | tee -a "$SPOW_SESH_ERR"
fi
) &
tl_net_pid="$!"
Waiting for Processes
Check if process is still running:
kill -0 "$tl_net_pid" >/dev/null 2>&1
tl_net_status="$?"
if [ "$tl_net_status" -eq 1 ]; then
# Process finished
break
fi
Output
printf vs echo
Always use printf instead of echo:
# Good
printf "s+ow: error: surplus is not installed.\n"
printf "running '%s'" "$LOCATION_CMD"
# Bad (non-portable)
echo "message"
Redirection
Redirect stderr appropriately:
# Suppress output
command -v "$SURPLUS_EXE" >/dev/null 2>&1
# Redirect to file
cat "$SPOW_NETLC_OUT" >>"$SPOW_SESH_OUT"
Common Patterns
Checking File Existence
# File exists
if [ -e "$file" ]; then
...
fi
# File exists and is non-empty
if [ -s "$file" ]; then
...
fi
# Directory exists
if [ -d "$dir" ]; then
...
fi
String Operations
# Extract command name
TERMUX_EXE=$(echo "$TERMUX_CMD" | awk '{print $1}')
# Pattern matching with case
case "$arg" in
"--search-here")
search_here=true
;;
"--plumbing")
plumbing=true
;;
esac
Arithmetic
Use POSIX arithmetic expansion:
LOCATION_TIMEOUT=$((LOCATION_TIMEOUT - 1))
# Check numeric comparison
if [ "$count" -gt 0 ]; then
...
fi
Avoid Bashisms
Don't Use
# Bash arrays - not POSIX
array=("item1" "item2")
echo "${array[0]}"
# [[ ]] test - not POSIX
if [[ "$var" == "value" ]]; then
...
fi
# $() command substitution is POSIX, but prefer it over backticks
output=$(command)
# let for arithmetic - not POSIX
let "count=count+1"
# source - not POSIX (use . instead)
source file.sh
Do Use
# POSIX command substitution
output=$(command)
# POSIX test
if [ "$var" = "value" ]; then
...
fi
# POSIX arithmetic
result=$((a + b))
# POSIX source
. ./file.sh
Shellcheck
Use shellcheck for linting:
# Check script
shellcheck script.sh
# Disable specific warnings with comments
# shellcheck disable=SC2059
LOCATION_FALLBACK="${LOCATION_FALLBACK:-"%d%d%d\nSingapore?"}"
Testing Skills
- Run
shellcheck script.sh- Should produce no warnings/errors - Test on multiple shells:
dash script.sh,bash script.sh,sh script.sh - Check exit codes: Run with invalid args, missing files, verify proper codes
- Test variable quoting: Use paths with spaces to verify no word splitting
- Verify portability: Avoid
[[,source,let, arrays unless in Bash exception
Integration
This skill extends dev-standards-majo. Always ensure dev-standards-majo is loaded for:
- AGENTS.md maintenance
- Universal code principles
- Documentation policies
Works alongside:
git-majo— For committing shell script changeswriting-docs-majo— For writing shell script documentationrunning-windows-commands-majo— For shell scripting on Windows (Git Bash/WSL)task-planning-majo— For planning complex shell workflows
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?