Shell overview
Pants's support for Shellcheck, shfmt, and shUnit2.
Pants integrates with these tools to empower you to follow best practices with your Shell scripts:
- Shellcheck: lint for common Shell mistakes.
- shfmt: autoformat Shell code so that you can instead focus on the logic.
- shUnit2: write light-weight unit tests for your Shell code.
Pants installs these tools deterministically and integrates them into the workflows you already use: pants fmt
, pants lint
, and pants test
.
Initial setup: add shell_sources
targets
Pants uses shell_source
and shunit2_test
targets to know which Shell files you want to operate on and to set any metadata.
To reduce boilerplate, the shell_sources
target generates a shell_source
target for each file in its sources
field, and shunit2_tests
generates a shunit2_test
target for each file in its sources
field.
shell_sources(name="lib", sources=["deploy.sh", "changelog.sh"])
shell_tests(name="tests", sources=["changelog_test.sh"])
# Spiritually equivalent to:
shell_source(name="deploy", source="deploy.sh")
shell_source(name="changelog", source="changelog.sh")
shell_test(name="changelog_test", source="changelog_test.sh")
# Thanks to the default `sources` values, spiritually equivalent to:
shell_sources(name="lib")
shell_tests(name="tests")
First, activate the Shell backend in your pants.toml
:
[GLOBAL]
backend_packages = [
"pants.backend.shell",
]
Then, run pants tailor ::
to generate BUILD files:
$ pants tailor ::
Created scripts/BUILD:
- Add shell_sources target scripts
Created scripts/subdir/BUILD:
- Add shell_sources target subdir
You can also manually add targets, which is necessary if you have any scripts that don't end in .sh
:
shell_source(name="script_without_a_extension", source="script_without_an_extension")
Pants will infer dependencies by looking for imports like source script.sh
and . script.sh
. You can check that the correct dependencies are inferred by running pants dependencies path/to/script.sh
and pants dependencies --transitive path/to/script.sh
.
Normally, Pants will not understand dynamic sources, e.g. using variable expansion. However, Pants uses Shellcheck for parsing, so you can use Shellcheck's syntax to give a hint to Pants:
another_script="dir/some_script.sh"
# Normally Pants couldn't infer this, but we can give a hint like this:
# shellcheck source=dir/some_script.sh
source "${another_script}"
Alternatively, you can explicitly add dependencies
in the relevant BUILD file.
shell_sources(dependencies=["path/to:shell_source_tgt"])
shfmt autoformatter
To activate, add this to your pants.toml
:
[GLOBAL]
backend_packages = [
"pants.backend.shell",
"pants.backend.shell.lint.shfmt",
]
Make sure that you also have set up shell_source
/shell_sources
or shunit2_test
/shunit2_tests
targets so that Pants knows to operate on the relevant files.
Now you can run pants fmt
and pants lint
:
$ pants lint scripts/my_script.sh
13:05:56.34 [WARN] Completed: lint - shfmt failed (exit code 1).
--- scripts/my_script.sh.orig
+++ scripts/my_script.sh
@@ -9,7 +9,7 @@
set -eo pipefail
-HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && \
+HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" &&
pwd)
𐄂 shfmt failed.
Use pants fmt lint dir:
to run on all files in the directory, and pants fmt lint dir::
to run on all files in the directory and subdirectories.
Pants will automatically include any relevant .editorconfig
files in the run. You can also pass command line arguments with --shfmt-args='-ci -sr'
or permanently set them in pants.toml
:
[shfmt]
args = ["-i 2", "-ci", "-sr"]
Temporarily disable shfmt with --shfmt-skip
:
pants --shfmt-skip fmt ::
Only run shfmt with --lint-only
and --fmt-only
:
pants fmt --only=shfmt ::
Normally, Pants runs formatters sequentially so that it can pipe the results of one formatter into the next. However, Pants will run shfmt in parallel to formatters for other languages, like Python, because shfmt does not operate on those languages.
You can see this concurrency through Pants's dynamic UI.
Shellcheck linter
To activate, add this to your pants.toml
:
[GLOBAL]
backend_packages = [
"pants.backend.shell",
"pants.backend.shell.lint.shellcheck",
]
Make sure that you also have set up shell_source
/ shell_sources
or shunit2_test
/ shunit_tests
targets so that Pants knows to operate on the relevant files.
Now you can run pants lint
:
$ pants lint scripts/my_script.sh
13:09:10.49 [WARN] Completed: lint - Shellcheck failed (exit code 1).
In scripts/my_script.sh line 12:
HERE=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)
^--------------------------^ SC2046: Quote this to prevent word splitting.
^---------------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
...