This document outlines conventions and best practices for Bash/ZSH scripting in our projects. Following these guidelines ensures consistency, readability, maintainability, and security across shell scripts while maximizing the effectiveness of AI-assisted coding. Scripts following these conventions will work reliably across Linux, macOS, and Windows Subsystem for Linux (WSL) environments.
- Bash: Preferred for scripts that need high portability across environments
- ZSH: Best for interactive shells and scripts leveraging ZSH's advanced features
project/
├── bin/ # Executable scripts for general use
├── lib/ # Library functions and modules
├── conf/ # Configuration files
├── test/ # Test scripts
├── modules/ # Dynamically loaded modules
├── data/ # Data files used by scripts
└── tmp/ # Temporary files (ensure proper cleanup)
- Use
.shextension for Bash scripts and.zshfor ZSH scripts4 - Use descriptive names with lowercase and underscores (e.g.,
process_data.sh)4 - Avoid spaces in filenames
- Split code into logical modules (e.g.,
network_utils.sh,string_manipulation.sh)4 - Use
sourceor.to include modules in scripts - Prefer
sourcein ZSH and.in Bash for maximum portability
#!/usr/bin/env bash
# or #!/usr/bin/env zsh
# Set strict mode
set -o errexit # Exit on error
set -o pipefail # Exit if any command in a pipeline fails
set -o nounset # Error on unset variables
# Debug mode (enabled with TRACE=1)
if [[ "${TRACE-0}" == "1" ]]; then
set -o xtrace
fi
# Script description, usage, and version
VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [options] <arguments>
Description:
Brief description of what the script does.
Options:
-h, --help Show this help message and exit
-v, --version Show version information and exit
EOF
}
# Process command-line arguments
if [[ $# -eq 0 ]]; then
usage
exit 0
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-v|--version)
echo "$SCRIPT_NAME v$VERSION"
exit 0
;;
# Add more options here
*)
# Handle positional arguments
break
;;
esac
shift
done
# Main function
main() {
# Script logic here
echo "Script running..."
}
# Call main function
main "$@"For ZSH scripts, include after the shebang:
#!/usr/bin/env zsh
# Reset ZSH options to ensure predictable behavior
emulate -LR zsh- Indent with 2 spaces (not tabs)5
- Aim for 80 characters per line, with hard limit of 1005
- Use whitespace to separate commands from arguments5
- Add blank lines to separate logical sections
- Use lowercase for local variables with underscores to separate words6
- Use UPPERCASE for environment variables and constants6
- Always quote variable expansions:
"${variable}"51 - For variables that might be unset, use
${variable:-default}1
# If statement - put "then" on same line as "if"
if [[ -d "${dir}" ]]; then
echo "Directory exists"
else
mkdir -p "${dir}"
fi
# For loop - put "do" on same line as "for"
for file in "${files[@]}"; do
process_file "${file}"
done
# While loop
while read -r line; do
echo "$line"
done < "${input_file}"# Define with parentheses and space before brace
function process_file() {
local file="$1" # Use local variables in functions
if [[ -f "${file}" ]]; then
# Process file
return 0
else
echo "Error: File not found" >&2
return 1
fi
}- Use
$(command)for command substitution, not backticks6 - Always quote command substitution:
"$(command)" - Prefer parameter expansion over external tools7:
# Instead of:
addition="$(expr "${X}" + "${Y}")"
substitution="$(echo "${string}" | sed -e 's/^foo/bar/')"
# Prefer:
addition="$(( X + Y ))"
substitution="${string/#foo/bar}"Always include these safety options at the beginning of scripts81:
set -o errexit # Exit on error
set -o pipefail # Exit if any command in a pipeline fails
set -o nounset # Error on unset variables- Validate input parameters at the beginning
- Use
[[ ]]for conditions instead of[ ]ortest1 - Always check for command existence before use:
if ! command -v required_command >/dev/null 2>&1; then
echo "Error: required_command is not installed" >&2
exit 1
fi- Direct error messages to stderr:
echo "Error: Invalid input parameter" >&2- Use descriptive error messages that suggest resolution
- Enable debug mode with environment variable:
if [[ "${TRACE-0}" == "1" ]]; then
set -o xtrace # Print commands before execution
fi- Run with:
TRACE=1 ./script.sh - Use ShellCheck to find and fix warnings6
- Check syntax errors:
bash -n script.shorzsh -n script.zsh6
- Quote all variable expansions to prevent word splitting and globbing51
- Create temporary files securely using
mktemp6:
temp_file=$(mktemp)
trap 'rm -f "${temp_file}"' EXIT- Validate and sanitize all external inputs
- Use restricted permissions:
chmod 755for scripts - Avoid storing sensitive data like passwords in scripts
- Never use
evalunless absolutely necessary
- Use POSIX-compliant features when possible
- Be aware of GNU vs BSD command differences (especially on macOS)
- For platform-specific code, use detection:
case "$(uname)" in
Linux*)
# Linux-specific commands
;;
Darwin*)
# macOS-specific commands
;;
MINGW*|CYGWIN*|MSYS*)
# Windows-specific commands
;;
*)
echo "Unsupported platform: $(uname)" >&2
exit 1
;;
esac- Use portable commands or provide alternatives:
# Instead of GNU-specific:
# readlink -f "$file"
# Use this portable function:
realpath() {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}- Be specific, descriptive, and detailed in prompts to AI9
- Include the target shell (Bash or ZSH) in your prompt
- Specify environment constraints (Linux, macOS, WSL)
- Provide examples of desired coding style9
- Choose appropriate tasks for AI assistance10:
- Well-defined, self-contained functions
- Standard utility functions
- Command-line argument parsing
- Maintain human oversight for:
- Security-critical sections
- Complex architectural decisions
- Platform-specific optimizations
- Apply Test-Driven Development with AI-generated code10:
- Ask AI to write tests first
- Ask AI to implement features that pass tests
- Iteratively run tests to verify functionality
- Ensure all AI-generated code passes ShellCheck validation10
- Implement type checking where possible using comments and validation
- Thoroughly review all AI-generated code10
- Understand all code before committing
- Verify edge cases and error handling
- Check for potential security vulnerabilities
- Write tests before implementing features10
- Use a testing framework like Bats (Bash Automated Testing System)
- Keep tests in a separate
test/directory - Test both normal operation and edge cases
#!/usr/bin/env bats
setup() {
# Setup test environment
source "../lib/functions.sh"
}
teardown() {
# Clean up after tests
}
@test "function_name handles valid input" {
run function_name "valid_input"
[ "$status" -eq 0 ]
[ "$output" = "expected output" ]
}
@test "function_name handles invalid input" {
run function_name "invalid_input"
[ "$status" -eq 1 ]
}- Variable Substitution: In ZSH, unquoted variables are NOT split by default3
# In Bash:
words="one two three"
echo $words # Outputs: one two three (split into arguments)
# In ZSH:
words="one two three"
echo $words # Outputs: one two three (as a single argument)- Array Indexing: ZSH arrays are 1-indexed by default, Bash arrays are 0-indexed
- Globbing Behavior: ZSH has more powerful globbing features
# ZSH only:
ls *.*(.) # List only regular files
ls **/*(.) # Recursively list regular files- For ZSH Compatibility Mode:
# Make ZSH behave more like Bash when needed
emulate -LR bashThese conventions provide a foundation for creating maintainable, secure, and cross-platform shell scripts. When using AI assistance for shell scripting, always ensure the AI follows these conventions and review all generated code thoroughly. Remember that while AI can help with implementation details, human oversight remains essential for ensuring that scripts meet security, performance, and reliability requirements.
Footnotes
-
https://sharats.me/posts/shell-script-best-practices/ ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
https://scriptingosx.com/2019/08/moving-to-zsh-part-8-scripting-zsh/ ↩ ↩2
-
https://github.com/SixArm/unix-shell-script-tactics ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
https://sap1ens.com/blog/2017/07/01/bash-scripting-best-practices/ ↩
-
https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api ↩ ↩2
-
https://engineering.axur.com/2025/05/09/best-practices-for-ai-assisted-coding.html ↩ ↩2 ↩3 ↩4 ↩5