Skip to content

Latest commit

 

History

History
534 lines (358 loc) · 14 KB

File metadata and controls

534 lines (358 loc) · 14 KB

Bash/ZSH Scripting Conventions

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.

Shell Selection

When to use Bash vs ZSH

  • Bash: Preferred for scripts that need high portability across environments
    • Strikes a good balance between portability and developer experience1
    • Use the shebang #!/usr/bin/env bash for better portability1
  • ZSH: Best for interactive shells and scripts leveraging ZSH's advanced features
    • Offers powerful tab completion, clever history, and pattern matching2
    • Use the shebang #!/usr/bin/env zsh
    • Reset options at beginning with emulate -LR zsh to ensure predictable behavior3

File Structure and Organization

Directory Structure

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)

File Naming Conventions

  • Use .sh extension for Bash scripts and .zsh for ZSH scripts4
  • Use descriptive names with lowercase and underscores (e.g., process_data.sh)4
  • Avoid spaces in filenames

Module Organization

  • Split code into logical modules (e.g., network_utils.sh, string_manipulation.sh)4
  • Use source or . to include modules in scripts
  • Prefer source in ZSH and . in Bash for maximum portability

Script Structure

Standard Template

#!/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 "$@"

ZSH Specific Template Additions

For ZSH scripts, include after the shebang:

#!/usr/bin/env zsh

# Reset ZSH options to ensure predictable behavior
emulate -LR zsh

Coding Style and Formatting

General Guidelines

  • 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

Variable Naming and Usage

  • 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

Control Structures

# 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}"

Functions

# 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
}

Command Substitution and Expansion

  • 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}"

Error Handling and Debugging

Safety Options

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

Defensive Programming

  • Validate input parameters at the beginning
  • Use [[ ]] for conditions instead of [ ] or test1
  • 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

Error Messages

  • Direct error messages to stderr:
echo "Error: Invalid input parameter" >&2
  • Use descriptive error messages that suggest resolution

Debugging

  • 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.sh or zsh -n script.zsh6

Security Best Practices

  • 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 755 for scripts
  • Avoid storing sensitive data like passwords in scripts
  • Never use eval unless absolutely necessary

Cross-Platform Compatibility

Linux, macOS, and WSL Compatibility

  • 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#./}"
}

AI-Assisted Coding Best Practices

Effective Prompting

  • 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

Task Selection

  • 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

Implementing Guardrails

  • Apply Test-Driven Development with AI-generated code10:
  1. Ask AI to write tests first
  2. Ask AI to implement features that pass tests
  3. Iteratively run tests to verify functionality
  • Ensure all AI-generated code passes ShellCheck validation10
  • Implement type checking where possible using comments and validation

Code Review

  • Thoroughly review all AI-generated code10
  • Understand all code before committing
  • Verify edge cases and error handling
  • Check for potential security vulnerabilities

Testing and Validation

Test-Driven Development

  • 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

Example Test Structure

#!/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 ]
}

Differences Between Bash and ZSH

Key Behavioral Differences

  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)
  1. Array Indexing: ZSH arrays are 1-indexed by default, Bash arrays are 0-indexed
  2. Globbing Behavior: ZSH has more powerful globbing features
# ZSH only:
ls *.*(.)    # List only regular files
ls **/*(.)   # Recursively list regular files
  1. For ZSH Compatibility Mode:
# Make ZSH behave more like Bash when needed
emulate -LR bash

Conclusion

These 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.

References

Footnotes

  1. https://sharats.me/posts/shell-script-best-practices/ 2 3 4 5 6 7

  2. https://www.sitepoint.com/zsh-tips-tricks/

  3. https://scriptingosx.com/2019/08/moving-to-zsh-part-8-scripting-zsh/ 2

  4. https://www.projectrules.ai/rules/zsh 2 3

  5. https://styles.goatbytes.io/lang/shell/ 2 3 4 5

  6. https://github.com/SixArm/unix-shell-script-tactics 2 3 4 5 6

  7. https://google.github.io/styleguide/shellguide.html

  8. https://sap1ens.com/blog/2017/07/01/bash-scripting-best-practices/

  9. https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api 2

  10. https://engineering.axur.com/2025/05/09/best-practices-for-ai-assisted-coding.html 2 3 4 5