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.
Applies Google Shell Style Guide rules to shell scripts for formatting, quoting, naming, and safety. Triggers when you ask to refactor, fix style, or review bash scripts.
/plugin marketplace add pproenca/dot-claude-old/plugin install shell@dot-claudeThis skill is limited to using the following tools:
Apply consistent, safe, and readable shell scripting practices based on Google's Shell Style Guide.
[[ ]], avoid eval| 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 |
Use 2 spaces. Never tabs (exception: heredoc with <<-).
if [[ -n "${var}" ]]; then
echo "indented with 2 spaces"
fi
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"
One line if fits, otherwise split with pipe on new line:
command1 | command2
command1 \
| command2 \
| command3
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
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
Always quote strings containing variables, command substitutions, or special characters:
flag="$(some_command)"
echo "${flag}"
grep -li Hugo /dev/null "$1"
Prefer "${var}" with braces. Exception: single-character positional params ($1, $@):
echo "PATH=${PATH}, file=${filename}"
echo "Positional: $1 $2 $3"
echo "All args: $@"
Use arrays for lists, especially command arguments:
declare -a flags
flags=(--foo --bar='baz')
flags+=(--config="${config_file}")
mybinary "${flags[@]}"
Always use $(command), never backticks:
var="$(command "$(nested_command)")"
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
Use (( )) or $(( )). Never let, expr, or $[ ]:
(( i += 3 ))
result=$(( x * y + z ))
if (( a < b )); then
echo "a is smaller"
fi
Lowercase with underscores. Braces on same line:
my_function() {
local result
result="$(do_work)"
echo "${result}"
}
mypackage::helper() {
# namespaced function
}
Lowercase with underscores for local variables:
local file_name
local -i count=0
Uppercase with underscores. Use readonly:
readonly CONFIG_PATH='/etc/app/config'
declare -xr EXPORTED_VAR='value'
Put all functions near top, after constants. No executable code between functions.
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 "$@"
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}"
}
eval $(set_my_variables)
Use functions instead:
fancy_ls() {
ls -lh "$@"
}
Use process substitution to preserve variables:
while read -r line; do
last_line="${line}"
done < <(your_command)
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")
Use explicit path prefix:
rm -v ./*
if ! mv "${files[@]}" "${dest}/"; then
echo "Move failed" >&2
exit 1
fi
Check pipeline component failures:
tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if (( PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0 )); then
echo "Pipeline failed" >&2
fi
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...
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
}
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.
For shell-expert agent reviews, issues are classified as:
rm, mv, or path operationseval with external input[ ] instead of [[ ]]local in functions$()main function in multi-function scriptsFor the complete Google Shell Style Guide, see: https://google.github.io/styleguide/shellguide.html