@gspothq/cli 0.1.1 → 0.2.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 +58 -11
- package/configurations/database/postgres/manifest.toml +51 -57
- package/configurations/database/postgres/rules/POSTGRES.md +14 -13
- package/configurations/framework/astro/manifest.toml +33 -35
- package/configurations/framework/astro/rules/ASTRO.md +2 -2
- package/configurations/framework/expo/manifest.toml +19 -19
- package/configurations/framework/express/manifest.toml +10 -4
- package/configurations/framework/express/rules/EXPRESS.md +9 -7
- package/configurations/framework/fastapi/manifest.toml +9 -3
- package/configurations/framework/fastapi/rules/FASTAPI.md +19 -18
- package/configurations/framework/nestjs/manifest.toml +12 -12
- package/configurations/framework/nestjs/rules/NESTJS.md +9 -9
- package/configurations/framework/nextjs/manifest.toml +39 -79
- package/configurations/framework/nextjs/rules/NEXTJS.md +9 -7
- package/configurations/framework/nextjs/tool-files/semgrep/nextjs.yml +35 -0
- package/configurations/framework/react/manifest.toml +33 -43
- package/configurations/framework/react/rules/REACT.md +14 -20
- package/configurations/framework/react-dom/manifest.toml +6 -6
- package/configurations/framework/react-native/manifest.toml +11 -11
- package/configurations/framework/react-native/rules/REACT-NATIVE.md +3 -5
- package/configurations/framework/site/manifest.toml +57 -110
- package/configurations/framework/site/rules/SITE.md +1 -1
- package/configurations/framework/svelte/manifest.toml +29 -31
- package/configurations/framework/svelte/rules/SVELTE.md +6 -6
- package/configurations/framework/vue/manifest.toml +37 -37
- package/configurations/framework/vue/rules/VUE.md +6 -6
- package/configurations/general/commits/manifest.toml +20 -84
- package/configurations/general/commits/rules/GIT.md +24 -13
- package/configurations/general/coverage/manifest.toml +1 -1
- package/configurations/general/dependencies/manifest.toml +47 -107
- package/configurations/general/dependencies/rules/DEPENDENCIES.md +13 -7
- package/configurations/general/docs/manifest.toml +28 -43
- package/configurations/general/docs/rules/DOCS.md +16 -9
- package/configurations/general/duplication/manifest.toml +12 -14
- package/configurations/general/engineering/manifest.toml +6 -2
- package/configurations/general/engineering/rules/agent/PLANNING.md +9 -6
- package/configurations/general/engineering/rules/agent/SUPPRESSIONS.md +20 -11
- package/configurations/general/engineering/rules/agent/TASKS.md +6 -2
- package/configurations/general/engineering/rules/agent/WORKING.md +34 -29
- package/configurations/general/engineering/rules/code/ACCESSIBILITY.md +9 -7
- package/configurations/general/engineering/rules/code/CLI.md +12 -9
- package/configurations/general/engineering/rules/code/COMMENTS.md +11 -1
- package/configurations/general/engineering/rules/code/CONFIGURATION.md +2 -0
- package/configurations/general/engineering/rules/code/ERRORS.md +5 -3
- package/configurations/general/engineering/rules/code/HTTP.md +9 -10
- package/configurations/general/engineering/rules/code/LOGGING.md +10 -9
- package/configurations/general/engineering/rules/code/TESTING.md +18 -11
- package/configurations/general/files/manifest.toml +45 -73
- package/configurations/general/files/rules/YAML.md +20 -10
- package/configurations/general/format/manifest.toml +53 -106
- package/configurations/general/gspot/manifest.toml +19 -39
- package/configurations/general/gspot/rules/GENERATED.md +14 -4
- package/configurations/general/licenses/manifest.toml +12 -14
- package/configurations/general/naming/defaults.json +14 -13
- package/configurations/general/naming/manifest.toml +17 -17
- package/configurations/general/naming/rules/NAMING.md +87 -67
- package/configurations/general/prose/manifest.toml +166 -25
- package/configurations/general/prose/rules/WRITING.md +6 -2
- package/configurations/general/repository/manifest.toml +12 -12
- package/configurations/general/secrets/manifest.toml +52 -73
- package/configurations/general/secrets/rules/SECRETS.md +3 -1
- package/configurations/general/security/manifest.toml +29 -52
- package/configurations/general/security/rules/SECURITY.md +8 -6
- package/configurations/general/spelling/manifest.toml +15 -25
- package/configurations/general/structure/manifest.toml +31 -31
- package/configurations/infrastructure/ansible/manifest.toml +15 -15
- package/configurations/infrastructure/docker/manifest.toml +55 -67
- package/configurations/infrastructure/docker/rules/DOCKER.md +15 -13
- package/configurations/infrastructure/github-actions/manifest.toml +21 -41
- package/configurations/infrastructure/github-actions/rules/GITHUB-ACTIONS.md +16 -12
- package/configurations/infrastructure/github-actions/tool-files/zizmor.yml +5 -0
- package/configurations/infrastructure/nginx/manifest.toml +20 -20
- package/configurations/infrastructure/nginx/rules/NGINX.md +5 -4
- package/configurations/infrastructure/openapi/manifest.toml +20 -20
- package/configurations/infrastructure/openapi/rules/OPENAPI.md +3 -1
- package/configurations/infrastructure/xcode/manifest.toml +55 -75
- package/configurations/infrastructure/xcode/rules/XCODE.md +5 -3
- package/configurations/language/bash/manifest.toml +89 -170
- package/configurations/language/bash/rules/BASH.md +79 -17
- package/configurations/language/bash/rules/LANGUAGE.md +30 -26
- package/configurations/language/bash/rules/OPERATIONS.md +10 -8
- package/configurations/language/bash/rules/SAFETY.md +39 -35
- package/configurations/language/css/manifest.toml +22 -61
- package/configurations/language/css/rules/CSS.md +21 -2
- package/configurations/language/css/rules/TAILWIND.md +7 -5
- package/configurations/language/html/manifest.toml +16 -35
- package/configurations/language/html/rules/HTML.md +23 -5
- package/configurations/language/javascript/eslint-runtime-names.json +66 -61
- package/configurations/language/javascript/eslint-runtime.mjs +118 -0
- package/configurations/language/javascript/manifest.toml +135 -207
- package/configurations/language/javascript/rules/BUN.md +2 -2
- package/configurations/language/javascript/rules/COMPONENTS.md +4 -4
- package/configurations/language/javascript/rules/DENO.md +1 -1
- package/configurations/language/javascript/rules/JAVASCRIPT.md +30 -17
- package/configurations/language/javascript/rules/NODE.md +2 -2
- package/configurations/language/javascript/rules/PLAYWRIGHT.md +10 -9
- package/configurations/language/markdown/manifest.toml +20 -51
- package/configurations/language/python/manifest.toml +231 -342
- package/configurations/language/python/rules/DESIGN.md +40 -33
- package/configurations/language/python/rules/FLOW.md +8 -6
- package/configurations/language/python/rules/PACKAGING.md +14 -7
- package/configurations/language/python/rules/PYTHON.md +83 -33
- package/configurations/language/python/rules/TYPING.md +25 -23
- package/configurations/language/sql/manifest.toml +19 -37
- package/configurations/language/sql/rules/SQL.md +90 -2
- package/configurations/language/swift/manifest.toml +67 -78
- package/configurations/language/swift/rules/SWIFT.md +138 -40
- package/configurations/language/swift/rules/SWIFTUI.md +11 -9
- package/configurations/language/swift/rules/UIKIT.md +21 -19
- package/configurations/language/typescript/manifest.toml +34 -34
- package/configurations/language/typescript/rules/TYPESCRIPT.md +62 -10
- package/configurations/library/drizzle/manifest.toml +25 -38
- package/configurations/library/drizzle/rules/DRIZZLE.md +4 -2
- package/configurations/library/react-hook-form/manifest.toml +4 -4
- package/configurations/library/react-hook-form/rules/REACT-HOOK-FORM.md +15 -13
- package/configurations/library/tanstack-query/manifest.toml +6 -6
- package/configurations/library/tanstack-query/rules/TANSTACK-QUERY.md +5 -3
- package/configurations/library/testing-library/manifest.toml +6 -6
- package/configurations/library/translations/manifest.toml +22 -22
- package/configurations/library/translations/rules/NEXT-INTL.md +6 -8
- package/configurations/library/translations/rules/TRANSLATIONS.md +22 -23
- package/configurations/library/trpc/manifest.toml +3 -3
- package/configurations/library/trpc/rules/TRPC.md +8 -6
- package/configurations/library/zod/manifest.toml +6 -6
- package/configurations/library/zod/rules/ZOD.md +4 -2
- package/configurations/library/zustand/manifest.toml +4 -4
- package/configurations/library/zustand/rules/ZUSTAND.md +16 -14
- package/configurations/platform/cloudflare/manifest.toml +28 -47
- package/configurations/platform/cloudflare/rules/CLOUDFLARE.md +11 -6
- package/configurations/platform/supabase/manifest.toml +48 -61
- package/configurations/platform/supabase/rules/SUPABASE.md +27 -28
- package/configurations/testing/jest/manifest.toml +21 -22
- package/configurations/testing/pytest/manifest.toml +28 -28
- package/configurations/testing/swift-tests/manifest.toml +18 -29
- package/configurations/testing/swift-tests/rules/SWIFT-TESTS.md +7 -5
- package/configurations/testing/vitest/manifest.toml +32 -32
- package/configurations/testing/vitest/rules/VITEST.md +3 -1
- package/dist/gspot.js +22297 -24707
- package/dist/{linkinator-program.js → linkinator-process.js} +5 -7
- package/dist/{purgecss-program.js → purgecss-process.js} +12 -12
- package/package.json +7 -9
- package/configurations/infrastructure/github-actions/zizmor.yml +0 -3
- package/configurations/language/bash/rules/NAMING.md +0 -62
- package/configurations/language/css/rules/NAMING.md +0 -17
- package/configurations/language/html/rules/NAMING.md +0 -16
- package/configurations/language/javascript/rules/NAMING.md +0 -31
- package/configurations/language/markdown/markdownlint-cli2.mjs +0 -19
- package/configurations/language/python/rules/NAMING.md +0 -53
- package/configurations/language/sql/rules/NAMING.md +0 -90
- package/configurations/language/swift/rules/NAMING.md +0 -88
- package/configurations/language/typescript/rules/NAMING.md +0 -73
- package/configurations/library/swift-snapshot-testing/manifest.toml +0 -32
- package/dist/THIRD-PARTY-NOTICES.md +0 -25
- package/dist/eslint-rule-levels.json +0 -10
- package/dist/tool-versions.json +0 -3
- package/gspot.schema.json +0 -45991
- /package/{dist/LICENSE.md → LICENSE.md} +0 -0
- /package/configurations/framework/express/{semgrep → tool-files/semgrep}/express.yml +0 -0
- /package/configurations/framework/fastapi/{semgrep → tool-files/semgrep}/fastapi.yml +0 -0
- /package/configurations/general/dependencies/{syncpack.json → tool-files/syncpack.json} +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/LICENSES.md +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/ampm.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/conscious-language.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/contractions.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/currency.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/dashes.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/date-ordinals.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/dates.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/file-paths.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/future.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/hedging.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/history.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/idioms.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/interface-verbs.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/jargon.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/leading-conjunctions.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/link-text.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/locations.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/marketing.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/merge-conflict-markers.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/militaristic-language.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/modals.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/numbered-units.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/obvious-fields.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/ordinal-adverbs.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/percentage-numbers.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/placeholders.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/product-centric-writing.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/punctuated-headings.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/self-reference.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/session-identifiers.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/since.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/straight-quotes.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/suspended-hyphens.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/symbols.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/time-ranges.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/url-prepositions.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/version-range.yml +0 -0
- /package/configurations/general/secrets/{gitleaks.toml → tool-files/gitleaks.toml} +0 -0
- /package/configurations/infrastructure/docker/{trivyignore → tool-files/trivyignore} +0 -0
- /package/configurations/language/bash/{semgrep → tool-files/semgrep}/bash.yml +0 -0
- /package/configurations/language/javascript/{semgrep → tool-files/semgrep}/javascript.yml +0 -0
- /package/configurations/language/swift/{semgrep → tool-files/semgrep}/swift.yml +0 -0
- /package/configurations/platform/cloudflare/{semgrep → tool-files/semgrep}/cloudflare.yml +0 -0
- /package/configurations/platform/supabase/{semgrep → tool-files/semgrep}/supabase.yml +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
allowed_words = ["Bash", "ShellCheck", "tree-sitter"]
|
|
2
|
+
|
|
2
3
|
[configuration]
|
|
3
|
-
|
|
4
|
+
description = "Shell scripts, hooks, and task files: ShellCheck, shfmt, a syntax check, and the structure rules for shell."
|
|
4
5
|
suggests = []
|
|
5
|
-
|
|
6
|
+
title = "Bash, zsh, and Bats scripts"
|
|
6
7
|
|
|
7
8
|
[detect]
|
|
8
9
|
extensions = [".sh", ".bash", ".zsh", ".bats"]
|
|
@@ -12,250 +13,168 @@ shebangs = ["shell"]
|
|
|
12
13
|
extensions = [".sh", ".bash", ".zsh", ".bats"]
|
|
13
14
|
tags = ["shebang:shell"]
|
|
14
15
|
|
|
16
|
+
[[naming.override]]
|
|
17
|
+
allowed_names = ["IFS", "OPTARG", "OPTIND", "EUID", "UID", "PPID", "RANDOM", "SECONDS", "LINENO", "BASH_SOURCE", "BASH_REMATCH", "BASH_VERSION", "BASH_VERSINFO", "FUNCNAME", "PIPESTATUS", "REPLY", "CDPATH", "OLDPWD", "SHLVL", "HOSTNAME", "OSTYPE", "MACHTYPE", "COLUMNS", "LINES"]
|
|
18
|
+
categories = ["variables"]
|
|
19
|
+
languages = ["bash"]
|
|
20
|
+
paths = ["**"]
|
|
21
|
+
reason = "The shell defines these variable names."
|
|
22
|
+
|
|
23
|
+
[[naming.override]]
|
|
24
|
+
categories = ["functions", "variables", "constants", "methods", "attributes"]
|
|
25
|
+
ignored_prefix = "^_+"
|
|
26
|
+
languages = ["bash"]
|
|
27
|
+
paths = ["**"]
|
|
28
|
+
reason = "A leading underscore marks a private shell declaration."
|
|
29
|
+
|
|
15
30
|
[[tool]]
|
|
31
|
+
apt = "shellcheck"
|
|
32
|
+
brew = "shellcheck"
|
|
33
|
+
github = { name = "koalaman/shellcheck", version = "v0.11.0" }
|
|
34
|
+
mise = "shellcheck"
|
|
16
35
|
name = "shellcheck"
|
|
17
36
|
refused_options = [
|
|
18
37
|
{ paths = ["enable", "disable"], message = "ShellCheck rule selection comes from level recommended or all and gspot ignore. Remove enable and disable from tools.shellcheck.extra.value." },
|
|
19
38
|
]
|
|
39
|
+
replace = [".shellcheckrc"]
|
|
20
40
|
rule_url = "https://www.shellcheck.net/wiki/{rule}"
|
|
41
|
+
scoop = "shellcheck"
|
|
21
42
|
suppression = { marker = "\\bshellcheck +disable=", reason = "# ?reason: (?<reason>\\S.*)" }
|
|
22
43
|
version = "0.11.0"
|
|
23
|
-
mise = "shellcheck"
|
|
24
|
-
brew = "shellcheck"
|
|
25
|
-
apt = "shellcheck"
|
|
26
44
|
winget = "koalaman.shellcheck"
|
|
27
|
-
scoop = "shellcheck"
|
|
28
|
-
github = { name = "koalaman/shellcheck", version = "v0.11.0" }
|
|
29
|
-
[[tool.replace]]
|
|
30
|
-
file = ".shellcheckrc"
|
|
31
|
-
shared = false
|
|
32
45
|
|
|
33
46
|
[[tool]]
|
|
47
|
+
brew = "shfmt"
|
|
48
|
+
github = { name = "mvdan/sh", version = "v3.12.0" }
|
|
49
|
+
mise = "shfmt"
|
|
34
50
|
name = "shfmt"
|
|
51
|
+
scoop = "shfmt"
|
|
35
52
|
version = "3.12.0"
|
|
36
|
-
mise = "shfmt"
|
|
37
|
-
brew = "shfmt"
|
|
38
53
|
winget = "mvdan.shfmt"
|
|
39
|
-
scoop = "shfmt"
|
|
40
|
-
github = { name = "mvdan/sh", version = "v3.12.0" }
|
|
41
|
-
|
|
42
|
-
[[tool]]
|
|
43
|
-
name = "bash"
|
|
44
|
-
min_version = "4.4"
|
|
45
|
-
install_hint = "install Bash 4.4 or newer, such as with brew install bash on macOS"
|
|
46
|
-
system = true
|
|
47
|
-
version_command = ["--version"]
|
|
48
|
-
version_pattern = "version (\\d+\\.\\d+(?:\\.\\d+)?)"
|
|
49
54
|
|
|
50
55
|
[[tool]]
|
|
51
56
|
name = "zsh"
|
|
52
57
|
system = true
|
|
53
58
|
|
|
54
|
-
[[tool]]
|
|
55
|
-
name = "bats"
|
|
56
|
-
system = true
|
|
57
|
-
|
|
58
59
|
[[tool_file]]
|
|
60
|
+
per_scope = true
|
|
59
61
|
target = ".gspot/config/shellcheckrc"
|
|
60
62
|
tool = "shellcheck"
|
|
61
|
-
rule_keys = ["enable", "disable"]
|
|
62
|
-
per_scope = true
|
|
63
63
|
|
|
64
|
-
[[
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
command = ["bash", "-n", "{file}"]
|
|
70
|
-
files = { tags = ["bash"] }
|
|
71
|
-
output = { format = "regex", pattern = "^.*: line (?<line>\\d+): (?<message>.*)$" }
|
|
72
|
-
summary = "Checks that Bash scripts parse before running them."
|
|
73
|
-
why = "A script with a syntax error stops at the broken line, sometimes after it has already done half its work."
|
|
74
|
-
help = "Open the file at the line bash names and fix the quoting, bracket, or keyword it complains about."
|
|
64
|
+
[[tool_file]]
|
|
65
|
+
per_scope = true
|
|
66
|
+
target = ".gspot/config/semgrep/bash.yml"
|
|
67
|
+
tool = "semgrep"
|
|
68
|
+
when = { configuration = "security" }
|
|
75
69
|
|
|
76
70
|
[[check]]
|
|
77
|
-
name = "zsh-syntax"
|
|
78
|
-
example = "In a Zsh script, `if then` has no condition. Use a real test: `if [[ -f \"$file\" ]]; then print -r -- \"$file\"; fi`, then rerun with Zsh installed."
|
|
79
|
-
level = "recommended"
|
|
80
|
-
stage = "commit"
|
|
81
71
|
command = ["zsh", "-n", "{file}"]
|
|
72
|
+
example = "In a Zsh script, `if then` has no condition. Write a real test, such as `if [[ -f \"$file\" ]]; then print -r -- \"$file\"; fi`."
|
|
82
73
|
files = { tags = ["zsh"] }
|
|
83
|
-
|
|
84
|
-
summary = "Checks Zsh syntax with the Zsh interpreter."
|
|
85
|
-
why = "Zsh syntax differs from Bash, so a Bash parser rejects valid Zsh programs."
|
|
86
|
-
help = "Fix the syntax at the line Zsh reports. Install zsh when the command is missing."
|
|
87
|
-
|
|
88
|
-
[[check]]
|
|
89
|
-
name = "bats-syntax"
|
|
90
|
-
example = "In a `.bats` file, an unterminated `@test \"greeting\" {` block fails parsing. Complete the test body and close it with `}` before rerunning with Bats installed."
|
|
74
|
+
help = "Fix the syntax at the line Zsh reports. Install zsh when the system lacks it."
|
|
91
75
|
level = "recommended"
|
|
76
|
+
name = "zsh-syntax"
|
|
77
|
+
output = { format = "regex", pattern = "^(?<file>[^:]+):(?<line>\\d+):\\s*(?<message>.*)$" }
|
|
92
78
|
stage = "commit"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
tool = ["bats", "bash"]
|
|
96
|
-
files = { tags = ["bats"] }
|
|
97
|
-
output = { format = "regex", pattern = "^# (?<file>[^:]+): line (?<line>\\d+): (?<message>.*)$" }
|
|
98
|
-
summary = "Parses Bats test files while counting their test cases."
|
|
99
|
-
why = "Bats test declarations need the Bats parser before Bash can read them."
|
|
100
|
-
help = "Fix the syntax reported by Bats. Install bats-core when the command is missing."
|
|
79
|
+
summary = "Checks the syntax of Zsh scripts with `zsh -n`."
|
|
80
|
+
why = "Zsh syntax differs from Bash, so a Bash parser rejects valid Zsh programs."
|
|
101
81
|
|
|
102
82
|
[[check]]
|
|
103
|
-
name = "shellcheck"
|
|
104
|
-
example = "In a Bash script, `printf \"%s\\n\" $name` reports SC2086 because the expansion permits word splitting and globbing. Use `printf \"%s\\n\" \"$name\"` to pass the value as one argument."
|
|
105
|
-
level = "recommended"
|
|
106
|
-
stage = "commit"
|
|
107
|
-
files = { tags = ["bash", "sh", "bats"] }
|
|
108
83
|
command = ["shellcheck", "--rcfile", "{tool_file:shellcheckrc}", "--severity=style", "--check-sourced", "--format=gcc", "{files}"]
|
|
84
|
+
example = "In a Bash script, `printf \"%s\\n\" $name` reports SC2086 because the expansion permits word splitting and globbing. Use `printf \"%s\\n\" \"$name\"` to pass the value as one argument."
|
|
109
85
|
exit_codes = [1]
|
|
86
|
+
files = { tags = ["bash", "sh", "bats"] }
|
|
87
|
+
help = "Read the SC code in the finding; `gspot explain shellcheck/SC2086` links to its page. Turn one code off with `gspot ignore bash/shellcheck --rule SC2086 --reason \"<why>\"`."
|
|
88
|
+
level = "recommended"
|
|
89
|
+
name = "shellcheck"
|
|
110
90
|
output = { format = "regex", pattern = "^(?<file>[^:]+):(?<line>\\d+):(?<column>\\d+): (?:note|warning|error|style): (?<message>.*?) \\[(?<rule>SC\\d+)\\]$" }
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
91
|
+
stage = "commit"
|
|
92
|
+
summary = "Runs ShellCheck over shell scripts and sourced files, with its stable optional rules at level all."
|
|
93
|
+
why = "ShellCheck catches the quoting, globbing, and exit-status mistakes that make a script fail on the one input nobody tested."
|
|
114
94
|
|
|
115
95
|
[[check]]
|
|
116
|
-
name = "shfmt"
|
|
117
|
-
example = "In a script, `if true;then echo hello;fi` differs from shfmt output. Run the configured formatter to add the required spacing and block layout, then rerun the check."
|
|
118
|
-
level = "recommended"
|
|
119
|
-
stage = "commit"
|
|
120
|
-
files = { tags = ["bash", "sh", "bats"] }
|
|
121
96
|
command = ["shfmt", "-d", "-i", "{indent}", "-ci", "-s", "{files}"]
|
|
97
|
+
example = "`if true;then echo hello;fi` differs from the shfmt output. Run `gspot check --fix` to add the spacing and line breaks."
|
|
98
|
+
files = { tags = ["bash", "sh", "bats"] }
|
|
122
99
|
fix = ["shfmt", "-w", "-i", "{indent}", "-ci", "-s", "{files}"]
|
|
100
|
+
help = "Run `gspot check --fix` to format every script, or `shfmt -w` on one file."
|
|
101
|
+
level = "recommended"
|
|
102
|
+
name = "shfmt"
|
|
123
103
|
output = { format = "regex", pattern = "^(?:--- )?(?<file>.+?)(?:\\.orig$|:(?<line>\\d+):(?<column>\\d+): (?<message>.+)$)", message = "This script is not formatted the way shfmt formats it.", fixable = "^--- " }
|
|
124
|
-
|
|
104
|
+
stage = "commit"
|
|
105
|
+
summary = "Checks that every shell script matches the shfmt output."
|
|
125
106
|
why = "One formatter means diffs show what changed, not how someone's editor indents."
|
|
126
|
-
help = "Run gspot check --fix to format every script, or shfmt -w on one file."
|
|
127
107
|
|
|
128
108
|
[[check]]
|
|
129
|
-
|
|
130
|
-
|
|
109
|
+
example = "An executable Bash script without `set -euo pipefail` reports a finding. Put that line before the first command, and when the script defines functions, call `main` last."
|
|
110
|
+
help = "Open with the shebang and run `set -euo pipefail` first. When an executable defines functions, end it with a call to main."
|
|
131
111
|
level = "all"
|
|
112
|
+
name = "script-layout"
|
|
132
113
|
stage = "commit"
|
|
133
|
-
summary = "Checks
|
|
134
|
-
why = "A script that skips strict mode or hides its runtime fails on the machine
|
|
135
|
-
help = "Open with the shebang and run set -euo pipefail first. When an executable defines functions, end it with a call to main."
|
|
114
|
+
summary = "Checks each Bash script for a shebang, strict mode, a final `main` call when it defines functions, declarative libraries, and removed temporary files."
|
|
115
|
+
why = "A script that skips strict mode or hides its runtime fails silently on the machine that matters."
|
|
136
116
|
|
|
137
117
|
[[check]]
|
|
138
|
-
|
|
139
|
-
|
|
118
|
+
example = "`_quiet() { printf \"%s\\n\" \"Hello\"; }` with no comment above it reports a finding. Add `# _quiet: prints a greeting.` on the line above."
|
|
119
|
+
help = "Write \"# name: what it does\" above the function; when it carries doc sections, keep Globals, Arguments, Outputs, and Returns in that order."
|
|
140
120
|
level = "all"
|
|
121
|
+
name = "doc-comments"
|
|
141
122
|
stage = "commit"
|
|
142
|
-
summary = "Checks that
|
|
143
|
-
why = "
|
|
144
|
-
help = "Write \"# name: what it does\" above the function; when it carries doc sections, keep Globals, Arguments, Outputs, Returns in that order."
|
|
123
|
+
summary = "Checks that each shell function has a comment above it that says more than its name."
|
|
124
|
+
why = "Without a summary, the reader must trace the function in their head, and nobody trusts a vague one."
|
|
145
125
|
|
|
146
126
|
[[check]]
|
|
147
|
-
|
|
148
|
-
|
|
127
|
+
example = "A private function `_orphan` that no script calls reports a finding. Delete it, or record a hook that needs it with `gspot ignore` and a reason."
|
|
128
|
+
help = "Delete the function. For a required hook entry point, run `gspot ignore bash/unused-functions --paths <file> --reason <why>`."
|
|
149
129
|
level = "recommended"
|
|
150
|
-
|
|
130
|
+
name = "unused-functions"
|
|
151
131
|
runs = "scope"
|
|
132
|
+
stage = "commit"
|
|
152
133
|
summary = "Finds shell functions no script in the scope calls."
|
|
153
|
-
why = "ShellCheck
|
|
154
|
-
help = "Delete the function. For a required hook entry point, run `gspot ignore bash/unused-functions --paths <file> --reason <why>`."
|
|
134
|
+
why = "ShellCheck works on one file at a time, while this check follows calls across the whole scope."
|
|
155
135
|
|
|
156
136
|
[[check]]
|
|
157
|
-
|
|
158
|
-
|
|
137
|
+
example = "`_deaf one two` reports dead parameters when `_deaf` never reads its arguments. Drop them from the call, or use them in the function."
|
|
138
|
+
help = "Read the argument in the function, or drop it from every call."
|
|
159
139
|
level = "recommended"
|
|
160
|
-
|
|
140
|
+
name = "unread-arguments"
|
|
161
141
|
runs = "scope"
|
|
162
|
-
summary = "Finds shell functions called with arguments they never read."
|
|
163
|
-
why = "A caller that passes an argument expects it to affect the result. An unread argument misleads the caller."
|
|
164
|
-
help = "Read the argument in the function, or drop it from every call."
|
|
165
|
-
|
|
166
|
-
[[check]]
|
|
167
|
-
name = "source-order"
|
|
168
|
-
example = "A block of `source` lines that is not ordered shortest first reports the sorted order. Reorder the lines."
|
|
169
|
-
level = "all"
|
|
170
142
|
stage = "commit"
|
|
171
|
-
summary = "
|
|
172
|
-
why = "
|
|
173
|
-
help = "Reorder the source statements shortest first."
|
|
143
|
+
summary = "Finds shell functions called with arguments they never read."
|
|
144
|
+
why = "An unread argument misleads the caller, who expects it to change the result."
|
|
174
145
|
|
|
175
146
|
[[check]]
|
|
176
|
-
|
|
177
|
-
|
|
147
|
+
example = "A function over `limits.bash.function_lines` reports `function-lines` with the count and the limit. Split it by job."
|
|
148
|
+
help = "Split the script or the function, or raise the named limits.bash setting with a reason."
|
|
178
149
|
level = "all"
|
|
179
|
-
stage = "commit"
|
|
180
|
-
runs = "scope"
|
|
181
|
-
summary = "Checks that a shell function no other file calls starts with an underscore, and that an underscore function is not called from outside."
|
|
182
|
-
why = "The underscore tells a reader what the file publishes without reading every caller."
|
|
183
|
-
help = "Rename the function with a leading underscore, or drop the underscore when another file calls it."
|
|
184
|
-
|
|
185
|
-
[[check]]
|
|
186
150
|
name = "function-size"
|
|
187
|
-
example = "A function over `limits.bash.function_lines`, `limits.bash.branches`, `limits.bash.nesting`, or `limits.bash.assignments` reports the count and the ceiling under `function-lines`, `branches`, `nesting`, or `assignments`. Split the function by the job, then rerun."
|
|
188
|
-
level = "all"
|
|
189
151
|
stage = "commit"
|
|
190
152
|
summary = "Checks every shell function against the size ceilings: code lines, branches, nesting, and assignments."
|
|
191
|
-
why = "A long script holds more than one job, and
|
|
192
|
-
help = "Split the script or the function, or raise the named limits.bash setting with a reason."
|
|
153
|
+
why = "A long script holds more than one job, and many branches or assignments hide several functions under one name."
|
|
193
154
|
|
|
194
155
|
[[check]]
|
|
195
|
-
|
|
196
|
-
|
|
156
|
+
example = "A launcher that holds only `exec bash scripts/build.sh` reports a finding. Call `scripts/build.sh` directly, and delete the launcher."
|
|
157
|
+
help = "Call the target script directly and delete the redundant launcher or alias."
|
|
197
158
|
level = "all"
|
|
159
|
+
name = "forwarders"
|
|
198
160
|
stage = "commit"
|
|
199
161
|
summary = "Finds forwarding wrappers and compatibility aliases in shell scripts."
|
|
200
|
-
why = "A wrapper
|
|
201
|
-
help = "Call the target script directly and delete the redundant launcher or alias."
|
|
162
|
+
why = "A wrapper adds a second entry point, and the two drift apart."
|
|
202
163
|
|
|
203
164
|
[[check]]
|
|
204
|
-
name = "inline-scripts"
|
|
205
165
|
example = "A shell heredoc containing a Python program reports embedded code. Move the program into a `.py` file and invoke that file from the shell script."
|
|
206
|
-
level = "all"
|
|
207
|
-
stage = "commit"
|
|
208
|
-
summary = "Finds inline Python, Node, and generated-script heredocs in shell scripts."
|
|
209
|
-
why = "Code inside a heredoc is checked by nothing."
|
|
210
166
|
help = "Move the embedded code into its own file and run that."
|
|
211
|
-
|
|
212
|
-
[[check]]
|
|
213
|
-
name = "ssh-blocks"
|
|
214
|
-
example = "An anonymous SSH heredoc reports a remote-block finding. Move the remote operation into a named function with a comment that explains its purpose, then invoke that function through the supported remote call."
|
|
215
|
-
level = "all"
|
|
216
|
-
stage = "commit"
|
|
217
|
-
summary = "Checks that ssh heredocs are documented."
|
|
218
|
-
why = "A remote command block with no name is the hardest thing in a script to test or reuse."
|
|
219
|
-
help = "Move the block into a named function and write \"# name: what it does\" above it."
|
|
220
|
-
|
|
221
|
-
[[check]]
|
|
222
|
-
name = "variable-defaults"
|
|
223
|
-
example = "A default such as `${PORT:-8080}` outside a declared configuration owner reports a misplaced default. Set the default in a path listed in `architecture.roles.env` and consume the configured value elsewhere."
|
|
224
|
-
level = "all"
|
|
225
|
-
stage = "commit"
|
|
226
|
-
summary = "Finds variable defaults of the form name:-value outside the configuration owners."
|
|
227
|
-
why = "A default set in three places is three settings with one name."
|
|
228
|
-
help = "Set the default in a configuration owner named under architecture.roles.env and read the variable everywhere else."
|
|
229
|
-
|
|
230
|
-
[[check]]
|
|
231
|
-
name = "include-guards"
|
|
232
|
-
example = "A sourced configuration owner without a load guard reports a guard finding. Begin it with `[[ -n ${APP_CONFIG_READY:-} ]] && return 0`, followed by `readonly APP_CONFIG_READY=1`, before declaring its configuration."
|
|
233
167
|
level = "all"
|
|
168
|
+
name = "inline-scripts"
|
|
234
169
|
stage = "commit"
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
why = "A configuration file sourced twice runs its side effects twice."
|
|
238
|
-
help = "Open the owner with [[ -n ${NAME_READY:-} ]] && return 0 and then readonly NAME_READY=1, with a guard name no other owner uses."
|
|
170
|
+
summary = "Finds inline Python, Node, and generated-script heredocs in shell scripts."
|
|
171
|
+
why = "No check reads code inside a heredoc."
|
|
239
172
|
|
|
240
173
|
[[check]]
|
|
241
|
-
|
|
242
|
-
|
|
174
|
+
example = "`killall worker` reports `broad-kill`. Stop the one process by its ID, or ignore the rule for that script with a reason."
|
|
175
|
+
help = "Handle the failure. For required cleanup, use `gspot ignore bash/safety` with `--rule`, `--paths`, and `--reason`."
|
|
243
176
|
level = "recommended"
|
|
177
|
+
name = "safety"
|
|
244
178
|
stage = "commit"
|
|
245
|
-
summary = "Finds broad process kills and recursive deletes, except traps that remove mktemp paths. At all,
|
|
246
|
-
why = "Each of these
|
|
247
|
-
help = "Handle the failure. For required cleanup, use gspot ignore bash/safety with --rule, --paths and --reason."
|
|
248
|
-
|
|
249
|
-
[[naming.override]]
|
|
250
|
-
paths = ["**"]
|
|
251
|
-
languages = ["bash"]
|
|
252
|
-
categories = ["variables"]
|
|
253
|
-
allowed_names = ["IFS", "OPTARG", "OPTIND", "EUID", "UID", "PPID", "RANDOM", "SECONDS", "LINENO", "BASH_SOURCE", "BASH_REMATCH", "BASH_VERSION", "BASH_VERSINFO", "FUNCNAME", "PIPESTATUS", "REPLY", "CDPATH", "OLDPWD", "SHLVL", "HOSTNAME", "OSTYPE", "MACHTYPE", "COLUMNS", "LINES"]
|
|
254
|
-
reason = "The shell defines these variable names."
|
|
255
|
-
|
|
256
|
-
[[naming.override]]
|
|
257
|
-
paths = ["**"]
|
|
258
|
-
languages = ["bash"]
|
|
259
|
-
categories = ["functions", "variables", "constants", "methods", "attributes"]
|
|
260
|
-
ignored_prefix = "^_+"
|
|
261
|
-
reason = "A leading underscore marks a private shell declaration."
|
|
179
|
+
summary = "Finds broad process kills and recursive deletes, except traps that remove mktemp paths. At level all, also reports discarded failures."
|
|
180
|
+
why = "Each of these lets a script silently destroy something by mistake."
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# Bash
|
|
2
2
|
|
|
3
|
+
Read this rule when you write or change a shell script.
|
|
4
|
+
|
|
3
5
|
## Bash scope
|
|
4
6
|
|
|
5
7
|
<!-- level: all -->
|
|
6
8
|
|
|
7
|
-
Bash
|
|
8
|
-
small scripts with explicit inputs
|
|
9
|
+
Use Bash as glue code that runs commands, not for application logic. Prefer
|
|
10
|
+
small scripts with explicit inputs and outputs and clear failure behavior.
|
|
9
11
|
Treat every path, argument, environment value, command output, and user input as unsafe until
|
|
10
12
|
quoted, validated, or parsed by a structured tool. A script that builds, deploys, deletes, or
|
|
11
13
|
publishes artifacts makes its inputs, effects, and failure behavior explicit.
|
|
12
14
|
|
|
13
|
-
`set -euo pipefail` does not replace checks around dangerous commands.
|
|
14
|
-
scripts short. Put longer logic in a script file the checks can lint.
|
|
15
|
+
`set -euo pipefail` does not replace checks around dangerous commands.
|
|
15
16
|
|
|
16
17
|
## Bash tasks
|
|
17
18
|
|
|
@@ -20,7 +21,7 @@ scripts short. Put longer logic in a script file the checks can lint.
|
|
|
20
21
|
Use Bash when the script mostly calls other command-line tools or wires together
|
|
21
22
|
installation, lint, build, runtime, publication, or cleanup steps. Use it to validate the
|
|
22
23
|
environment and dispatch to project commands, or for file movement, process checks,
|
|
23
|
-
and retry loops. Do not use Bash for
|
|
24
|
+
and retry loops. Do not use Bash for complex text parsing. Do not use it
|
|
24
25
|
for JSON, YAML, XML, or HTML transformations beyond extraction with a dedicated
|
|
25
26
|
parser. Large mutable data structures, long-lived daemons, high-performance work,
|
|
26
27
|
security-sensitive parsing of untrusted input, and behavior that needs typed contracts belong
|
|
@@ -40,22 +41,22 @@ for cleanup that can run safely on the relevant exit path.
|
|
|
40
41
|
|
|
41
42
|
<!-- level: all -->
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
End an executable script with a meaningful status. A final message, a false condition, or an
|
|
45
|
+
optional cleanup check must not set the exit status by accident. Use
|
|
45
46
|
an explicit `exit 0` only when the final command's status differs from the program result and
|
|
46
47
|
prior checks establish success.
|
|
47
48
|
|
|
48
49
|
## Options
|
|
49
50
|
|
|
50
|
-
Use `errexit` as a backstop
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
Use `errexit` as a backstop, not as control flow. It does not stop the script inside conditionals, pipelines,
|
|
52
|
+
command substitutions, subshells, and some functions. Check the status of `cd`, `rm`, builds, publishing, uploads,
|
|
53
|
+
and destructive commands explicitly. Do not toggle
|
|
53
54
|
options around a small operation without restoring the prior state. Do not change `IFS` as a
|
|
54
55
|
strict-mode ritual; set it locally where reading or joining data requires it. Keep `set -x`
|
|
55
56
|
out of committed code.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
Put explicit checks inside a function called in `if`, `while`, `&&`, or `||`, because `errexit`
|
|
59
|
+
does not stop it there:
|
|
59
60
|
|
|
60
61
|
```bash
|
|
61
62
|
# cleanup: Removes the contents of the target directory.
|
|
@@ -67,16 +68,16 @@ cleanup() {
|
|
|
67
68
|
}
|
|
68
69
|
```
|
|
69
70
|
|
|
70
|
-
Process substitution hides producer failures
|
|
71
|
-
temporary file, use `PIPESTATUS`, or call
|
|
72
|
-
readers such as `head` and `grep -q` can make the producer
|
|
73
|
-
|
|
71
|
+
Process substitution hides producer failures. When the producer status matters, write to a checked
|
|
72
|
+
temporary file, use `PIPESTATUS`, or call it separately. Under `pipefail`, short
|
|
73
|
+
readers such as `head` and `grep -q` can make the producer receive SIGPIPE and fail the pipeline.
|
|
74
|
+
Consume input predictably or handle the known status. Under `nounset`, use `${name:-}` where
|
|
74
75
|
the public interface accepts an unset variable and `${name:?message}` for a required value at a clear
|
|
75
76
|
boundary. Use `${1:-}` where callers can omit an argument.
|
|
76
77
|
|
|
77
78
|
## Output, logging, and errors
|
|
78
79
|
|
|
79
|
-
Write
|
|
80
|
+
Write messages with `printf`, not `echo`. Return or exit with the intended status: printing
|
|
80
81
|
an error does not make a command fail.
|
|
81
82
|
|
|
82
83
|
```bash
|
|
@@ -104,3 +105,64 @@ log_status() {
|
|
|
104
105
|
|
|
105
106
|
A public, sourced, complex, or risky function carries the full header with `Globals`,
|
|
106
107
|
`Arguments`, `Outputs`, and `Returns` sections.
|
|
108
|
+
|
|
109
|
+
## Naming
|
|
110
|
+
|
|
111
|
+
### Bash case rules
|
|
112
|
+
|
|
113
|
+
<!-- level: all -->
|
|
114
|
+
|
|
115
|
+
- Executable scripts use `.sh` when invoked through a task runner or build
|
|
116
|
+
rules.
|
|
117
|
+
- Omit the extension on an executable script only when users invoke it
|
|
118
|
+
as a command on `PATH`.
|
|
119
|
+
- Give sourced libraries the `.sh` extension and no executable permission.
|
|
120
|
+
- Numbered pipeline steps use `NN-description.sh`, such as `01-install.sh`; the naming check strips
|
|
121
|
+
the numeric prefix before it matches the stem.
|
|
122
|
+
- Functions and mutable variables use `lower_snake_case`.
|
|
123
|
+
- Use `::` in package-like function prefixes only when a script family already
|
|
124
|
+
follows that convention.
|
|
125
|
+
|
|
126
|
+
| Avoid | Prefer | Meaning |
|
|
127
|
+
| ------------------ | -------------------------- | -------------------- |
|
|
128
|
+
| `DeployScript.sh` | `deploy-api.sh` | API deployment. |
|
|
129
|
+
| `deploy_script.sh` | `upload-storage-assets.sh` | Storage upload. |
|
|
130
|
+
| `helpers.sh` | `archive-reports` | A command on `PATH`. |
|
|
131
|
+
|
|
132
|
+
Name a deployment function `deploy_api` and its local target
|
|
133
|
+
`target_environment`.
|
|
134
|
+
|
|
135
|
+
### Bash variables
|
|
136
|
+
|
|
137
|
+
<!-- level: all -->
|
|
138
|
+
|
|
139
|
+
- Name each loop variable after its item.
|
|
140
|
+
- Name a temporary path after what it holds, such as `download_file` or `work_dir`; `tmp` and
|
|
141
|
+
`temp` say nothing.
|
|
142
|
+
- Avoid vague names when the domain has a term.
|
|
143
|
+
- Avoid reserved and special shell names for unrelated values.
|
|
144
|
+
|
|
145
|
+
Use `migration_file` when iterating migration paths and `report_output` for
|
|
146
|
+
captured report text. Check the report command's exit status before printing
|
|
147
|
+
the captured output. A loop variable such as `i` does not describe a file.
|
|
148
|
+
|
|
149
|
+
### Bash functions
|
|
150
|
+
|
|
151
|
+
<!-- level: all -->
|
|
152
|
+
|
|
153
|
+
- Give a function with side effects a verb-phrase name.
|
|
154
|
+
- Name a function that writes to `stdout` for the data it prints.
|
|
155
|
+
- Do not name scripts or functions after shell builtins or common commands.
|
|
156
|
+
- Do not make function names so generic that logs and stack traces lose context.
|
|
157
|
+
|
|
158
|
+
Name a port validator `validate_port`. Avoid `test`, which shadows a shell builtin, and
|
|
159
|
+
generic names such as `run` or `process`.
|
|
160
|
+
|
|
161
|
+
### Files
|
|
162
|
+
|
|
163
|
+
<!-- level: all -->
|
|
164
|
+
|
|
165
|
+
- Put a related script family in its own subfolder, and give its files role names such as
|
|
166
|
+
`main.sh`, `state.sh`, `query.sh`, or `report.sh`.
|
|
167
|
+
- Hook families may use a dynamic shared prefix when an external interface owns those
|
|
168
|
+
filenames.
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# Bash Language
|
|
2
2
|
|
|
3
|
+
Read this rule when you write Bash functions, variables, quoting, or loops.
|
|
4
|
+
|
|
3
5
|
## Functions
|
|
4
6
|
|
|
5
|
-
Declare function-local variables with `local
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
Declare function-local variables with `local`. When the exit code of a command substitution
|
|
8
|
+
matters, assign it apart from the declaration: `local`, `declare`, `readonly`, and `export`
|
|
9
|
+
report their own status:
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
local output
|
|
11
13
|
output="$(some_command)" || return 1
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
Preserve the documented status contract: a predicate may
|
|
15
|
-
|
|
16
|
+
Preserve the documented status contract: a predicate may return 1 for false, distinct from an
|
|
17
|
+
execution error.
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
20
|
# report_unreadable_files: Reports each unreadable input and fails if any input is unreadable.
|
|
@@ -35,16 +37,18 @@ report_unreadable_files() {
|
|
|
35
37
|
|
|
36
38
|
<!-- level: all -->
|
|
37
39
|
|
|
38
|
-
Use `name() { ... }` for a function that runs in the
|
|
39
|
-
`name() ( ... )`,
|
|
40
|
+
Use `name() { ... }` for a function that runs in the current shell, and a subshell body,
|
|
41
|
+
`name() ( ... )`, to isolate state and traps from the caller on purpose.
|
|
42
|
+
|
|
43
|
+
Every executable defines `main` and calls it last.
|
|
40
44
|
|
|
41
45
|
## Variables and constants
|
|
42
46
|
|
|
43
47
|
Mark constants `readonly` immediately after assignment, and
|
|
44
|
-
`export` only what child processes need.
|
|
45
|
-
`
|
|
48
|
+
`export` only what child processes need. Do not overwrite `PATH`, `HOME`, `IFS`, `CDPATH`,
|
|
49
|
+
`SHELL`, `PWD`, or `BASH_ENV` without a clear need. Never export `CDPATH`.
|
|
46
50
|
|
|
47
|
-
Compute a
|
|
51
|
+
Compute a folder constant with `CDPATH='' cd -- <path> && pwd -P` and add a failure path: `|| exit 1` in an
|
|
48
52
|
entrypoint and `|| return 1` in a library. Use `$HOME`, not a quoted `~`, inside paths, and
|
|
49
53
|
assign a home-relative value before exporting it. Quote array elements passed to `unset -v`.
|
|
50
54
|
|
|
@@ -57,9 +61,9 @@ export PROJECT_ROOT
|
|
|
57
61
|
|
|
58
62
|
## Quoting and expansion
|
|
59
63
|
|
|
60
|
-
Forward arguments with `"$@"`, and use `$*` only to join
|
|
61
|
-
purpose.
|
|
62
|
-
output never supplies an unquoted argument list
|
|
64
|
+
Forward arguments with `"$@"`, and use `$*` only to join them into one string on
|
|
65
|
+
purpose. Use single quotes for literals, double for expansions. Command substitution
|
|
66
|
+
output never supplies an unquoted argument list. Word splitting never parses data. Use functions,
|
|
63
67
|
not aliases. Parameter expansion replaces external tools for string work:
|
|
64
68
|
|
|
65
69
|
```bash
|
|
@@ -70,7 +74,7 @@ base="${filename%.tar.gz}"
|
|
|
70
74
|
## Arrays and argument lists
|
|
71
75
|
|
|
72
76
|
Command arguments live in arrays, expanded with `"${array[@]}"` and never populated from raw
|
|
73
|
-
`$(...)`. Read lines with a `while read` loop or `readarray`, and
|
|
77
|
+
`$(...)`. Read lines with a `while read` loop or `readarray`, and filenames from `NUL`-delimited streams:
|
|
74
78
|
|
|
75
79
|
```bash
|
|
76
80
|
declare -a archive_args
|
|
@@ -86,9 +90,9 @@ Do not use arrays to imitate nested data structures. A need for those structures
|
|
|
86
90
|
|
|
87
91
|
## Conditionals and arithmetic
|
|
88
92
|
|
|
89
|
-
`[[ ... ]]` holds string, pattern, and file tests.
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
`[[ ... ]]` holds string, pattern, and file tests. With `==`, quote the right-hand side to
|
|
94
|
+
compare equality when the value may hold glob characters, and leave it unquoted only for
|
|
95
|
+
pattern matching. Store a regular expression in a variable and use it
|
|
92
96
|
unquoted with `=~`. `-z` and `-n` test for emptiness. Numeric comparisons live in `(( ... ))` after validation of untrusted numbers; `<` and
|
|
93
97
|
`>` inside `[[ ... ]]` compare strings.
|
|
94
98
|
|
|
@@ -104,8 +108,8 @@ zero, so under `errexit` increment with `retry_count=$(( retry_count + 1 ))`. Un
|
|
|
104
108
|
strings never enter `(( ... ))`, `$(( ... ))`, `[[ value -gt n ]]`, array indices, or
|
|
105
109
|
arithmetic `for` expressions: validate with a pattern first. `10#${value}` converts unsigned
|
|
106
110
|
base-10 strings; a signed value needs `$(( ${value%%[!+-]*}10#${value#[-+]} ))`. Call `date`
|
|
107
|
-
once when several fields describe one instant
|
|
108
|
-
|
|
111
|
+
once when several fields describe one instant. Compute a redirection path before the command
|
|
112
|
+
when its expression mutates a variable.
|
|
109
113
|
|
|
110
114
|
```bash
|
|
111
115
|
if [[ ! "${port}" =~ ^[0-9]{1,5}$ ]]; then
|
|
@@ -142,8 +146,8 @@ done <"${file_list}"
|
|
|
142
146
|
`IFS= read -r` prevents trimming and backslash handling. Do not save and restore `IFS` with
|
|
143
147
|
`old_ifs="${IFS}"`, which loses the distinction between unset and empty; use a function-local
|
|
144
148
|
`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
|
|
146
|
-
application code with a real parser, never `IFS=, read`.
|
|
149
|
+
keeps its trailing empty field only with an appended delimiter. Parse general CSV in
|
|
150
|
+
application code with a real parser, never with `IFS=, read`.
|
|
147
151
|
|
|
148
152
|
```bash
|
|
149
153
|
# join_path_parts: Joins the arguments with slashes.
|
|
@@ -155,10 +159,10 @@ join_path_parts() {
|
|
|
155
159
|
|
|
156
160
|
## Paths, globs, and file names
|
|
157
161
|
|
|
158
|
-
Quote paths, put `--` before
|
|
162
|
+
Quote paths, put `--` before them where the command supports it, prefer globs with
|
|
159
163
|
an explicit prefix such as `./*.sql`, and never filter filenames with `grep`. Filenames can
|
|
160
164
|
hold spaces, newlines, quotes, brackets, and leading dashes. Never assume the current
|
|
161
|
-
|
|
165
|
+
folder; compute it, and check `cd` explicitly. A broken symlink needs `-L` beside `-e`
|
|
162
166
|
when existence matters, and a basename pattern reads `"${path##*/}"`. Handle the no-match
|
|
163
167
|
glob deliberately, either with `[[ -e ${file} ]] || continue` or with `nullglob` scoped to a
|
|
164
168
|
subshell:
|
|
@@ -190,9 +194,9 @@ content="${content_with_sentinel%x}"
|
|
|
190
194
|
|
|
191
195
|
## Pipelines and redirection
|
|
192
196
|
|
|
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`.
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
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`. Never
|
|
198
|
+
read from and write to the same file in one pipeline. Rewrite it through a temporary file
|
|
199
|
+
and an atomic rename. Parallel `xargs` jobs do not produce ordered, unmixed
|
|
196
200
|
output: write per-job files and combine them, or use a tool that serializes. Check a command
|
|
197
201
|
directly or capture its status in a named variable rather than `(( ! $? )) || die`.
|
|
198
202
|
|