@gspothq/cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -2
- package/configurations/database/postgres/manifest.toml +114 -0
- package/configurations/database/postgres/rules/POSTGRES.md +78 -0
- package/configurations/framework/astro/eslint-presets.json +138 -0
- package/configurations/framework/astro/manifest.toml +95 -0
- package/configurations/framework/astro/rules/ASTRO.md +39 -0
- package/configurations/framework/expo/manifest.toml +43 -0
- package/configurations/framework/express/manifest.toml +17 -0
- package/configurations/framework/express/rules/EXPRESS.md +73 -0
- package/configurations/framework/express/semgrep/express.yml +71 -0
- package/configurations/framework/fastapi/manifest.toml +11 -0
- package/configurations/framework/fastapi/rules/FASTAPI.md +100 -0
- package/configurations/framework/fastapi/semgrep/fastapi.yml +44 -0
- package/configurations/framework/nestjs/eslint-presets.json +62 -0
- package/configurations/framework/nestjs/manifest.toml +52 -0
- package/configurations/framework/nestjs/rules/NESTJS.md +44 -0
- package/configurations/framework/nextjs/eslint-presets.json +73 -0
- package/configurations/framework/nextjs/manifest.toml +119 -0
- package/configurations/framework/nextjs/rules/NEXTJS.md +43 -0
- package/configurations/framework/react/eslint-presets.json +77 -0
- package/configurations/framework/react/manifest.toml +93 -0
- package/configurations/framework/react/rules/REACT.md +108 -0
- package/configurations/framework/react-dom/eslint-presets.json +110 -0
- package/configurations/framework/react-dom/manifest.toml +24 -0
- package/configurations/framework/react-native/manifest.toml +46 -0
- package/configurations/framework/react-native/rules/REACT-NATIVE.md +51 -0
- package/configurations/framework/site/manifest.toml +170 -0
- package/configurations/framework/site/rules/SITE.md +20 -0
- package/configurations/framework/svelte/eslint-presets.json +67 -0
- package/configurations/framework/svelte/manifest.toml +83 -0
- package/configurations/framework/svelte/rules/SVELTE.md +48 -0
- package/configurations/framework/vue/eslint-presets.json +180 -0
- package/configurations/framework/vue/manifest.toml +83 -0
- package/configurations/framework/vue/rules/VUE.md +41 -0
- package/configurations/general/commits/manifest.toml +111 -0
- package/configurations/general/commits/rules/GIT.md +31 -0
- package/configurations/general/coverage/manifest.toml +3 -0
- package/configurations/general/dependencies/manifest.toml +159 -0
- package/configurations/general/dependencies/rules/DEPENDENCIES.md +30 -0
- package/configurations/general/dependencies/syncpack.json +15 -0
- package/configurations/general/docs/manifest.toml +79 -0
- package/configurations/general/docs/rules/DOCS.md +86 -0
- package/configurations/general/duplication/manifest.toml +32 -0
- package/configurations/general/engineering/manifest.toml +8 -0
- package/configurations/general/engineering/rules/agent/PLANNING.md +42 -0
- package/configurations/general/engineering/rules/agent/SUPPRESSIONS.md +16 -0
- package/configurations/general/engineering/rules/agent/TALKING.md +3 -0
- package/configurations/general/engineering/rules/agent/TASKS.md +19 -0
- package/configurations/general/engineering/rules/agent/WORKING.md +83 -0
- package/configurations/general/engineering/rules/code/ACCESSIBILITY.md +32 -0
- package/configurations/general/engineering/rules/code/CLI.md +37 -0
- package/configurations/general/engineering/rules/code/COMMENTS.md +38 -0
- package/configurations/general/engineering/rules/code/CONFIGURATION.md +25 -0
- package/configurations/general/engineering/rules/code/ERRORS.md +49 -0
- package/configurations/general/engineering/rules/code/HTTP.md +46 -0
- package/configurations/general/engineering/rules/code/LOGGING.md +55 -0
- package/configurations/general/engineering/rules/code/TESTING.md +58 -0
- package/configurations/general/files/manifest.toml +141 -0
- package/configurations/general/files/rules/YAML.md +19 -0
- package/configurations/general/format/manifest.toml +152 -0
- package/configurations/general/gspot/manifest.toml +65 -0
- package/configurations/general/gspot/rules/GENERATED.md +10 -0
- package/configurations/general/licenses/manifest.toml +33 -0
- package/configurations/general/naming/defaults.json +423 -0
- package/configurations/general/naming/manifest.toml +35 -0
- package/configurations/general/naming/rules/NAMING.md +179 -0
- package/configurations/general/prose/manifest.toml +86 -0
- package/configurations/general/prose/rules/WRITING.md +31 -0
- package/configurations/general/prose/vale/styles/LICENSES.md +55 -0
- package/configurations/general/prose/vale/styles/gspot/ampm.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/conscious-language.yml +16 -0
- package/configurations/general/prose/vale/styles/gspot/contractions.yml +43 -0
- package/configurations/general/prose/vale/styles/gspot/currency.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/dashes.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/date-ordinals.yml +41 -0
- package/configurations/general/prose/vale/styles/gspot/dates.yml +7 -0
- package/configurations/general/prose/vale/styles/gspot/file-paths.yml +7 -0
- package/configurations/general/prose/vale/styles/gspot/future.yml +17 -0
- package/configurations/general/prose/vale/styles/gspot/hedging.yml +22 -0
- package/configurations/general/prose/vale/styles/gspot/history.yml +16 -0
- package/configurations/general/prose/vale/styles/gspot/idioms.yml +21 -0
- package/configurations/general/prose/vale/styles/gspot/interface-verbs.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/jargon.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/leading-conjunctions.yml +12 -0
- package/configurations/general/prose/vale/styles/gspot/link-text.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/locations.yml +14 -0
- package/configurations/general/prose/vale/styles/gspot/marketing.yml +26 -0
- package/configurations/general/prose/vale/styles/gspot/merge-conflict-markers.yml +13 -0
- package/configurations/general/prose/vale/styles/gspot/militaristic-language.yml +22 -0
- package/configurations/general/prose/vale/styles/gspot/modals.yml +9 -0
- package/configurations/general/prose/vale/styles/gspot/numbered-units.yml +18 -0
- package/configurations/general/prose/vale/styles/gspot/obvious-fields.yml +14 -0
- package/configurations/general/prose/vale/styles/gspot/ordinal-adverbs.yml +15 -0
- package/configurations/general/prose/vale/styles/gspot/percentage-numbers.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/placeholders.yml +7 -0
- package/configurations/general/prose/vale/styles/gspot/product-centric-writing.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/punctuated-headings.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/self-reference.yml +15 -0
- package/configurations/general/prose/vale/styles/gspot/session-identifiers.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/since.yml +6 -0
- package/configurations/general/prose/vale/styles/gspot/straight-quotes.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/suspended-hyphens.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/symbols.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/time-ranges.yml +14 -0
- package/configurations/general/prose/vale/styles/gspot/url-prepositions.yml +10 -0
- package/configurations/general/prose/vale/styles/gspot/version-range.yml +8 -0
- package/configurations/general/repository/manifest.toml +24 -0
- package/configurations/general/secrets/gitleaks.toml +2 -0
- package/configurations/general/secrets/manifest.toml +125 -0
- package/configurations/general/secrets/rules/SECRETS.md +13 -0
- package/configurations/general/security/manifest.toml +77 -0
- package/configurations/general/security/rules/SECURITY.md +55 -0
- package/configurations/general/spelling/manifest.toml +46 -0
- package/configurations/general/structure/manifest.toml +70 -0
- package/configurations/infrastructure/ansible/manifest.toml +33 -0
- package/configurations/infrastructure/docker/manifest.toml +127 -0
- package/configurations/infrastructure/docker/rules/DOCKER.md +135 -0
- package/configurations/infrastructure/docker/trivyignore +2 -0
- package/configurations/infrastructure/github-actions/manifest.toml +76 -0
- package/configurations/infrastructure/github-actions/rules/GITHUB-ACTIONS.md +43 -0
- package/configurations/infrastructure/github-actions/zizmor.yml +3 -0
- package/configurations/infrastructure/nginx/manifest.toml +51 -0
- package/configurations/infrastructure/nginx/rules/NGINX.md +38 -0
- package/configurations/infrastructure/openapi/manifest.toml +50 -0
- package/configurations/infrastructure/openapi/rules/OPENAPI.md +39 -0
- package/configurations/infrastructure/xcode/manifest.toml +131 -0
- package/configurations/infrastructure/xcode/rules/XCODE.md +34 -0
- package/configurations/language/bash/manifest.toml +261 -0
- package/configurations/language/bash/rules/BASH.md +106 -0
- package/configurations/language/bash/rules/LANGUAGE.md +213 -0
- package/configurations/language/bash/rules/NAMING.md +62 -0
- package/configurations/language/bash/rules/OPERATIONS.md +77 -0
- package/configurations/language/bash/rules/SAFETY.md +222 -0
- package/configurations/language/bash/semgrep/bash.yml +30 -0
- package/configurations/language/css/manifest.toml +98 -0
- package/configurations/language/css/rules/CSS.md +28 -0
- package/configurations/language/css/rules/NAMING.md +17 -0
- package/configurations/language/css/rules/TAILWIND.md +26 -0
- package/configurations/language/css/stylelint-rule-names.json +89 -0
- package/configurations/language/html/manifest.toml +59 -0
- package/configurations/language/html/rules/HTML.md +27 -0
- package/configurations/language/html/rules/NAMING.md +16 -0
- package/configurations/language/javascript/eslint-presets.json +997 -0
- package/configurations/language/javascript/eslint-rule-names.json +299 -0
- package/configurations/language/javascript/eslint-runtime-names.json +61 -0
- package/configurations/language/javascript/manifest.toml +393 -0
- package/configurations/language/javascript/rules/BUN.md +13 -0
- package/configurations/language/javascript/rules/COMPONENTS.md +18 -0
- package/configurations/language/javascript/rules/DENO.md +15 -0
- package/configurations/language/javascript/rules/JAVASCRIPT.md +60 -0
- package/configurations/language/javascript/rules/NAMING.md +31 -0
- package/configurations/language/javascript/rules/NODE.md +21 -0
- package/configurations/language/javascript/rules/PLAYWRIGHT.md +34 -0
- package/configurations/language/javascript/semgrep/javascript.yml +84 -0
- package/configurations/language/markdown/manifest.toml +85 -0
- package/configurations/language/markdown/markdownlint-cli2.mjs +19 -0
- package/configurations/language/python/manifest.toml +621 -0
- package/configurations/language/python/rules/DESIGN.md +136 -0
- package/configurations/language/python/rules/FLOW.md +124 -0
- package/configurations/language/python/rules/NAMING.md +53 -0
- package/configurations/language/python/rules/PACKAGING.md +49 -0
- package/configurations/language/python/rules/PYTHON.md +136 -0
- package/configurations/language/python/rules/TYPING.md +132 -0
- package/configurations/language/sql/manifest.toml +68 -0
- package/configurations/language/sql/rules/NAMING.md +90 -0
- package/configurations/language/sql/rules/SQL.md +9 -0
- package/configurations/language/swift/manifest.toml +164 -0
- package/configurations/language/swift/rules/NAMING.md +88 -0
- package/configurations/language/swift/rules/SWIFT.md +134 -0
- package/configurations/language/swift/rules/SWIFTUI.md +61 -0
- package/configurations/language/swift/rules/UIKIT.md +99 -0
- package/configurations/language/swift/semgrep/swift.yml +126 -0
- package/configurations/language/typescript/eslint-presets.json +215 -0
- package/configurations/language/typescript/manifest.toml +72 -0
- package/configurations/language/typescript/rules/NAMING.md +73 -0
- package/configurations/language/typescript/rules/TYPESCRIPT.md +79 -0
- package/configurations/library/drizzle/manifest.toml +68 -0
- package/configurations/library/drizzle/rules/DRIZZLE.md +44 -0
- package/configurations/library/react-hook-form/manifest.toml +15 -0
- package/configurations/library/react-hook-form/rules/REACT-HOOK-FORM.md +48 -0
- package/configurations/library/swift-snapshot-testing/manifest.toml +32 -0
- package/configurations/library/tanstack-query/eslint-presets.json +21 -0
- package/configurations/library/tanstack-query/manifest.toml +26 -0
- package/configurations/library/tanstack-query/rules/TANSTACK-QUERY.md +41 -0
- package/configurations/library/testing-library/eslint-presets.json +191 -0
- package/configurations/library/testing-library/manifest.toml +47 -0
- package/configurations/library/translations/manifest.toml +40 -0
- package/configurations/library/translations/rules/NEXT-INTL.md +33 -0
- package/configurations/library/translations/rules/TRANSLATIONS.md +102 -0
- package/configurations/library/trpc/manifest.toml +14 -0
- package/configurations/library/trpc/rules/TRPC.md +43 -0
- package/configurations/library/zod/manifest.toml +25 -0
- package/configurations/library/zod/rules/ZOD.md +44 -0
- package/configurations/library/zustand/manifest.toml +18 -0
- package/configurations/library/zustand/rules/ZUSTAND.md +106 -0
- package/configurations/platform/cloudflare/manifest.toml +94 -0
- package/configurations/platform/cloudflare/rules/CLOUDFLARE.md +23 -0
- package/configurations/platform/cloudflare/semgrep/cloudflare.yml +12 -0
- package/configurations/platform/supabase/manifest.toml +112 -0
- package/configurations/platform/supabase/rules/SUPABASE.md +129 -0
- package/configurations/platform/supabase/semgrep/supabase.yml +60 -0
- package/configurations/testing/jest/manifest.toml +54 -0
- package/configurations/testing/pytest/manifest.toml +53 -0
- package/configurations/testing/swift-tests/manifest.toml +52 -0
- package/configurations/testing/swift-tests/rules/SWIFT-TESTS.md +79 -0
- package/configurations/testing/vitest/eslint-presets.json +22 -0
- package/configurations/testing/vitest/manifest.toml +69 -0
- package/configurations/testing/vitest/rules/VITEST.md +18 -0
- package/dist/LICENSE.md +201 -0
- package/dist/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/eslint-rule-levels.json +10 -0
- package/dist/gspot.js +30740 -0
- package/dist/linkinator-program.js +65 -0
- package/dist/purgecss-program.js +51 -0
- package/dist/tool-versions.json +3 -0
- package/grammars/bash.wasm +0 -0
- package/grammars/css.wasm +0 -0
- package/grammars/html.wasm +0 -0
- package/grammars/javascript.wasm +0 -0
- package/grammars/licenses/tree-sitter-bash.txt +21 -0
- package/grammars/licenses/tree-sitter-css.txt +21 -0
- package/grammars/licenses/tree-sitter-html.txt +21 -0
- package/grammars/licenses/tree-sitter-javascript.txt +21 -0
- package/grammars/licenses/tree-sitter-python.txt +21 -0
- package/grammars/licenses/tree-sitter-swift.txt +21 -0
- package/grammars/licenses/tree-sitter-typescript.txt +21 -0
- package/grammars/python.wasm +0 -0
- package/grammars/swift.wasm +0 -0
- package/grammars/tsx.wasm +0 -0
- package/grammars/typescript.wasm +0 -0
- package/gspot.schema.json +45991 -0
- package/package.json +93 -5
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Bash Language
|
|
2
|
+
|
|
3
|
+
## Functions
|
|
4
|
+
|
|
5
|
+
Declare function-local variables with `local`, and separate the declaration from a command
|
|
6
|
+
substitution assignment when the exit code matters, because `local`, `declare`, `readonly`,
|
|
7
|
+
and `export` report their own status rather than the substitution's:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
local output
|
|
11
|
+
output="$(some_command)" || return 1
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Preserve the documented status contract: a predicate may use status 1 for false, kept
|
|
15
|
+
distinct from an execution error. At level `all`, every executable defines `main` and calls it last.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# report_unreadable_files: Reports each unreadable input and fails if any input is unreadable.
|
|
19
|
+
report_unreadable_files() {
|
|
20
|
+
local path
|
|
21
|
+
local status=0
|
|
22
|
+
|
|
23
|
+
for path in "$@"; do
|
|
24
|
+
if [[ -r ${path} ]]; then
|
|
25
|
+
continue
|
|
26
|
+
fi
|
|
27
|
+
printf 'error: input is unreadable: %s\n' "${path}" >&2
|
|
28
|
+
status=1
|
|
29
|
+
done
|
|
30
|
+
return "${status}"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Function conventions
|
|
35
|
+
|
|
36
|
+
<!-- level: all -->
|
|
37
|
+
|
|
38
|
+
Use `name() { ... }` for a function that runs in the caller's shell, and a subshell body,
|
|
39
|
+
`name() ( ... )`, for deliberate isolation of the caller's state and traps.
|
|
40
|
+
|
|
41
|
+
## Variables and constants
|
|
42
|
+
|
|
43
|
+
Mark constants `readonly` immediately after assignment, and
|
|
44
|
+
`export` only what child processes need. Never casually overwrite `PATH`, `HOME`, `IFS`,
|
|
45
|
+
`CDPATH`, `SHELL`, `PWD`, or `BASH_ENV`, and never export `CDPATH`.
|
|
46
|
+
|
|
47
|
+
Compute a directory constant with `CDPATH='' cd -- <path> && pwd -P` and carries a failure path, `|| exit 1` in an
|
|
48
|
+
entrypoint and `|| return 1` in a library. Use `$HOME`, not a quoted `~`, inside paths, and
|
|
49
|
+
assign a home-relative value before exporting it. Quote array elements passed to `unset -v`.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
script_parent="$(dirname -- "${BASH_SOURCE[0]}")" || exit 1
|
|
53
|
+
PROJECT_ROOT="$(CDPATH='' cd -- "${script_parent}/.." && pwd -P)" || exit 1
|
|
54
|
+
readonly PROJECT_ROOT
|
|
55
|
+
export PROJECT_ROOT
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quoting and expansion
|
|
59
|
+
|
|
60
|
+
Forward arguments with `"$@"`, and use `$*` only to join arguments into one string on
|
|
61
|
+
purpose. Single quotes hold literals and double quotes hold expansions. Command substitution
|
|
62
|
+
output never supplies an unquoted argument list, and word splitting never parses data. Use functions,
|
|
63
|
+
not aliases. Parameter expansion replaces external tools for string work:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
filename="archive.tar.gz"
|
|
67
|
+
base="${filename%.tar.gz}"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Arrays and argument lists
|
|
71
|
+
|
|
72
|
+
Command arguments live in arrays, expanded with `"${array[@]}"` and never populated from raw
|
|
73
|
+
`$(...)`. Read lines with a `while read` loop or `readarray`, and read filenames from `NUL`-delimited streams:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
declare -a archive_args
|
|
77
|
+
archive_args=(-czf "${archive_path}" -C "${source_directory}" .)
|
|
78
|
+
tar "${archive_args[@]}"
|
|
79
|
+
|
|
80
|
+
while IFS= LC_ALL=C read -r -d '' file; do
|
|
81
|
+
files+=("${file}")
|
|
82
|
+
done < <(find . -type f -name '*.sql' -print0)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Do not use arrays to imitate nested data structures. A need for those structures signals that the work belongs outside Bash.
|
|
86
|
+
|
|
87
|
+
## Conditionals and arithmetic
|
|
88
|
+
|
|
89
|
+
`[[ ... ]]` holds string, pattern, and file tests. `==` compares for equality with a quoted
|
|
90
|
+
right-hand side when the value may hold glob characters, and an unquoted right-hand side only
|
|
91
|
+
for pattern matching. Store a regular expression in a variable and use it
|
|
92
|
+
unquoted with `=~`. `-z` and `-n` test for emptiness. Numeric comparisons live in `(( ... ))` after validation of untrusted numbers; `<` and
|
|
93
|
+
`>` inside `[[ ... ]]` compare strings.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
readonly version_re='^[0-9]+\.[0-9]+\.[0-9]+$'
|
|
97
|
+
if [[ "${version}" =~ ${version_re} ]]; then
|
|
98
|
+
printf 'valid version\n'
|
|
99
|
+
fi
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Arithmetic uses `$(( ... ))` and `(( ... ))`. `(( i++ ))` returns false when the expression equals
|
|
103
|
+
zero, so under `errexit` increment with `retry_count=$(( retry_count + 1 ))`. Untrusted
|
|
104
|
+
strings never enter `(( ... ))`, `$(( ... ))`, `[[ value -gt n ]]`, array indices, or
|
|
105
|
+
arithmetic `for` expressions: validate with a pattern first. `10#${value}` converts unsigned
|
|
106
|
+
base-10 strings; a signed value needs `$(( ${value%%[!+-]*}10#${value#[-+]} ))`. Call `date`
|
|
107
|
+
once when several fields describe one instant, and compute a redirection path before the
|
|
108
|
+
command when the path expression mutates a variable.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
if [[ ! "${port}" =~ ^[0-9]{1,5}$ ]]; then
|
|
112
|
+
printf 'error: port must be numeric\n' >&2
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
if (( 10#${port} < 1 || 10#${port} > 65535 )); then
|
|
116
|
+
printf 'error: port is out of range\n' >&2
|
|
117
|
+
return 1
|
|
118
|
+
fi
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Loops and delimited data
|
|
122
|
+
|
|
123
|
+
Iterate arguments with `for arg in "$@"; do`, globs directly rather than `ls`, and files with
|
|
124
|
+
`while IFS= read -r line || [[ -n ${line} ]]; do ... done <"${file}"`, which keeps the last
|
|
125
|
+
unterminated line. A pipe into `while` runs the loop in a subshell, so variables set inside it
|
|
126
|
+
vanish; use process substitution or a redirected file. A here-string with command
|
|
127
|
+
substitution collects all output first, strips trailing newlines, and drops `NUL` bytes.
|
|
128
|
+
Neither form propagates the producer's failure; use a checked temporary file when that status
|
|
129
|
+
matters. Counter loops use `for (( index = 0; index < count; index++ ))`, not `seq`.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
root_dir="$(CDPATH='' cd -- "${1:?Root directory is required}" && pwd -P)" || exit 1
|
|
133
|
+
file_list="$(mktemp)" || exit 1
|
|
134
|
+
trap 'rm -f -- "${file_list}"' EXIT
|
|
135
|
+
find "${root_dir}" -type f -print0 >"${file_list}" || exit 1
|
|
136
|
+
|
|
137
|
+
while IFS= LC_ALL=C read -r -d '' file; do
|
|
138
|
+
printf 'file: %q\n' "${file}"
|
|
139
|
+
done <"${file_list}"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`IFS= read -r` prevents trimming and backslash handling. Do not save and restore `IFS` with
|
|
143
|
+
`old_ifs="${IFS}"`, which loses the distinction between unset and empty; use a function-local
|
|
144
|
+
`IFS` or a subshell. `read` treats `IFS` as a terminator, so a controlled delimited line
|
|
145
|
+
keeps its trailing empty field only with an appended delimiter. Parse general CSV with
|
|
146
|
+
application code with a real parser, never `IFS=, read`.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# join_path_parts: Joins the arguments with slashes.
|
|
150
|
+
join_path_parts() {
|
|
151
|
+
local IFS='/'
|
|
152
|
+
printf '%s\n' "$*"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Paths, globs, and file names
|
|
157
|
+
|
|
158
|
+
Quote paths, put `--` before path arguments where the command supports it, prefer globs with
|
|
159
|
+
an explicit prefix such as `./*.sql`, and never filter filenames with `grep`. Filenames can
|
|
160
|
+
hold spaces, newlines, quotes, brackets, and leading dashes. Never assume the current
|
|
161
|
+
directory; compute it, and check `cd` explicitly. A broken symlink needs `-L` beside `-e`
|
|
162
|
+
when existence matters, and a basename pattern reads `"${path##*/}"`. Handle the no-match
|
|
163
|
+
glob deliberately, either with `[[ -e ${file} ]] || continue` or with `nullglob` scoped to a
|
|
164
|
+
subshell:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
for file in ./*.sql; do
|
|
168
|
+
[[ -e ${file} ]] || continue
|
|
169
|
+
printf '%s\n' "${file}"
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
if ! cd -- "${target_dir}"; then
|
|
173
|
+
printf 'error: cannot enter target dir: %s\n' "${target_dir}" >&2
|
|
174
|
+
return 1
|
|
175
|
+
fi
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Command substitution
|
|
179
|
+
|
|
180
|
+
Quote `$(...)`, capture the status at once, and remember that it strips trailing newlines,
|
|
181
|
+
cannot carry binary data, and never builds an argument list. Use `$(<file)` only when
|
|
182
|
+
the operation allows stripping trailing newlines. When trailing newlines matter, keep them with a
|
|
183
|
+
sentinel:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
commit_sha="$(git rev-parse HEAD)" || exit 1
|
|
187
|
+
content_with_sentinel="$(some_command || exit; printf x)" || return 1
|
|
188
|
+
content="${content_with_sentinel%x}"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Pipelines and redirection
|
|
192
|
+
|
|
193
|
+
Read `PIPESTATUS` immediately when individual statuses matter. Order redirections to show the intent, `>file 2>&1`, and never close a standard descriptor as a shortcut for `/dev/null`. A
|
|
194
|
+
pipeline never reads from and writes to the same file; rewrite a file through a
|
|
195
|
+
temporary file and an atomic rename. Parallel `xargs` jobs do not produce ordered, unmixed
|
|
196
|
+
output: write per-job files and combine them, or use a tool that serializes. Check a command
|
|
197
|
+
directly or capture its status in a named variable rather than `(( ! $? )) || die`.
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
tar -cf - ./* | (cd -- "${target_dir}" && tar -xf -)
|
|
201
|
+
statuses=( "${PIPESTATUS[@]}" )
|
|
202
|
+
if (( statuses[0] != 0 || statuses[1] != 0 )); then
|
|
203
|
+
printf 'error: tar copy failed\n' >&2
|
|
204
|
+
return 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
edited_file="$(mktemp "${file}.XXXXXX")" || return 1
|
|
208
|
+
sed 's/foo/bar/g' "${file}" >"${edited_file}" || {
|
|
209
|
+
rm -f -- "${edited_file}"
|
|
210
|
+
return 1
|
|
211
|
+
}
|
|
212
|
+
mv -- "${edited_file}" "${file}"
|
|
213
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Bash Naming
|
|
2
|
+
|
|
3
|
+
Bash naming follows Google shell guidance, with the overrides below for file stems.
|
|
4
|
+
|
|
5
|
+
## Bash case rules
|
|
6
|
+
|
|
7
|
+
<!-- level: all -->
|
|
8
|
+
|
|
9
|
+
- Executable scripts use `.sh` when invoked through a task runner or build
|
|
10
|
+
rules.
|
|
11
|
+
- Omit the extension on an executable script only when users invoke it
|
|
12
|
+
as a command on `PATH`.
|
|
13
|
+
- Give sourced libraries the `.sh` extension and no executable permission.
|
|
14
|
+
- Numbered pipeline steps use `NN-description.sh`, such as `01-install.sh`; the numeric prefix defines a
|
|
15
|
+
structural prefix the naming check strips before it matches the stem.
|
|
16
|
+
- Functions and mutable variables use `lower_snake_case`.
|
|
17
|
+
- Package-like function prefixes may use `::` only when a script family already
|
|
18
|
+
uses that convention.
|
|
19
|
+
|
|
20
|
+
| Avoid | Prefer | Meaning |
|
|
21
|
+
| ------------------ | -------------------------- | -------------------- |
|
|
22
|
+
| `DeployScript.sh` | `deploy-api.sh` | API deployment. |
|
|
23
|
+
| `deploy_script.sh` | `upload-storage-assets.sh` | Storage upload. |
|
|
24
|
+
| `helpers.sh` | `archive-reports` | A command on `PATH`. |
|
|
25
|
+
|
|
26
|
+
Name a deployment function `deploy_api` and its local target
|
|
27
|
+
`target_environment`.
|
|
28
|
+
|
|
29
|
+
## Bash variables
|
|
30
|
+
|
|
31
|
+
<!-- level: all -->
|
|
32
|
+
|
|
33
|
+
- Loop variables name each item in the loop.
|
|
34
|
+
- Name a temporary path after what it holds, such as `download_file` or `work_dir`; `tmp` and
|
|
35
|
+
`temp` say nothing.
|
|
36
|
+
- Avoid vague names when the domain provides a name.
|
|
37
|
+
- Avoid shell-reserved and shell-special names for unrelated values.
|
|
38
|
+
|
|
39
|
+
Use `migration_file` when iterating migration paths and `report_output` for
|
|
40
|
+
captured report text. Check the report command's exit status before printing
|
|
41
|
+
the captured output. A loop variable such as `i` does not describe a file.
|
|
42
|
+
|
|
43
|
+
## Bash functions
|
|
44
|
+
|
|
45
|
+
<!-- level: all -->
|
|
46
|
+
|
|
47
|
+
- Function names use verb phrases when the function has side effects.
|
|
48
|
+
- Name a function that prints to `stdout` for the data it prints.
|
|
49
|
+
- Do not name scripts or functions after shell builtins or common commands.
|
|
50
|
+
- Do not make function names so generic that logs and stack traces lose context.
|
|
51
|
+
|
|
52
|
+
Name a port validator `validate_port`. Avoid `test`, which shadows a shell builtin, and
|
|
53
|
+
generic names such as `run` or `process`.
|
|
54
|
+
|
|
55
|
+
## Files
|
|
56
|
+
|
|
57
|
+
<!-- level: all -->
|
|
58
|
+
|
|
59
|
+
- Put a related script family in an owning subdirectory and give the contained
|
|
60
|
+
files role names such as `main.sh`, `state.sh`, `query.sh`, or `report.sh`.
|
|
61
|
+
- External hook families may use a dynamic shared prefix when the external
|
|
62
|
+
interface owns those filenames.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Bash Operations
|
|
2
|
+
|
|
3
|
+
## Module ownership and visibility
|
|
4
|
+
|
|
5
|
+
<!-- level: all -->
|
|
6
|
+
|
|
7
|
+
Each file owns one cohesive responsibility, and the directory supplies the family name.
|
|
8
|
+
|
|
9
|
+
- A sourced public function carries its family name, such as `release_prepare` or
|
|
10
|
+
`cache_read_state`.
|
|
11
|
+
- A function with one caller lives in the caller's file.
|
|
12
|
+
|
|
13
|
+
## Deployment and publishing pipelines
|
|
14
|
+
|
|
15
|
+
Deployment, publishing, and long-running scripts separate validation, planning, confirmation,
|
|
16
|
+
execution, readiness checks, and cleanup.
|
|
17
|
+
|
|
18
|
+
- Fail before any work when the script lacks a required input, command, file, host, or secret.
|
|
19
|
+
- Name the target explicitly: never infer production, a remote, a host, or an artifact from a
|
|
20
|
+
branch name without a confirmation or a CI contract.
|
|
21
|
+
- A destructive or remote action asks for confirmation unless CI owns the guard.
|
|
22
|
+
- Use idempotent commands and stop at the first failed required step.
|
|
23
|
+
- Check readiness after deploying or publishing, and print diagnostics a person can act on.
|
|
24
|
+
- Keep rollback and cleanup explicit, and delete no artifact or result as a side effect.
|
|
25
|
+
- Log enough to reconstruct what happened, without secrets.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# confirm_exact: Requires the user to type the expected value.
|
|
29
|
+
# Arguments:
|
|
30
|
+
# Prompt label.
|
|
31
|
+
# Expected response.
|
|
32
|
+
confirm_exact() {
|
|
33
|
+
local label="$1"
|
|
34
|
+
local expected="$2"
|
|
35
|
+
local response
|
|
36
|
+
|
|
37
|
+
printf '%s Type %s to continue: ' "${label}" "${expected}" >&2
|
|
38
|
+
IFS= read -r response
|
|
39
|
+
|
|
40
|
+
[[ "${response}" == "${expected}" ]]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Remote commands
|
|
45
|
+
|
|
46
|
+
Copy a reviewed script to the remote host and invoke it with arguments. SSH joins its arguments
|
|
47
|
+
into one remote shell string, so local quoting does not keep remote argument boundaries. Validate
|
|
48
|
+
host, user, service, and path values before using them, never build a remote command from user
|
|
49
|
+
input, and give every remote call that can hang a timeout.
|
|
50
|
+
|
|
51
|
+
Good, for a remote Bash login shell:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
printf -v remote_command 'bash -- %q %q %q' "${remote_script}" "${dir}" "${service}"
|
|
55
|
+
ssh -- "${host}" "${remote_command}"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Persisted state
|
|
59
|
+
|
|
60
|
+
Never `source` generated state or pass it to `bash -c`. Write state in a fixed data format with an
|
|
61
|
+
exact key schema to a restricted file in the destination folder, and replace the previous state
|
|
62
|
+
with an atomic `mv`. A reader treats a malformed, duplicate, or unknown key as an error.
|
|
63
|
+
|
|
64
|
+
## Retries
|
|
65
|
+
|
|
66
|
+
Retry only an operation known to converge, such as a network pull or readiness polling, with a
|
|
67
|
+
bounded number of attempts. A retry never masks corrupt data, invalid credentials, a failed
|
|
68
|
+
migration, a syntax error, or a permission problem. Capture the status in the `else` branch,
|
|
69
|
+
because an `if` whose condition fails and that has no `else` returns zero:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
if "$@"; then
|
|
73
|
+
return 0
|
|
74
|
+
else
|
|
75
|
+
status=$?
|
|
76
|
+
fi
|
|
77
|
+
```
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Bash Safety
|
|
2
|
+
|
|
3
|
+
## Calling commands
|
|
4
|
+
|
|
5
|
+
Use fixed command names with argument arrays, never strings built from input, and a script
|
|
6
|
+
never passes untrusted input to a shell. Never use `bash -lc` in repository scripts, and a
|
|
7
|
+
function that runs a caller-supplied command takes the command and its arguments after
|
|
8
|
+
`shift` and invokes `"$@"`.
|
|
9
|
+
|
|
10
|
+
Check uncommon commands with `command -v` before a long-running, destructive, publishing, or
|
|
11
|
+
error-handling path depends on them. Prefer builtins and parameter expansion to external
|
|
12
|
+
commands for string and arithmetic work, and pass files directly rather than through
|
|
13
|
+
`cat`. Never run `su -c` without the target user.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# require_command: Ensures a command exists on PATH.
|
|
17
|
+
# Arguments:
|
|
18
|
+
# Command name.
|
|
19
|
+
require_command() {
|
|
20
|
+
local command_name="$1"
|
|
21
|
+
|
|
22
|
+
if ! command -v "${command_name}" >/dev/null 2>&1; then
|
|
23
|
+
printf 'error: required command not found: %s\n' "${command_name}" >&2
|
|
24
|
+
return 1
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Processes and privilege
|
|
30
|
+
|
|
31
|
+
Treat process names as advisory information, never authorization. Stop and restart paths stop only
|
|
32
|
+
process ids recorded by the owning script family, found through the service manager, a `PID` file, or
|
|
33
|
+
`pgrep -x`, never `ps | grep`. A script that starts background jobs saves each `PID`, waits for
|
|
34
|
+
each, captures each status, and writes per-job output to separate files. It bounds its
|
|
35
|
+
fan-out and kills owned children on `INT`, `TERM`, and `EXIT`. `sudo command > file` redirects
|
|
36
|
+
as the current user, and a glob in `sudo command /path/*` expands before `sudo` runs.
|
|
37
|
+
Privileged writes go through `sudo tee`, and keep a privileged shell string as a fixed literal
|
|
38
|
+
with no input.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
generate_config | sudo tee /etc/service/config >/dev/null
|
|
42
|
+
|
|
43
|
+
# run_remote_checks: Runs remote checks and returns non-zero on any failure.
|
|
44
|
+
# Arguments:
|
|
45
|
+
# Small, already bounded host list to check.
|
|
46
|
+
run_remote_checks() {
|
|
47
|
+
local host
|
|
48
|
+
local pid
|
|
49
|
+
local status=0
|
|
50
|
+
|
|
51
|
+
trap 'cleanup_children' EXIT
|
|
52
|
+
trap 'cleanup_children; exit 130' INT
|
|
53
|
+
trap 'cleanup_children; exit 143' TERM
|
|
54
|
+
|
|
55
|
+
for host in "$@"; do
|
|
56
|
+
check_host "${host}" >"${work_dir}/${host}.log" 2>&1 &
|
|
57
|
+
child_pids+=( "$!" )
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
for pid in "${child_pids[@]}"; do
|
|
61
|
+
wait "${pid}" || status=1
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
trap - EXIT INT TERM
|
|
65
|
+
return "${status}"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Structured data
|
|
70
|
+
|
|
71
|
+
Use parameter expansion for string edits. JSON goes through `jq`, and YAML through `yq` or
|
|
72
|
+
a project-owned parser. Never parse JSON, YAML, XML, HTML, plist, or xcodebuild output
|
|
73
|
+
with ad hoc `grep | sed | awk` unless the script controls the input and the format has no complex structure.
|
|
74
|
+
Prefer machine output modes such as JSON, `NUL`, or explicit format flags and never parse process lists,
|
|
75
|
+
pretty tables, progress bars, or localized text by fixed field numbers. Quote `tr` character
|
|
76
|
+
classes and pin `LC_ALL=C` when converting case. macOS and GNU `sed -i` differ; a committed
|
|
77
|
+
script rewrites through a temporary file unless its documentation restricts it to one platform.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# get_service_url: Prints a required nonempty service URL from JSON configuration.
|
|
81
|
+
get_service_url() {
|
|
82
|
+
local config_file="${1:?Configuration file is required}"
|
|
83
|
+
local service_url
|
|
84
|
+
|
|
85
|
+
service_url="$(jq -er '.service.url | select(type == "string" and length > 0)' "${config_file}")" || return 1
|
|
86
|
+
printf '%s\n' "${service_url}"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Network
|
|
91
|
+
|
|
92
|
+
Downloads use `curl --fail --show-error --silent --location` into an explicit file, with
|
|
93
|
+
tool-native timeouts such as `--connect-timeout` plus `--max-time`, or SSH `ConnectTimeout`, rather
|
|
94
|
+
than GNU `timeout`, which macOS lacks. Download executable content, verify it against an
|
|
95
|
+
independently obtained checksum or signature, and only then run it.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
installer="${work_dir}/install.sh"
|
|
99
|
+
curl --fail --show-error --silent --location \
|
|
100
|
+
--connect-timeout 10 \
|
|
101
|
+
--max-time 60 \
|
|
102
|
+
--output "${installer}" \
|
|
103
|
+
"${installer_url}" || exit 1
|
|
104
|
+
printf '%s %s\n' "${expected_sha256}" "${installer}" | shasum -a 256 -c - || exit 1
|
|
105
|
+
bash "${installer}" --version "${tool_version}"
|
|
106
|
+
|
|
107
|
+
ssh -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=2 \
|
|
108
|
+
-- "${host}" systemctl is-active --quiet "${service_name}"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Secrets and environment
|
|
112
|
+
|
|
113
|
+
Keep `set -x` away from secrets. An indirect expansion `${!name}` takes only a validated name.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# require_env: Ensures an environment variable is set and non-empty.
|
|
117
|
+
# Arguments:
|
|
118
|
+
# Environment variable name.
|
|
119
|
+
require_env() {
|
|
120
|
+
local name="$1"
|
|
121
|
+
|
|
122
|
+
if [[ ! "${name}" =~ ^[A-Z_][A-Z0-9_]*$ ]]; then
|
|
123
|
+
printf 'error: invalid env var name\n' >&2
|
|
124
|
+
return 1
|
|
125
|
+
fi
|
|
126
|
+
if [[ -z "${!name:-}" ]]; then
|
|
127
|
+
printf 'error: %s is required\n' "${name}" >&2
|
|
128
|
+
return 1
|
|
129
|
+
fi
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Temporary files, locks, and cleanup
|
|
134
|
+
|
|
135
|
+
Temporary paths come from `mktemp` or `mktemp -d` under the system or an explicit project
|
|
136
|
+
temp directory, never from predictable names. Register cleanup immediately after creation.
|
|
137
|
+
Put downloaded, copied, or generated content in a temporary file, validate it with the real
|
|
138
|
+
parser, and replace the destination with `mv`. The file keeps the restrictive permissions
|
|
139
|
+
`mktemp` gave it unless deployment needs others. Remove failed artifacts unless the script
|
|
140
|
+
documents keeping them. Acquire locks atomically with a `mkdir` lock directory holding
|
|
141
|
+
the owner `PID` or with `noclobber` redirection, never with a `test` followed by a create.
|
|
142
|
+
|
|
143
|
+
Stop commands remove only what the script created. Validate a cleanup path from
|
|
144
|
+
input or persistent state against its permitted root. Reject empty values, `/`,
|
|
145
|
+
the repository root, `$HOME`, symlinks, and anything outside the owner. Keep a `mktemp -d` result
|
|
146
|
+
exactly. Never widen the path to its neighbors. `|| true` never follows a destructive
|
|
147
|
+
command or a required installation, publishing, build, or runtime command.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
downloaded_file="$(mktemp "${config_file}.XXXXXX")" || exit 1
|
|
151
|
+
trap 'rm -f -- "${downloaded_file}"' EXIT
|
|
152
|
+
curl --fail --show-error --silent --location --output "${downloaded_file}" "${config_url}" || exit 1
|
|
153
|
+
jq empty "${downloaded_file}" >/dev/null || exit 1
|
|
154
|
+
mv -- "${downloaded_file}" "${config_file}" || exit 1
|
|
155
|
+
trap - EXIT
|
|
156
|
+
|
|
157
|
+
if ! mkdir "${lock_dir}"; then
|
|
158
|
+
printf 'error: lock is already held: %s\n' "${lock_dir}" >&2
|
|
159
|
+
return 1
|
|
160
|
+
fi
|
|
161
|
+
printf '%s\n' "$$" >"${lock_dir}/pid" || {
|
|
162
|
+
rmdir "${lock_dir}"
|
|
163
|
+
return 1
|
|
164
|
+
}
|
|
165
|
+
trap 'rm -f -- "${lock_dir}/pid"; rmdir -- "${lock_dir}"' EXIT
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Put recursive deletes in one script, and accept that script with an `[[ignore]]` record for `bash/safety`.
|
|
169
|
+
Validate its cleanup path against the owner root:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# remove_build_dir: Removes the build child of the approved owner directory.
|
|
173
|
+
remove_build_dir() {
|
|
174
|
+
local owner_root build_dir
|
|
175
|
+
|
|
176
|
+
[[ -n "$1" ]] || return 1
|
|
177
|
+
owner_root="$(cd -- "$1" && pwd -P)" || return 1
|
|
178
|
+
[[ "${owner_root}" != / ]] || return 1
|
|
179
|
+
build_dir="${owner_root}/build"
|
|
180
|
+
[[ -d "${build_dir}" && ! -L "${build_dir}" ]] || return 1
|
|
181
|
+
rm -rf -- "${build_dir}"
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Portability
|
|
186
|
+
|
|
187
|
+
macOS and BSD
|
|
188
|
+
differ from GNU in `sed`, `date`, `readlink`, `mktemp`, `stat`, `xargs`, and `grep`; keep a
|
|
189
|
+
platform-specific branch with the operation that owns it. Handle `realpath` and `date`
|
|
190
|
+
parsing differences. macOS `/bin/bash` uses an old Bash version,
|
|
191
|
+
Linux-only utilities need checks in macOS-compatible scripts, and CI does not share the
|
|
192
|
+
developer's `PATH`. Symlink-aware absolute paths come from `pwd -P` after `cd`, or from
|
|
193
|
+
product-owned application code.
|
|
194
|
+
|
|
195
|
+
## Verification and debugging
|
|
196
|
+
|
|
197
|
+
Run the configured syntax, ShellCheck, and shfmt commands. Run destructive or provider tests
|
|
198
|
+
only in a throwaway environment.
|
|
199
|
+
|
|
200
|
+
Production scripts gain no test-only flags, branches, or command replacements. When
|
|
201
|
+
debugging, start from the exact error and line Bash reports, run `bash -n` and ShellCheck,
|
|
202
|
+
reduce to the smallest reproducing block, and expose invisible characters with
|
|
203
|
+
`printf '%q\n'`. Trace with `bash -x` or a local `set -x` block with a `PS4` naming file, line, and function,
|
|
204
|
+
only while diagnosing, never around secrets, and never commit broad tracing, `trap DEBUG`, or stepping
|
|
205
|
+
code. Bash 4.4 supports `BASH_XTRACEFD`. A debug helper preserves the status it diagnoses.
|
|
206
|
+
|
|
207
|
+
Common causes follow. `unexpected EOF` indicates an unmatched quote, an unterminated here document,
|
|
208
|
+
a missing `fi`, `done`, or `esac`, or a CRLF ending hiding the delimiter.
|
|
209
|
+
|
|
210
|
+
`too many arguments` indicates an unquoted expansion inside `[ ... ]`. `event not found` indicates interactive history expansion
|
|
211
|
+
of `!`. Use `type -a` to find an alias, function, builtin, or `PATH` collision when a command behaves differently.
|
|
212
|
+
Failure before the shebang indicates a byte order mark (BOM) or CRLF.
|
|
213
|
+
|
|
214
|
+
## Refactoring existing scripts
|
|
215
|
+
|
|
216
|
+
Read the whole script and its sourced libraries, identify the caller interface: local, CI,
|
|
217
|
+
remote host, or package script, and preserve behavior before changing style. Fix quoting and
|
|
218
|
+
argument arrays near the touched logic, and add explicit checks around dangerous commands.
|
|
219
|
+
Share a helper across the script family only when it has a real shared contract. Do not
|
|
220
|
+
convert a large script in one pass or change shebangs across a family without verifying the
|
|
221
|
+
runtime. Run the narrow shell lint and format command before the broader checks. Move complex script
|
|
222
|
+
logic into application code and call it directly.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: gspot.bash.curl-pipe-shell
|
|
3
|
+
languages:
|
|
4
|
+
- bash
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: A script is downloaded and run in one step; nobody can read what runs.
|
|
7
|
+
Download it to a file, check it, then run it.
|
|
8
|
+
pattern-either:
|
|
9
|
+
- pattern: curl ... | sh ...
|
|
10
|
+
- pattern: curl ... | sudo sh ...
|
|
11
|
+
- pattern: sh <(curl ...)
|
|
12
|
+
- pattern: sh -c "$(curl ...)"
|
|
13
|
+
- pattern: curl ... | bash ...
|
|
14
|
+
- pattern: curl ... | sudo bash ...
|
|
15
|
+
- pattern: bash <(curl ...)
|
|
16
|
+
- pattern: bash -c "$(curl ...)"
|
|
17
|
+
- pattern: wget ... | sh ...
|
|
18
|
+
- pattern: wget ... | sudo sh ...
|
|
19
|
+
- pattern: sh <(wget ...)
|
|
20
|
+
- pattern: sh -c "$(wget ...)"
|
|
21
|
+
- pattern: wget ... | bash ...
|
|
22
|
+
- pattern: wget ... | sudo bash ...
|
|
23
|
+
- pattern: bash <(wget ...)
|
|
24
|
+
- pattern: bash -c "$(wget ...)"
|
|
25
|
+
- id: gspot.bash.eval
|
|
26
|
+
languages:
|
|
27
|
+
- bash
|
|
28
|
+
severity: ERROR
|
|
29
|
+
message: eval runs a string as code; build the command as an array instead.
|
|
30
|
+
pattern: eval ...
|