Repository: luizm/action-sh-checker
Branch: master
Commit: 883217215b11
Files: 7
Total size: 13.9 KB
Directory structure:
gitextract_prljnb1b/
├── .github/
│ └── workflows/
│ └── ci.yml
├── Dockerfile
├── LICENSE
├── README.md
├── action.yml
├── entrypoint.sh
└── upgrade.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
---
name: Build
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: sh-checker
uses: ./. # Use the local action, at the current version
with:
sh_checker_comment: true
sh_checker_only_diff: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: Dockerfile
================================================
FROM alpine:3.18.4
LABEL "name"="sh-checker"
LABEL "maintainer"="Luiz Muller <contact@luizm.dev>"
ARG shfmt_version=3.12.0
ARG shellcheck_version=0.11.0
ARG gh_version=2.37.0
RUN apk add --no-cache bash git jq curl checkbashisms xz \
&& apk add --no-cache --virtual .build-deps tar \
&& wget "https://github.com/mvdan/sh/releases/download/v${shfmt_version}/shfmt_v${shfmt_version}_linux_amd64" -O /usr/local/bin/shfmt \
&& chmod +x /usr/local/bin/shfmt \
&& wget "https://github.com/koalaman/shellcheck/releases/download/v${shellcheck_version}/shellcheck-v${shellcheck_version}.linux.x86_64.tar.xz" -O- | tar xJ -C /usr/local/bin/ --strip-components=1 --wildcards '*/shellcheck' \
&& chmod +x /usr/local/bin/shellcheck \
&& curl -L https://github.com/cli/cli/releases/download/v${gh_version}/gh_${gh_version}_linux_amd64.tar.gz | tar xz -C /usr/local/ --strip-components=1 \
&& apk del --no-cache .build-deps \
&& rm -rf /tmp/*
# https://github.com/actions/runner-images/issues/6775#issuecomment-1410270956
RUN git config --system --add safe.directory /github/workspace
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Luiz Muller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# sh-checker
[](https://github.com/luizm/action-sh-checker/actions/workflows/ci.yml)
A [GitHub action](https://docs.github.com/en/free-pro-team@latest/actions) that performs static analysis of shell scripts using [shellcheck](https://github.com/koalaman/shellcheck), [shfmt](https://github.com/mvdan/sh) and [checkbashisms](https://linux.die.net/man/1/checkbashisms)

## Usage
Job example to check all sh files but ignore the directory `.terraform` and file `dir/example.sh`
```yaml
name: example
on:
- pull_request
jobs:
sh-checker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run the sh-checker
uses: luizm/action-sh-checker@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHELLCHECK_OPTS: -e SC1004 # exclude some shellcheck warnings.
SHFMT_OPTS: -s # arguments to shfmt.
with:
sh_checker_comment: true
sh_checker_exclude: ".terraform ^dir/example.sh"
```
### Environment Variables
`SHELLCHECK_OPTS`: Used to specify shellcheck arguments.
`SHFMT_OPTS`: Used to specify shfmt arguments.
### Inputs
`sh_checker_only_diff`: (optional) Only check the files that were changed in the pull request. Default is to check all files in the repo.
`sh_checker_exclude`: (optional) Directory or file name that doesn't need to be checked.
`sh_checker_comment`: (optional) If true, it will show the errors as commentaries in the pull requests. Default is false.
`sh_checker_shfmt_disable`: (optional) If true, it will skip shfmt. Default is false.
`sh_checker_shellcheck_disable`: (optional) If true, it will skip shellcheck. Default is false.
`sh_checker_checkbashisms_enable`: (optional) If true, run checkbashisms tool against scripts. Default is false.
### Secrets
`GITHUB_TOKEN`: The GitHub API token used to post comments to pull requests. Required only if `sh_checker_only_diff` or `sh_checker_comment` is set to true.
================================================
FILE: action.yml
================================================
name: "sh-checker"
description: "Run shellcheck and/or shfmt on all shell script files and include commentaries, there is a exclude parameter"
author: "luizm"
branding:
icon: "terminal"
color: "black"
inputs:
sh_checker_exclude:
description: "Directory or file name that doesn't need to be checked"
required: false
sh_checker_shfmt_disable:
description: "If true, it will skip the shfmt. Default is false"
required: false
sh_checker_shellcheck_disable:
description: "If true, it will skip the shellcheck. Default is false"
required: false
sh_checker_checkbashisms_enable:
description: "If true, run checkbashisms tool against scripts. Default is false"
required: false
default: "false"
sh_checker_comment:
description: "If true, it will show the errors as commentaries in the pull requests. Default is false"
required: false
sh_checker_only_diff:
description: "If true, run only on files changed in the PR branch. Default is false"
required: false
runs:
using: "docker"
image: "Dockerfile"
================================================
FILE: entrypoint.sh
================================================
#!/usr/bin/env bash
cd "$GITHUB_WORKSPACE" || {
printf 'Directory not found: "%s"\n' "$GITHUB_WORKSPACE"
exit 1
}
SHELLCHECK_DISABLE=0
SHFMT_DISABLE=0
SH_CHECKER_COMMENT=0
CHECKBASHISMS_ENABLE=0
SH_CHECKER_ONLY_DIFF=0
shopt -s nocasematch
if [[ "${INPUT_SH_CHECKER_SHELLCHECK_DISABLE}" =~ ^(1|true|on|yes)$ ]]; then
SHELLCHECK_DISABLE=1
fi
if [[ "${INPUT_SH_CHECKER_SHFMT_DISABLE}" =~ ^(1|true|on|yes)$ ]]; then
SHFMT_DISABLE=1
fi
if [[ "${INPUT_SH_CHECKER_COMMENT}" =~ ^(1|true|on|yes)$ ]]; then
SH_CHECKER_COMMENT=1
fi
if [[ "${INPUT_SH_CHECKER_CHECKBASHISMS_ENABLE}" =~ ^(1|true|on|yes)$ ]]; then
CHECKBASHISMS_ENABLE=1
fi
if [[ "${INPUT_SH_CHECKER_ONLY_DIFF}" =~ ^(1|true|on|yes)$ ]]; then
SH_CHECKER_ONLY_DIFF=1
fi
if ((SHELLCHECK_DISABLE == 1 && SHFMT_DISABLE == 1 && CHECKBASHISMS_ENABLE != 1)); then
echo "All checks are disabled: \`sh_checker_shellcheck_disable\` and \`sh_checker_shfmt_disable\` are both set to 1/true."
fi
# Internal functions
_show_sh_files() {
# Store the array of files to check in sh_files
# using a global, as returning arrays in bash is ugly
# setting IFS to \n allows for spaces in file names:
if ((SH_CHECKER_ONLY_DIFF == 1)); then
# Compute the intersection of all shell scripts in the repo and files changes on the PR branch
if [[ "$GITHUB_REF" =~ ^refs/pull/ ]]; then
# The `on: pull_request` trigger does not give branch information, so we need to supply the PR number to gh
# See https://frontside.com/blog/2020-05-26-github-actions-pull_request/
# and https://docs.github.com/en/actions/learn-github-actions/variables
pr_number="$(echo "$GITHUB_REF" | cut -d/ -f3)"
else
pr_number="" # have gh figure out the PR number from the branch name
fi
IFS=$'\n' mapfile -t sh_files < <(sort <(shfmt -f .) <(gh pr diff "$pr_number" --name-only) | uniq -d)
echo "Checking only the shell script(s) changed in the PR branch:"
printf '"%s"\n' "${sh_files[@]}"
else
IFS=$'\n' mapfile -t sh_files < <(shfmt -f .)
fi
if [ -z "$INPUT_SH_CHECKER_EXCLUDE" ]; then
return 0
fi
OLDIFS="$IFS"
IFS=$' \t\n' read -d '' -ra excludes <<<"$INPUT_SH_CHECKER_EXCLUDE"
IFS=$'\n'
sh_all=("${sh_files[@]}")
sh_files=()
excluded=()
local sh exclude
for sh in "${sh_all[@]}"; do
for exclude in "${excludes[@]}"; do
grep -q -E "$exclude" <<<"$sh" || continue
excluded+=("$sh")
continue 2
done
sh_files+=("$sh")
done
if (("${#excluded[@]}" != 0)); then
printf 'The following %d shell script(s) will not be checked:\n' "${#excluded[@]}"
printf '"%s"\n' "${excluded[@]}"
fi
IFS="$OLDIFS"
}
_comment_on_github() {
local content
IFS= read -r -d '' content <<EOF
#### \`sh-checker report\`
To get the full details, please check in the [job]("https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID") output.
<details>
<summary>shellcheck errors</summary>
\`\`\`
$1
\`\`\`
</details>
<details>
<summary>shfmt errors</summary>
\`\`\`
$2
\`\`\`
</details>
EOF
local -r payload=$(jq -R --slurp '{body: .}' <<<"$content")
local -r comment_url=$(jq -r .pull_request.comments_url <"$GITHUB_EVENT_PATH")
echo "Commenting on the pull request"
curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data @- "$comment_url" <<<"$payload"
}
_show_sh_files
((${#sh_files[@]} == 0)) && {
if ((SH_CHECKER_ONLY_DIFF == 1)); then
echo "No shell scripts were changed."
exit 0
fi
if [ -n "$INPUT_SH_CHECKER_EXCLUDE" ]; then
if ((${#excluded[@]} == ${#sh_all[@]})); then
printf 'All %d shell script(s) have been excluded per your sh_checker_exclude setting:\n' "${#sh_all[@]}"
IFS=$' \t\n' printf '"%s"\n' "${excludes[@]}"
exit 0
fi
fi
echo "No shell scripts were found in this repository. Please check your settings."
exit 0
}
# Validate sh files
shellcheck_code=0
checkbashisms_code=0
shfmt_code=0
exit_code=0
shellcheck_error='shellcheck checking is disabled.'
shfmt_error='shfmt checking is disabled.'
if ((SHELLCHECK_DISABLE != 1)); then
printf "Validating %d shell script(s) using 'shellcheck %s':\\n" "${#sh_files[@]}" "$SHELLCHECK_OPTS"
IFS=$' \t\n' read -d '' -ra args <<<"$SHELLCHECK_OPTS"
shellcheck_output="$(shellcheck "${args[@]}" "${sh_files[@]}" 2>&1)"
shellcheck_code=$?
if ((shellcheck_code == 0)); then
printf -v shellcheck_error "'shellcheck %s' found no issues.\\n" "$SHELLCHECK_OPTS"
else
# .shellcheck returns 0-4: https://github.com/koalaman/shellcheck/blob/dff8f9492a153b4ad8ac7d085136ce532e8ea081/shellcheck.hs#L191
exit_code=$shellcheck_code
IFS= read -r -d '' shellcheck_error <<EOF
'shellcheck $SHELLCHECK_OPTS' returned error $shellcheck_code finding the following syntactical issues:
----------
$shellcheck_output
----------
You can address the above issues in one of three ways:
1. Manually correct the issue in the offending shell script;
2. Disable specific issues by adding the comment:
# shellcheck disable=NNNN
above the line that contains the issue, where NNNN is the error code;
3. Add '-e NNNN' to the SHELLCHECK_OPTS setting in your .yml action file.
EOF
fi
printf '%s' "$shellcheck_error"
fi
if ((SHFMT_DISABLE != 1)); then
printf "Validating %d shell script(s) using 'shfmt %s':\\n" "${#sh_files[@]}" "$SHFMT_OPTS"
IFS=$' \t\n' read -d '' -ra args <<<"$SHFMT_OPTS"
# Error with a diff when the formatting differs
args+=('-d')
# Disable colorization of diff output
export NO_COLOR=1
shfmt_output="$(shfmt "${args[@]}" "${sh_files[@]}" 2>&1)"
shfmt_code=$?
if ((shfmt_code == 0)); then
printf -v shfmt_error "'shfmt %s' found no issues.\\n" "$SHFMT_OPTS"
else
# shfmt returns 0 or 1: https://github.com/mvdan/sh/blob/dbbad59b44d586c0f3d044a3820c18c41b495e2a/cmd/shfmt/main.go#L72
((exit_code |= 8))
IFS= read -r -d '' shfmt_error <<EOF
'shfmt $SHFMT_OPTS' returned error $shfmt_code finding the following formatting issues:
----------
$shfmt_output
----------
You can reformat the above files to meet shfmt's requirements by typing:
shfmt $SHFMT_OPTS -w filename
EOF
fi
printf '%s' "$shfmt_error"
fi
if ((CHECKBASHISMS_ENABLE == 1)); then
printf '\n\nValidating %d shell script(s) files using checkbashisms:\n' "${#sh_files[@]}"
checkbashisms "${sh_files[@]}"
checkbashisms_code=$?
if ((checkbashisms_code == 0)); then
printf '\ncheckbashisms found no issues.\n'
else
printf '\ncheckbashisms returned error %d finding the bashisms listed above.\n' "$checkbashisms_code"
if ((checkbashisms_code == 4)); then
# see https://github.com/duggan/shlint/blob/0fcd979319e3f37c2cd53ccea0b51e16fda710a1/lib/checkbashisms#L489
printf "\\nIgnoring the spurious non-issue titled 'could not find any possible bashisms in bash script'\\n"
else
# checkbashisms returns 0-3: https://linux.die.net/man/1/checkbashisms
((exit_code |= (checkbashisms_code << 4)))
fi
fi
fi
if ((shellcheck_code != 0 || shfmt_code != 0)); then
if [[ "$GITHUB_EVENT_NAME" == "pull_request" || "$GITHUB_EVENT_NAME" == "pull_request_target" ]] && ((SH_CHECKER_COMMENT == 1)); then
_comment_on_github "$shellcheck_error" "$shfmt_error"
fi
fi
if ((exit_code == 0)); then
printf '\nNo issues found in the %d shell script(s) scanned :)\n' "${#sh_files[@]}"
fi
# shellcheck disable=SC2086
exit $exit_code
================================================
FILE: upgrade.sh
================================================
#!/usr/bin/env bash
# shellcheck disable=SC2312 # (info): Consider invoking this command separately to avoid masking its return value (or use '|| true' to ignore).
set -e
if command -v curl >/dev/null; then
# -sq == --silent --disable
dl="curl -sq"
else
# -q0 == --quiet --output-document
dl='wget --no-config -qO -'
fi
ALPINE_VER=$(${dl} 'https://registry.hub.docker.com/v2/repositories/library/alpine/tags/' | jq -r '.results[1].name')
GH_VER=$(${dl} https://api.github.com/repos/cli/cli/tags | jq -r '.[0].name')
SHELLCHECK_VER=$(${dl} https://api.github.com/repos/koalaman/shellcheck/tags | jq -r '.[0].name')
SHFMT_VER=$(${dl} https://api.github.com/repos/mvdan/sh/tags | jq -r '.[0].name')
GH_VER="${GH_VER/v/}"
SHELLCHECK_VER="${SHELLCHECK_VER/v/}"
SHFMT_VER="${SHFMT_VER/v/}"
sed -Ei "
s/^(FROM\s+alpine:).*/\1${ALPINE_VER}/;
s/^(ARG\s+gh_version=).*/\1${GH_VER}/;
s/^(ARG\s+shellcheck_version=).*/\1${SHELLCHECK_VER}/;
s/^(ARG\s+shfmt_version=).*/\1${SHFMT_VER}/;
" Dockerfile
git diff
gitextract_prljnb1b/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── entrypoint.sh └── upgrade.sh
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (15K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 470,
"preview": "---\nname: Build\n\non:\n push:\n branches:\n - master\n\n pull_request:\n branches:\n - master\n\n workflow_disp"
},
{
"path": "Dockerfile",
"chars": 1200,
"preview": "FROM alpine:3.18.4\nLABEL \"name\"=\"sh-checker\"\nLABEL \"maintainer\"=\"Luiz Muller <contact@luizm.dev>\"\n\nARG shfmt_version=3.1"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2020 Luiz Muller\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 2182,
"preview": "# sh-checker\n\n[](https://github.c"
},
{
"path": "action.yml",
"chars": 1062,
"preview": "name: \"sh-checker\"\ndescription: \"Run shellcheck and/or shfmt on all shell script files and include commentaries, there i"
},
{
"path": "entrypoint.sh",
"chars": 7248,
"preview": "#!/usr/bin/env bash\n\ncd \"$GITHUB_WORKSPACE\" || {\n\tprintf 'Directory not found: \"%s\"\\n' \"$GITHUB_WORKSPACE\"\n\texit 1\n}\n\nSH"
},
{
"path": "upgrade.sh",
"chars": 1009,
"preview": "#!/usr/bin/env bash\n# shellcheck disable=SC2312 # (info): Consider invoking this command separately to avoid masking its"
}
]
About this extraction
This page contains the full source code of the luizm/action-sh-checker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (13.9 KB), approximately 4.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.