bash-safety

star 1.5k

Enforce safe bash scripting practices when writing, reviewing, or fixing shell scripts. Covers quoting, arrays, conditionals, arithmetic, redirections, strict mode, and static analysis. Use when editing .sh/.bash files, reviewing shell scripts, fixing shellcheck warnings, or writing new bash code.

tenstorrent By tenstorrent schedule Updated 3/19/2026

name: bash-safety description: >- Enforce safe bash scripting practices when writing, reviewing, or fixing shell scripts. Covers quoting, arrays, conditionals, arithmetic, redirections, strict mode, and static analysis. Use when editing .sh/.bash files, reviewing shell scripts, fixing shellcheck warnings, or writing new bash code.

Bash Safety & Best Practices

When writing or reviewing bash scripts, apply the rules below. For the complete catalog of rules with detailed examples, see reference.md.

Sources: BashPitfalls, Shellharden.

Rule 0: Run ShellCheck

Before any manual review, run static analysis:

shellcheck -o all script.sh

-o all enables every optional check (including those not on by default). Fix all errors and warnings before proceeding. ShellCheck catches the majority of the pitfalls listed here automatically.

Rule Categories (quick reference)

# Category Key Principle
1 Quoting Always quote expansions
2 Arrays Use real arrays, not strings
3 Conditionals [ is a command; prefer [[ ]]
4 Loops Use globs / while read, not for in $(…)
5 Arithmetic Use (( )) for math
6 Command Substitution Prefer "$(…)" over backticks
7 Redirections & Pipes Order matters; pipes create subshells
8 Filenames & Paths Prefix with ./, use --
9 Output Use printf, not echo
10 Script Structure Hashbang, strict mode, nullglob
11 Dangerous Patterns Avoid injection, validate input

1. Quoting

The single most important rule. An unquoted variable undergoes word splitting and pathname expansion (globbing). Always quote "$var" and "$(cmd)".

# BAD
cp $file $target
echo $msg

# GOOD
cp -- "$file" "$target"
printf '%s\n' "$msg"

Exceptions (quoting optional but never harmful): $?, $$, $!, $#, ${#array[@]}, right side of assignments (a=$b), inside [[ ]], and inside case … in.

2. Arrays

Use real arrays when you need a list. Never use whitespace-delimited strings.

# BAD
files="a b c"
for f in $files; do …; done

# GOOD
files=(a b c)
for f in "${files[@]}"; do …; done

Always iterate positional parameters with "$@", never $* or $@.

3. Conditionals & Tests

[ is a command (alias for test). Spaces around every argument are mandatory. [[ ]] is a bash keyword with safer parsing.

# BAD – missing spaces, wrong operator
[bar="$foo"]
[ bar == "$foo" ]
[ "$foo" = bar && "$bar" = foo ]

# GOOD
[ "$bar" = "$foo" ]
[[ $foo = "$bar" && $bar = "$baz" ]]

Unquoted RHS in [[ ]] is treated as a glob pattern. Quote it for literal comparison: [[ $foo = "$bar" ]].

4. Loops & Iteration

Never parse ls or unquoted command substitutions in for.

# BAD
for f in $(ls *.mp3); do …; done

# GOOD – use globs
for f in ./*.mp3; do
    [ -e "$f" ] || continue
    …
done

# GOOD – iterate command output via process substitution
while IFS= read -r line; do
    …
done < <(some_command)

5. Arithmetic

Use (( )) for integer math. Never use [[ $x > 7 ]] (string comparison).

# BAD
[[ $foo > 7 ]]
[ $foo > 7 ]        # creates a file named "7"

# GOOD
(( foo > 7 ))
[ "$foo" -gt 7 ]    # POSIX alternative

Validate user-supplied values before using them in arithmetic contexts to prevent code injection.

6. Command Substitution

Prefer "$(cmd)" over backticks. Always quote the result.

# BAD
dir=`dirname $f`
cd $(dirname "$f")

# GOOD
dir="$(dirname -- "$f")"
cd -P -- "$(dirname -- "$f")"

local var=$(cmd) masks the exit status of cmd. Separate declaration from assignment:

local var
var=$(cmd)
rc=$?

7. Redirections & Pipes

Redirections are evaluated left to right. 2>&1 must come after the stdout redirect:

# BAD – stderr still goes to terminal
somecmd 2>&1 >>logfile

# GOOD
somecmd >>logfile 2>&1

Each command in a pipeline runs in a subshell; variable changes are lost after the loop:

# BAD – count stays 0
grep foo bar | while read -r; do ((count++)); done

# GOOD – process substitution keeps loop in current shell
while read -r; do
    ((count++))
done < <(grep foo bar)

Never read from and write to the same file in one pipeline:

# BAD – truncates file
sed 's/foo/bar/' file > file

# GOOD
sed -i 's/foo/bar/' file      # GNU sed
sed 's/foo/bar/' file > tmp && mv tmp file   # portable

8. Filenames & Paths

Filenames can contain spaces, dashes, newlines, and glob characters.

  • Prefix relative paths with ./ to prevent dash-as-option: cp "./$f" dest/
  • Use -- to end option parsing: rm -- "$file"
  • Use -print0 / read -d '' with find:
while IFS= read -r -d '' file; do
    …
done < <(find . -type f -name '*.mp3' -print0)

9. Output

echo cannot safely print arbitrary data (leading -n, -e interpreted as options; no -- support in GNU echo).

# BAD – breaks if $var starts with -n, -e, etc.
echo "$var"

# GOOD
printf '%s\n' "$var"

Never use the variable as the format string:

# BAD – format string injection
printf "$var"

# GOOD
printf '%s' "$var"

10. Script Structure

Hashbang

#!/usr/bin/env bash

Don't put options (-euo pipefail) in the hashbang; they can be overridden.

Strict mode

set -euo pipefail

Caveats:

  • set -u treats empty arrays as unset in bash < 4.4. Use a feature check or omit -u for older bash.
  • set -e is ignored inside functions called as conditions (f && …), and inside command substitutions. Always add explicit error checks.
  • pipefail can cause false failures when earlier pipeline stages exit due to SIGPIPE (e.g. cmd | head).

Safer globbing

shopt -s nullglob globstar

nullglob prevents unmatched globs from being passed as literal strings. globstar enables ** recursive globbing.

Dependency assertion

require() { hash "$@" || exit 127; }
require curl jq

11. Dangerous Patterns

Pattern Risk Fix
eval "$var" Code injection Avoid eval; use arrays
$(( array[$x] )) Injection via index Validate $x first
find -exec sh -c 'echo {}' Code injection Use sh -c '…' _ {} with "$1"
export CDPATH=… Breaks cd in child scripts Don't export CDPATH
echo "Hello!" (interactive) History expansion Use printf or set +H

Remediation Workflow

When fixing an existing script:

  1. Run shellcheck -o all script.sh and fix all findings
  2. Quote every unquoted variable and command substitution
  3. Replace echo "$var" with printf '%s\n' "$var"
  4. Replace for x in $(cmd) with while read loops
  5. Replace string-based lists with arrays
  6. Add set -euo pipefail (with appropriate caveats)
  7. Add shopt -s nullglob if globs are used
  8. Add -- after commands that accept options before variable args
  9. Prefix relative paths with ./ where needed
  10. Re-run shellcheck -o all and verify clean

For the complete rule reference with 40+ detailed rules and examples, see reference.md.

Install via CLI
npx skills add https://github.com/tenstorrent/tt-metal --skill bash-safety
Repository Details
star Stars 1,524
call_split Forks 502
navigation Branch main
article Path SKILL.md
More from Creator