Shell script¶
Directory layout¶
For builds that involve independent command-line tools, use Make, and follow DataMade’s Making Data Guidelines and Clark Grubb’s Makefile Style Guide, for example: standard_profile_template
If a repository has scripts to set itself up and/or update itself, follow GitHub’s Scripts to Rule Them All, for example: deploy and standard_profile_template
Filename conventions¶
sh and bash scripts should use the .sh extension, unless they are in a script/ directory.
Shell options¶
Start shell scripts with set -euo pipefail. If the script explicitly handles unset variables, omit -u. To see which command failed due to the -e option, add -x.
See also
Code style¶
Check /bin/sh scripts using checkbashisms.
Check shell scripts using shellcheck.
Style shell scripts using shfmt: for example, shfmt -w -i 4 -sr (shfmt -f .).
Use:
shinstead ofbash, where possible. Bash is needed for:Shell Parameter Expansion (
//,##,%%, etc.)Process Substitution (
<())
[ ]instead oftest[ ]instead of[[ ]]in Bash, unless required$NAMEinstead of${NAME}, unless followed by a word characterSubshells to temporarily change directory, for example:
( cd subdir/ mv x.txt y.txt )
Instead of:
cd subdir/ mv x.txt y.txt cd .. # AVOID
And:
Avoid
set -xin scripts run by continuous integration, because it will expand any secret variables
Continuous integration¶
Create a .github/workflows/shell.yml file. As a base, use:
name: Lint Shell
on: [push, pull_request]
jobs:
build:
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: |
sudo apt update
sudo apt install devscripts shellcheck shfmt
- run: checkbashisms $(shfmt -f .)
- run: shellcheck $(shfmt -f .)
- run: shfmt -d -i 4 -sr $(shfmt -f .)
Tip
In most cases, you can reuse the shell workflow. For example:
jobs:
lint:
uses: open-contracting/.github/.github/workflows/shell.yml@main
permissions:
contents: read
with:
ignore: file.sh
Maintenance¶
Find repositories with shell scripts but without shell.yml files:
find . \( -path '*/script/*' -o -name '*.sh' \) ! -path '*/.mypy_cache/*' ! -path '*/node_modules/*' ! -path '*/vendor/*' -exec bash -c 'if [[ -z $(find $(echo {} | cut -d/ -f2) -name shell.yml) ]]; then echo {}; fi' \;