diff --git a/.github/instructions/bash.instructions.md b/.github/instructions/bash.instructions.md index 5a43e6b..4b17389 100644 --- a/.github/instructions/bash.instructions.md +++ b/.github/instructions/bash.instructions.md @@ -84,7 +84,10 @@ set -uo pipefail; word-splitting 3. **Double-quoted traps:** `trap "rm ${var}" EXIT` expands at definition time. Use functions or single quotes. -4. **`$@` in for-loops:** Always quote: `for arg in "$@"` +4. **`$@` in for-loops:** Use `for arg; do`, which iterates `"$@"` without + expanding it. Unquoted `$@` word-splits, and quoted `"$@"` is an unbound + variable under `set -u` on Bash 3.2 (macOS `/bin/bash`) when there are + no arguments. 5. **Regex anchoring:** `^1.1` matches `1.10.x` because `.` is a regex wildcard. Use `^1\.1\.` for exact prefix matching. diff --git a/AGENTS.md b/AGENTS.md index ca97b1a..9857260 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -300,7 +300,10 @@ These are the most frequent sources of bugs. Check for them in every change: conditions, and assignments. Unquoted variables cause word-splitting. 3. **Double-quoted traps:** `trap "rm ${var}" EXIT` expands at definition time and is vulnerable to word-splitting. Use functions or single quotes. -4. **`$@` in for-loops:** Always quote: `for arg in "$@"`. +4. **`$@` in for-loops:** Use `for arg; do`, which iterates `"$@"` without + expanding it. Unquoted `$@` word-splits, and quoted `"$@"` is an unbound + variable under `set -u` on Bash 3.2 (macOS `/bin/bash`) when there are + no arguments. 5. **Regex anchoring:** `^1.1` matches `1.10.x` because `.` is a regex wildcard. Use `^1\.1\.` for exact prefix matching. 6. **Cross-platform differences:** macOS uses BSD `sed`/`grep`/`readlink`. diff --git a/lib/tfenv-exec.sh b/lib/tfenv-exec.sh index 2b08573..f8f86ed 100644 --- a/lib/tfenv-exec.sh +++ b/lib/tfenv-exec.sh @@ -29,7 +29,10 @@ function realpath-relative-to() { export -f realpath-relative-to; function tfenv-exec() { - for _arg in "${@}"; do + # `for _arg; do` iterates "$@" without expanding it. Bash 3.2 (macOS /bin/bash) + # treats "$@" as unbound under `set -u` when there are no arguments, and + # zsh's bashcompinit runs `complete -C` commands with no arguments (#532). + for _arg; do if [[ "${_arg}" == -chdir=* ]]; then chdir="${_arg#-chdir=}"; log 'debug' "Found -chdir arg: ${chdir}"; diff --git a/test/test_exec.sh b/test/test_exec.sh new file mode 100644 index 0000000..6a16efd --- /dev/null +++ b/test/test_exec.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# Source common test setup +source "$(dirname "${0}")/test_common.sh"; + +##################### +# Begin Script Body # +##################### + +declare -a errors=(); + +log 'info' '### Test Suite: terraform shim (tfenv-exec)'; + +cleanup || log 'error' 'Cleanup failed?!'; +tfenv install 1.6.1 || early_death 'Failed to install 1.6.1 for shim tests'; +tfenv use 1.6.1 || early_death 'Failed to use 1.6.1 for shim tests'; + +# zsh's bashcompinit runs `complete -C` commands with no arguments, and Bash 3.2 +# (macOS /bin/bash) treats "$@" as unbound under `set -u` when it is empty (#532). +# Run the shim under each available bash so the system bash is exercised on macOS +# even when a newer bash is first on PATH. +for shell in bash /bin/bash; do + command -v "${shell}" >/dev/null 2>&1 || continue; + log 'info' "## terraform (no args) under ${shell}: passes through to terraform usage"; + ( + declare output; + output="$("${shell}" "${TFENV_ROOT}/bin/terraform" 2>&1 || true)"; + echo "${output}" | grep -q 'unbound variable' && exit 1; + echo "${output}" | grep -q 'Usage: terraform' || exit 1; + ) && log 'info' "## terraform (no args) under ${shell}: passed" \ + || error_and_proceed "terraform with no args under ${shell} did not reach terraform usage"; +done; + +log 'info' '## terraform -chdir with a space in the path: argument is not word-split'; +cleanup || log 'error' 'Cleanup failed?!'; +( + echo '1.6.1' > .terraform-version; + mkdir -p 'chdir-dir/with space'; + echo '1.6.0' > 'chdir-dir/with space/.terraform-version'; + declare output; + output="$(terraform '-chdir=chdir-dir/with space' version 2>&1)" || exit 1; + echo "${output}" | grep -q 'Terraform v1.6.0' || exit 1; +) && log 'info' '## terraform -chdir with a space in the path: passed' \ + || error_and_proceed 'terraform -chdir with a space in the path did not resolve the version from that directory'; + +cleanup || log 'error' 'Cleanup failed?!'; + +finish_tests 'exec'; + +exit 0;