AgentSkillsCN

shell-engineering

基于Google Shell风格指南的全面Shell/bash工程指南。适用于编写Shell脚本、审查bash代码或回答关于Shell脚本最佳实践的问题时。适用于.sh文件、bash脚本和任何Shell编程任务。

SKILL.md
--- frontmatter
name: shell-engineering
description: Comprehensive shell/bash engineering guidelines based on Google's Shell Style Guide. This skill should be used when writing shell scripts, reviewing bash code, or answering questions about shell scripting best practices. Applies to .sh files, bash scripts, and any shell programming tasks.

Shell Engineering

Comprehensive guidelines for writing production-quality shell scripts based on Google's Shell Style Guide.

When to Use Shell

  • Small utilities and simple wrapper scripts
  • Scripts calling other tools with straightforward logic
  • Rewrite in a structured language (Go, Python) when exceeding ~100 lines or using complex control flow

Shell Choice

  • Bash is the only permitted shell for executables
  • Start scripts with #!/bin/bash with minimal flags
  • Libraries must have .sh extension and not be executable
  • SUID/SGID are forbidden on shell scripts

File Structure

bash
#!/bin/bash
#
# Brief description of the script's purpose.

set -euo pipefail

# Constants and environment variables (UPPERCASE)
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/tmp/script.log"

# Source libraries
source "${SCRIPT_DIR}/lib/utils.sh"

# Function definitions (lowercase_with_underscores)
my_function() {
  local arg1="$1"
  # ...
}

# Main function
main() {
  # Script logic here
}

main "$@"

Formatting Rules

Indentation and Length

  • 2 spaces for indentation (no tabs)
  • 80 characters maximum line length
  • Split long pipelines with pipe at line start:
bash
command1 \
  | command2 \
  | command3

Control Structures

  • ; then and ; do on same line as if/while/for:
bash
if [[ -n "${var}" ]]; then
  # ...
fi

for file in "${files[@]}"; do
  # ...
done

Quoting

  • Always quote strings with variables, command substitutions, or spaces
  • Use "${var}" format with braces for clarity
  • Use "$@" not $* for argument lists
  • Use arrays for lists with spaces in elements

Naming Conventions

TypeConventionExample
Functionslowercase_underscoresprocess_file()
Variableslowercase_underscoresfile_count
ConstantsUPPERCASE_UNDERSCORESreadonly MAX_RETRIES=3
Environment varsUPPERCASEexport PATH
Source fileslowercase_underscores.shstring_utils.sh

Preferred Syntax

Use These

bash
# Command substitution
result=$(command)

# Test conditions
if [[ -n "${var}" ]]; then

# Arithmetic
if (( count > 10 )); then
total=$(( a + b ))

# Local variables in functions
my_func() {
  local name="$1"
}

# Arrays for lists
files=("file1.txt" "file2.txt" "file with spaces.txt")
for f in "${files[@]}"; do

Avoid These

bash
# Backticks (use $() instead)
result=`command`

# Single brackets (use [[ ]] instead)
if [ -n "$var" ]; then

# let, expr, $[ ] (use $(( )) instead)
let count=count+1

# eval (security risk)
eval "$cmd"

# Piping to while (loses variable scope)
cat file | while read line; do

# alias in scripts (use functions)
alias ll='ls -la'

# Unquoted wildcards
for f in *; do  # Use ./* instead

Error Handling

STDERR for Errors

bash
err() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}

if ! process_file "${file}"; then
  err "Failed to process ${file}"
  exit 1
fi

Check Return Values

bash
# Direct if check
if ! mv "${file}" "${dest}"; then
  err "Failed to move file"
fi

# Pipeline status
tar -cf - . | gzip > archive.tar.gz
if (( PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0 )); then
  err "Archive creation failed"
fi

Comments and Documentation

File Header (Required)

bash
#!/bin/bash
#
# Script description explaining purpose and usage.
#
# Usage: script.sh [options] <input_file>

Function Documentation

bash
#######################################
# Process a data file and output results.
# Globals:
#   OUTPUT_DIR
# Arguments:
#   $1 - Input file path
#   $2 - Output format (csv|json)
# Outputs:
#   Writes processed data to OUTPUT_DIR
# Returns:
#   0 on success, non-zero on error
#######################################
process_data() {
  local input_file="$1"
  local format="${2:-csv}"
  # ...
}

TODO Comments

bash
# TODO(username): Handle edge case for empty input

Testing and Validation

  • Use ShellCheck to identify bugs
  • Test string emptiness explicitly:
bash
# Good
if [[ -z "${var}" ]]; then  # empty
if [[ -n "${var}" ]]; then  # non-empty

# Avoid
if [[ "${var}" ]]; then

Built-in Preference

Prefer bash builtins over external commands:

bash
# Good: parameter expansion
filename="${path##*/}"
extension="${filename##*.}"
basename="${filename%.*}"

# Avoid: external commands
filename=$(basename "$path")
extension=$(echo "$filename" | sed 's/.*\.//')

Quick Reference

DoDon't
$(command)`command`
[[ condition ]][ condition ]
(( arithmetic ))let, expr
"${var}"$var
"$@"$*
local varglobal variables in functions
./* wildcards* wildcards
functionsaliases
arraysspace-separated strings