| name | google-shell-style |
| description | Use when the user asks to "refactor shell script", "fix bash style", "apply Google style guide to shell", "review shell script style", "format bash script", or when writing, reviewing, or refactoring shell/bash scripts. Provides Google Shell Style Guide rules for formatting, quoting, naming, control flow, and common anti-patterns. |
| allowed-tools | Bash(shellcheck:*), Bash(bash -n:*), Read, Write, Edit |
Google Shell Style Guide
Apply consistent, safe, and readable shell scripting practices based on Google's Shell Style Guide.
Core Principles
- Consistency - Follow existing style in files being modified
- Safety - Quote variables, use
[[ ]], avoideval - Readability - 2-space indent, 80-char lines, clear naming
Quick Reference
| Rule | Do | Avoid |
|---|---|---|
| Command substitution | $(command) |
`command` |
| Tests | [[ condition ]] |
[ condition ] or test |
| Arithmetic | (( x + y )) |
let, expr, $[ ] |
| Variables | "${var}" |
$var or ${var} unquoted |
| Functions | func_name() { |
function func_name { |
| Indentation | 2 spaces | Tabs |
Formatting Rules
Indentation
Use 2 spaces. Never tabs (exception: heredoc with <<-).
if [[ -n "${var}" ]]; then
echo "indented with 2 spaces"
fi
Line Length
Maximum 80 characters. For long strings, use heredocs or embedded newlines:
cat <<EOF
Long string content
spanning multiple lines
EOF
long_string="First line
second line"
Pipelines
One line if fits, otherwise split with pipe on new line:
command1 | command2
command1 \
| command2 \
| command3
Control Flow
Put ; then and ; do on same line as if/for/while:
if [[ -d "${dir}" ]]; then
process_dir
fi
for file in "${files[@]}"; do
process_file "${file}"
done
while read -r line; do
echo "${line}"
done < input.txt
Case Statements
Indent alternatives by 2 spaces. Simple cases on one line:
case "${option}" in
a) action_a ;;
b) action_b ;;
complex)
do_something
do_more
;;
*)
error "Unknown option"
;;
esac
Variable Handling
Quoting
Always quote strings containing variables, command substitutions, or special characters:
flag="$(some_command)"
echo "${flag}"
grep -li Hugo /dev/null "$1"
Variable Expansion
Prefer "${var}" with braces. Exception: single-character positional params ($1, $@):
echo "PATH=${PATH}, file=${filename}"
echo "Positional: $1 $2 $3"
echo "All args: $@"
Arrays
Use arrays for lists, especially command arguments:
declare -a flags
flags=(--foo --bar='baz')
flags+=(--config="${config_file}")
mybinary "${flags[@]}"
Command Substitution and Tests
Command Substitution
Always use $(command), never backticks:
var="$(command "$(nested_command)")"
Tests
Use [[ ]] for all tests. Use -z/-n for string checks, (( )) for numeric:
if [[ -z "${my_var}" ]]; then
echo "empty"
fi
if [[ "${my_var}" == "value" ]]; then
echo "match"
fi
if (( count > 10 )); then
echo "large"
fi
Arithmetic
Use (( )) or $(( )). Never let, expr, or $[ ]:
(( i += 3 ))
result=$(( x * y + z ))
if (( a < b )); then
echo "a is smaller"
fi
Naming Conventions
Functions
Lowercase with underscores. Braces on same line:
my_function() {
local result
result="$(do_work)"
echo "${result}"
}
mypackage::helper() {
# namespaced function
}
Variables
Lowercase with underscores for local variables:
local file_name
local -i count=0
Constants
Uppercase with underscores. Use readonly:
readonly CONFIG_PATH='/etc/app/config'
declare -xr EXPORTED_VAR='value'
Script Structure
Function Location
Put all functions near top, after constants. No executable code between functions.
Main Function
Scripts with multiple functions must have a main function:
#!/bin/bash
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
helper_func() {
# helper implementation
}
main() {
local arg="$1"
helper_func "${arg}"
}
main "$@"
Local Variables
Always declare function variables as local. Separate declaration from command substitution:
my_func() {
local name="$1"
local result
result="$(some_command)"
(( $? == 0 )) || return 1
echo "${result}"
}
Common Anti-Patterns
Avoid eval
eval $(set_my_variables)
Avoid Aliases in Scripts
Use functions instead:
fancy_ls() {
ls -lh "$@"
}
Avoid Pipes to While
Use process substitution to preserve variables:
while read -r line; do
last_line="${line}"
done < <(your_command)
Avoid mapfile/readarray (macOS Incompatible)
mapfile and readarray are Bash 4+ builtins not available on macOS (Bash 3.2). Use while read loop instead:
# AVOID - fails on macOS
mapfile -t files < <(find . -name "*.txt")
# USE - portable
files=()
while IFS= read -r file; do
files+=("$file")
done < <(find . -name "*.txt")
Wildcard Safety
Use explicit path prefix:
rm -v ./*
Error Handling
Check Return Values
if ! mv "${files[@]}" "${dest}/"; then
echo "Move failed" >&2
exit 1
fi
PIPESTATUS
Check pipeline component failures:
tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if (( PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0 )); then
echo "Pipeline failed" >&2
fi
Security Patterns
Safe Temporary Files
Always use mktemp with trap cleanup:
cleanup() {
local exit_code=$?
[[ -n "${TEMP_FILE:-}" ]] && rm -f "${TEMP_FILE}"
exit "${exit_code}"
}
trap cleanup EXIT
TEMP_FILE="$(mktemp)"
# use TEMP_FILE...
Input Validation
Validate user input before use:
validate_path() {
local path="$1"
# Reject empty
[[ -z "${path}" ]] && return 1
# Reject path traversal
[[ "${path}" == *..* ]] && return 1
# Require within allowed directory
[[ "${path}" != "${ALLOWED_DIR}"/* ]] && return 1
return 0
}
Command Injection Prevention
Never use eval with external input. Use arrays for dynamic commands:
# DANGEROUS - injection risk
eval "${user_command}"
# SAFE - use arrays
declare -a cmd_args
cmd_args=("--flag" "${user_input}")
mycommand "${cmd_args[@]}"
Always quote variable expansions in commands:
# DANGEROUS
rm $file_path
grep $pattern $file
# SAFE
rm -- "${file_path}"
grep -- "${pattern}" "${file}"
Use -- to prevent option injection with user-provided filenames.
Issue Severity Classification
For shell-expert agent reviews, issues are classified as:
Critical (Must Fix)
- Unquoted variables in
rm,mv, or path operations - Use of
evalwith external input - Missing error handling on destructive operations
- Command injection vulnerabilities
Important (Should Fix)
- Using
[ ]instead of[[ ]] - Missing
localin functions - Backticks instead of
$() - Missing
mainfunction in multi-function scripts
Minor (Suggestions)
- Inconsistent indentation
- Long lines (> 80 chars)
- Missing braces on simple variables
- Comment formatting
Additional Resources
For the complete Google Shell Style Guide, see: https://google.github.io/styleguide/shellguide.html