Use when writing shell scripts following modern best practices. Covers portable scripting, Bash patterns, error handling, and secure coding.
View on GitHubTheBushidoCollective/han
jutsu-shfmt
January 24, 2026
Select agents to install to:
npx add-skill https://github.com/TheBushidoCollective/han/blob/main/jutsu/jutsu-shfmt/skills/shell-best-practices/SKILL.md -a claude-code --skill shell-best-practicesInstallation paths:
.claude/skills/shell-best-practices/# Shell Scripting Best Practices
Comprehensive guide to writing robust, maintainable, and secure shell scripts following modern best practices.
## Script Foundation
### Shebang Selection
Choose the appropriate shebang for your needs:
```bash
# Portable bash (recommended)
#!/usr/bin/env bash
# Direct bash path (faster, less portable)
#!/bin/bash
# POSIX-compliant shell (most portable)
#!/bin/sh
# Specific shell version
#!/usr/bin/env bash
# Requires Bash 4.0+
```
### Strict Mode
Always enable strict error handling:
```bash
#!/usr/bin/env bash
set -euo pipefail
# What these do:
# -e: Exit immediately on command failure
# -u: Treat unset variables as errors
# -o pipefail: Pipeline fails if any command fails
```
For debugging, add:
```bash
set -x # Print commands as they execute
```
### Script Header Template
```bash
#!/usr/bin/env bash
set -euo pipefail
# Script: script-name.sh
# Description: Brief description of what this script does
# Usage: ./script-name.sh [options] <arguments>
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
```
## Variable Handling
### Always Quote Variables
Prevents word splitting and glob expansion:
```bash
# Good
echo "$variable"
cp "$source" "$destination"
if [ -f "$file" ]; then
# Bad - can break on spaces/special chars
echo $variable
cp $source $destination
if [ -f $file ]; then
```
### Use Meaningful Names
```bash
# Good
readonly config_file="/etc/app/config.yml"
local user_input="$1"
declare -a log_files=()
# Bad
readonly f="/etc/app/config.yml"
local x="$1"
declare -a arr=()
```
### Default Values
```bash
# Use default if unset
name="${NAME:-default_value}"
# Use default if unset or empty
name="${NAME:-}"
# Assign default if unset
: "${NAME:=default_value}"
# Error if unset (with message)
: "${REQUIRED_VAR:?Error: REQUIRED_VAR must be set}"
```
### Readonly and Local
```bash
# Constants
readonly MAX_RETRIES=3
readonly CONFIG_DIR="/