Agent Skill
2/7/2026

shell-scripting

Guidelines for portable, POSIX-compliant shell scripts. Apply when writing, reviewing, or modifying shell scripts or .sh files.

S
shellicar
0GitHub Stars
1Views
npx skills add shellicar/claude-config

SKILL.md

Nameshell-scripting
DescriptionGuidelines for portable, POSIX-compliant shell scripts. Apply when writing, reviewing, or modifying shell scripts or .sh files.

name: shell-scripting description: Guidelines for portable, POSIX-compliant shell scripts. Apply when writing, reviewing, or modifying shell scripts or .sh files. user-invocable: false

Shell Scripting Guidelines

Scope: Rules for writing portable, POSIX-compliant shell scripts — shebangs, quoting, error handling, argument parsing, and compatibility.

Write portable shell scripts that work across systems.

Quick Reference

Use This (POSIX)Not This (Bash)
#!/bin/sh#!/bin/bash
[ -z "$VAR" ][[ -z "$VAR" ]]
[ "$A" = "B" ][[ "$A" == "B" ]]
$(command)`command`
${VAR:-default}(same, this is POSIX)

Shebang

Always use POSIX Bourne shell:

#!/bin/sh

Never use #!/bin/bash unless bash-specific features are absolutely required and documented.

Error Handling

Use set -e by default — exit on first failure to prevent cascading errors:

set -e

Use set +e temporarily when you need to handle errors explicitly:

set +e
result=$(some_command)
status=$?
set -e

if [ $status -ne 0 ]; then
  echo "Handling error..."
fi

POSIX Compatibility

Test Constructs

Use single brackets [ instead of double brackets [[:

# CORRECT (POSIX)
if [ -z "$VAR" ]; then
if [ "$VAR" = "value" ]; then
if [ "$A" = "B" ] || [ "$C" = "D" ]; then

# WRONG (bash-specific)
if [[ -z "$VAR" ]]; then
if [[ "$VAR" == "value" ]]; then
if [[ "$A" == "B" || "$C" == "D" ]]; then

String Comparison

Use = not ==:

# CORRECT (POSIX)
if [ "$VAR" = "value" ]; then

# WRONG (bash-specific)
if [ "$VAR" == "value" ]; then

Arrays

POSIX shell does not support arrays. Use positional parameters or separate variables:

# CORRECT (POSIX)
set -- "item1" "item2" "item3"
for item in "$@"; do
  echo "$item"
done

# WRONG (bash-specific)
arr=("item1" "item2" "item3")
for item in "${arr[@]}"; do

Function Definitions

Use portable function syntax:

# CORRECT (POSIX)
my_function() {
  echo "hello"
}

# Also acceptable
my_function() { echo "hello"; }

# WRONG (bash-specific)
function my_function {
  echo "hello"
}

Local Variables

local is not strictly POSIX but widely supported. If maximum portability is needed, avoid it:

# Widely portable (but not strictly POSIX)
my_function() {
  local var="value"
}

# Strictly POSIX (use naming conventions)
my_function() {
  _my_function_var="value"
}

Command Substitution

Use $() instead of backticks for readability and nesting:

# PREFERRED
result=$(command)
nested=$(echo $(date))

# AVOID (hard to read and nest)
result=`command`

Here Documents

Use quoted delimiter to prevent variable expansion when needed:

# Variables expanded
cat <<EOF
Hello $USER
EOF

# Variables NOT expanded (note quotes)
cat <<'EOF'
Hello $USER prints literally
EOF

Arithmetic

Use $(( )) for arithmetic, not let or (( )):

# CORRECT (POSIX)
count=$((count + 1))
result=$((a * b))

# WRONG (bash-specific)
let count++
((count++))

Common Portable Commands

Output

# CORRECT - printf is more portable than echo
printf '%s\n' "$message"
printf '%s' "$no_newline"

# echo behavior varies - use for simple cases only
echo "simple message"

Emojis in Output

Prefer emojis over unicode characters for user feedback - they have color and more variety:

# Emojis (preferred)
echo "✅ Operation successful"
echo "❌ Operation failed"

# Unicode characters (also fine)
echo "✓ Operation successful"
echo "✗ Operation failed"

Modern terminals handle UTF-8 emojis correctly.

Reading Input

# Read a line
read -r line

# Always use -r to prevent backslash interpretation

Temporary Files

# CORRECT (POSIX)
tmp_file=$(mktemp)

# Clean up on exit
trap 'rm -f "$tmp_file"' EXIT

Finding Commands

# CORRECT (POSIX)
if command -v git >/dev/null 2>&1; then
  echo "git is installed"
fi

# WRONG (bash-specific)
if type -t git >/dev/null; then
if hash git 2>/dev/null; then

Required Parameters

When scripts interact with external systems (APIs, different tenants, orgs), make identifying parameters required, not optional:

# CORRECT - org is required
if [ -z "$ORG" ]; then
  echo "Error: --org is required" >&2
  exit 1
fi

# WRONG - defaulting could hit wrong system
ORG="${ORG:-default}"

This prevents accidentally operating on the wrong system.

Argument Parsing

Use a while loop with case statement:

while [ $# -gt 0 ]; do
  case $1 in
    --org)
      ORG="$2"
      shift 2
      ;;
    --id)
      ID="$2"
      shift 2
      ;;
    --flag)
      FLAG=1
      shift
      ;;
    *)
      echo "Unknown option: $1" >&2
      exit 1
      ;;
  esac
done

File Tests

All standard file tests are POSIX:

[ -f "$file" ]   # regular file exists
[ -d "$dir" ]    # directory exists
[ -e "$path" ]   # path exists
[ -r "$file" ]   # readable
[ -w "$file" ]   # writable
[ -x "$file" ]   # executable
[ -s "$file" ]   # file exists and not empty
[ -z "$var" ]    # string is empty
[ -n "$var" ]    # string is not empty

Quoting

Always quote variables to handle spaces and special characters:

# CORRECT
echo "$variable"
[ -f "$file" ]
command "$arg1" "$arg2"

# WRONG - breaks on spaces
echo $variable
[ -f $file ]
command $arg1 $arg2

Validation

Check scripts with ShellCheck in POSIX mode:

# If installed locally
shellcheck -s sh script.sh

# Via Docker
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable -s sh /mnt/script.sh

Security

Use -- to separate options from arguments (prevents injection):

rm -- "$file"
grep -- "$pattern" "$file"

Script Template

#!/bin/sh
# Description of what this script does
#
# Usage:
#   script.sh --org <ORG> --id <ID> [--flag]

set -e

# Parse arguments
ORG=""
ID=""
FLAG=0

while [ $# -gt 0 ]; do
  case $1 in
    --org)
      ORG="$2"
      shift 2
      ;;
    --id)
      ID="$2"
      shift 2
      ;;
    --flag)
      FLAG=1
      shift
      ;;
    -h|--help)
      echo "Usage: script.sh --org <ORG> --id <ID> [--flag]"
      exit 0
      ;;
    *)
      echo "Unknown option: $1" >&2
      exit 1
      ;;
  esac
done

# Validate required parameters
if [ -z "$ORG" ]; then
  echo "Error: --org is required" >&2
  exit 1
fi

if [ -z "$ID" ]; then
  echo "Error: --id is required" >&2
  exit 1
fi

# Main logic here
Skills Info
Original Name:shell-scriptingAuthor:shellicar