{"id":17383116,"url":"https://github.com/mcandre/stank","last_synced_at":"2026-03-17T02:14:12.613Z","repository":{"id":57573986,"uuid":"99871628","full_name":"mcandre/stank","owner":"mcandre","description":"shell script linters","archived":false,"fork":false,"pushed_at":"2026-01-13T19:59:30.000Z","size":3733,"stargazers_count":57,"open_issues_count":67,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-13T22:20:14.227Z","etag":null,"topics":["golang","shell-scripts","static-analysis"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mcandre.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-08-10T02:20:47.000Z","updated_at":"2026-01-13T19:59:32.000Z","dependencies_parsed_at":"2024-01-04T01:28:51.044Z","dependency_job_id":"5922a4fe-9bff-4eed-8aa8-d5d9b88d3c35","html_url":"https://github.com/mcandre/stank","commit_stats":{"total_commits":288,"total_committers":4,"mean_commits":72.0,"dds":"0.24305555555555558","last_synced_commit":"1214660b291d915a87e22180991e1365f30ad1dd"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/mcandre/stank","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcandre%2Fstank","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcandre%2Fstank/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcandre%2Fstank/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcandre%2Fstank/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mcandre","download_url":"https://codeload.github.com/mcandre/stank/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcandre%2Fstank/sbom","scorecard":{"id":631626,"data":{"date":"2025-08-11","repo":{"name":"github.com/mcandre/stank","commit":"7fb8ecdb584d4613fd3b795c4e0e184355109b36"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/actionlint.yml:1","Warn: no topLevel permission defined: .github/workflows/audit.yml:1","Warn: no topLevel permission defined: .github/workflows/lint.yml:1","Warn: no topLevel permission defined: .github/workflows/rubberstamp.yml:1","Warn: no topLevel permission defined: .github/workflows/test-crosscompile.yml:1","Warn: no topLevel permission defined: .github/workflows/test-futureproof-dependencies.yml:1","Warn: no topLevel permission defined: .github/workflows/test-futureproof-language.yml:1","Warn: no topLevel permission defined: .github/workflows/test-futureproof-os.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":10,"reason":"12 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.0.36 not signed: https://api.github.com/repos/mcandre/stank/releases/236436444","Warn: release artifact v0.0.35 not signed: https://api.github.com/repos/mcandre/stank/releases/226049236","Warn: release artifact v0.0.34 not signed: https://api.github.com/repos/mcandre/stank/releases/213370392","Warn: release artifact v0.0.33 not signed: https://api.github.com/repos/mcandre/stank/releases/206421896","Warn: release artifact v0.0.32 not signed: https://api.github.com/repos/mcandre/stank/releases/203896009","Warn: release artifact v0.0.36 does not have provenance: https://api.github.com/repos/mcandre/stank/releases/236436444","Warn: release artifact v0.0.35 does not have provenance: https://api.github.com/repos/mcandre/stank/releases/226049236","Warn: release artifact v0.0.34 does not have provenance: https://api.github.com/repos/mcandre/stank/releases/213370392","Warn: release artifact v0.0.33 does not have provenance: https://api.github.com/repos/mcandre/stank/releases/206421896","Warn: release artifact v0.0.32 does not have provenance: https://api.github.com/repos/mcandre/stank/releases/203896009"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Info: Possibly incomplete results: error parsing shell code: reached EOF without closing quote \": examples/hello-unbalanced:0","Info: Possibly incomplete results: error parsing shell code: | can only immediately follow a statement: examples/i-have-an-extension.lisp:0","Info: Possibly incomplete results: error parsing shell code: | can only immediately follow a statement: examples/i-should-have-an-extension:0","Info: Possibly incomplete results: error parsing shell code: if statement must end with \"fi\": examples/imposter:0","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/actionlint.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/actionlint.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/actionlint.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/actionlint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/audit.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/audit.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/audit.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/audit.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/lint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/lint.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/lint.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/lint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/rubberstamp.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/rubberstamp.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-crosscompile.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-crosscompile.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-crosscompile.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-crosscompile.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-futureproof-dependencies.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-futureproof-dependencies.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-futureproof-dependencies.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-futureproof-dependencies.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-futureproof-language.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-futureproof-language.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-futureproof-language.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-futureproof-language.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-futureproof-os.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-futureproof-os.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-futureproof-os.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test-futureproof-os.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/mcandre/stank/test.yml/main?enable=pin","Info:   0 out of  16 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   1 out of   1 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'main'","Warn: could not determine whether codeowners review is allowed","Warn: 'up-to-date branches' is disabled on branch 'main'","Info: status check found to merge onto on branch 'main'","Warn: PRs are not required to make changes on branch 'main'; or we don't have data to detect it.If you think it might be the latter, make sure to run Scorecard with a PAT or use Repo Rules (that are always public) instead of Branch Protection settings"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T08:04:54.692Z","repository_id":57573986,"created_at":"2025-08-21T08:04:54.692Z","updated_at":"2025-08-21T08:04:54.692Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28518627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T18:55:29.170Z","status":"ssl_error","status_checked_at":"2026-01-17T18:55:03.375Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","shell-scripts","static-analysis"],"created_at":"2024-10-16T07:40:40.155Z","updated_at":"2026-03-17T02:14:12.606Z","avatar_url":"https://github.com/mcandre.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# stank: shell script linters\n\n[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white)](https://pkg.go.dev/github.com/mcandre/stank) [![Test](https://github.com/mcandre/stank/actions/workflows/test.yml/badge.svg)](https://github.com/mcandre/stank/actions/workflows/test.yml) [![license](https://img.shields.io/badge/license-BSD-3)](LICENSE.md)\n\n# SUMMARY\n\nstank recursively lints shell scripts.\n\n# EXAMPLES\n\nThe stank system includes the stank Go library as well as several command line utilities for convenience. The `stank` application scans directories and files for POSIX-derived shell scripts and prints their paths, designed as a convenient standalone filter for linting large collections of source code.\n\n```console\n$ cd examples\n\n$ stank .\n.profile\n.shrc\n.zlogin\n...\n```\n\nThe `stank` command line utility searches file paths for shell scripts that may warrant linting.\n\nstank integrates with external linters, helping to feed them a more focused set of file paths to analyze within larger project directories.\n\n```console\n$ stank -print0 . | xargs -0 -n 1 shellcheck\nIn welcome.sh line 1:\n#!bash\n^----^ SC2239 (error): Ensure the shebang uses an absolute path to the interpreter.\n\nFor more information:\n  https://www.shellcheck.net/wiki/SC2239 -- Ensure the shebang uses an absolu...\n```\n\nMachine-generated files, including git hook default `*.sample` files, are automatically skipped.\n\nSee `stank -help` for additional options.\n\n# DOWNLOAD\n\n```sh\ngo install github.com/mcandre/stank/cmd/...@latest\n```\n\n## Prerequisites\n\n* [Go](https://go.dev/)\n\nFor information on developing stank, see our [development guide](DEVELOPMENT.md).\n\n# ABOUT\n\nstank is a library and collection of command line utilities for sniffing files to identify shell scripts like bash, sh, zsh, ksh and so on, those funky farmfresh gobs of garbaggio; versus other more palatable files like rb, py, pl.\n\nBelieve it or not, shell scripts are notoriously difficult to write well, so it behooves a developer to either write shell scripts in safer languages, or else wargame your scripts with an armada of linters. Trouble is, in large projects one can never be too sure which files are honest to dog POSIX compliant shell scripts, and which are pretenders. csh, tcsh, fish, ion, rc, and most other nonderivatives of bash tend to be NOT POSIX compatible. If you're geeky enough to have followed thus far, let's get crackalackin with some fruity examples dammit!\n\n# MORE EXAMPLES\n\nThe `funk` linter reports strange odors emanating from scripts, such as improper line endings, the presence of Byte Order Marker's in some Unicode scripts.\n\n```console\n$ funk examples\nAmbiguous launch style. Either feature a file extensions, or else feature executable bits: examples/.shrc\nTokenize like `unset IFS` at the top of executable scripts: examples/.shrc\nControl program flow like `set -euf` at the top of executable scripts: examples/.shrc\nTokenize like `unset IFS` at the top of executable scripts: examples/badconfigs/zprofile\nControl program flow like `set -euf` at the top of executable scripts: examples/badconfigs/zprofile\nMissing shebang: examples/blank.bash\nTraps may reset in subshells: examples/cleanup.sh\nMissing shebang: examples/goodbye.sh\nMissing shebang: examples/greetings.bash\nControl program flow like `set -euf` at the top of executable scripts: examples/hello-commented\n\n$ funk -modulino examples\nConfiguration features shebang: examples/badconfigs/.bash_profile\nConfiguration features executable permissions: examples/badconfigs/zprofile\nMissing final end of line sequence: examples/blank.bash\nMissing shebang: examples/blank.bash\nInterpreter mismatch between shebang and extension: examples/derp.zsh\nMissing shebang: examples/greetings.bash\nMissing final end of line sequence: examples/hello-crlf.sh\nCR/CRLF line ending detected: examples/hello-crlf.sh\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/hello-crlf.sh\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/howdy\nMissing shebang: examples/howdy.zsh\nMissing shebang: examples/just-eol.bash\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/lo\nMissing final end of line sequence: examples/lo-cr.csh\nCR/CRLF line ending detected: examples/lo-cr.csh\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/pipefail\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/shout.sh\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/wednesday\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/wednesday-bom\nLeading BOM reduces portability: examples/wednesday-bom\nModulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/welcome\n\n$ funk -help\n  -cr\n        Report presence/absence of final end of line sequence (default true)\n  -eol\n        Report presence/absence of final end of line sequence (default true)\n  -help\n        Show usage information\n  -modulino\n        Enforce strict separation of application scripts vs. library scripts\n  -version\n        Show version information\n```\n\nBoth `stank` and `funk` have the ability to select low level, nonPOSIX scripts as well, such as csh/tcsh scripts used in FreeBSD.\n\nNote that funk cannot reliably warn for missing shebangs if the extension is also missing; typically, script authors use one or the other to mark files as shell scripts. Lacking both a shebang and a file extension, means that a file could contain code for many languages, making it difficult to determine the POSIXy nature of the code. Even if an exhaustive set of ASTs are applied to test the file contents for syntactical validity across the dozens of available shell languages, there is a strong possibility in shorter files that the contents are merely incidentally valid script syntax, though the intent of the file is not to operate as a POSIX shell script. Short, nonPOSIX scripts such as for csh/tcsh could easily trigger a \"POSIX\" syntax match. In any case, know that the shebang is requisite for ensuring your scripts are properly interpreted.\n\nNote that funk may fail to present permissions warnings if the scripts are housed on non-UNIX file systems such as NTFS, where executable bits are often missing from the file metadata altogether. When storing shell scripts, be sure to set the appropriate file permissions, and transfer files as a bundle in a tarball or similar to safeguard against dropped permissions.\n\nNote that funk may warn of interpreter mismatches for scripts with extraneous dots in the filename. Rather than `.envrc.sample`, name the file `sample.envrc`. Rather than `wget-google.com`, name the file `wget-google-com`. Appending `.sh` is also an option, so `update.es.cluster` renames to `update.es.cluster.sh`.\n\nThe optional `-modulino` flag to funk enables strict separation of script duties, into distinct application scripts vs. library scripts. Application scripts are generally executed by invoking the path, such as `./hello` or `~/bin/hello` or simply `hello` when `$PATH` is appropriately modified. Application scripts feature owner executable permissions, and perhaps group and other as well depending on system configuration needs. In contrast, library scripts are intended to be imported with dot (`.`) or `source` into user shells or other scripts, and should feature a file extension like `.lib.sh`, `.sh`, `.bash`, etc. By using separate naming conventions, we more quickly communicate to downstream users how to interact with a shell script. In particular, by dropping file extensions for shell script applications, we encourage authors to choose more meaningful script names. Instead of the generic `build.sh`, choose `build-docker`. Instead of `kafka.sh`, choose `start-kafka`, `kafka-entrypoint`, etc.\n\nFinally, `stink` prints a record of each file's POSIXyness, including any interesting fields it identified along the way. Note that some fields may be zero valued if the stench of POSIX or rosy waft of nonPOSIX is overwhelming, short-circuiting analysis. This short-circuiting feature dramatically speeds up how `stank` searches large projects.\n\nNote that permissions are relayed as decimals, due to constraints on JSON integer formatting (we didn't want to use a custom octal string field). Use `echo 'obase=8;\u003csome integer\u003e | bc` to display these values in octal.\n\nNote that legacy systems, packages, and shell scripts referencing \"sh\" may refer to a plethora of pre-POSIX shells. Modern systems rename \"sh\" to \"lksh\", \"tsh\", \"etsh\", etc. to avoid confusion. In general, the stank suite will assume that the majority of scripts being scanned are targeting post-1971 technology, so use your human intuition and context to note any legacy Thompson UNIX v6 \"sh\", etc. scripts. Most modern linters will neither be able to parse such scripts of any complexity, nor will they recognize them for the legacy scripts that they are, unless the scripts' shebangs are rendered with the modern retro interpreters \"lksh\", \"tsh\", \"etsh\", etc. for deployment on modern UNIX systems. One could almost use the fs stats for modification/change to try to identify these legacy outliers, but this is a practically unrealistic assumption except for the most obsessive archaeologist, diligently ensuring their legacy scripts continue to present 1970's metadata even after experimental content modifications. So the stank system will simply punt and assume sh -\u003e POSIX sh, ksh -\u003e ksh88 / ksh93 for the sake of modernity and balance.\n\nSimilarly, the old Bourne shell AKA \"sh\" AKA \"bsh\" presents language identification difficulties. Old Bourne shell scripts are most likely to present themselves with \"sh\" shebangs, which is okay as Bourne sh and ksh88/pdksh/ksh served as the bases for the POSIX sh standard. Some modern systems may present a Bourne shell as a \"sh\" or \"bsh\" binary. The former presents few problems for stank identification, though \"bsh\" is tricky, as the majority of its uses today are not associated with the Bourne shell but with the Java BeanShell. So stank may default to treating `bsh` scripts as non-POSIXy, and any such Bourne shell scripts are advised to feature either `bash` or `sh` shebangs, and perhaps `.sh` or `.bash` extensions, in order to self-identify as modern, POSIX compliant scripts.\n\n```console\n$ stink examples/hello\n{\"Path\":\"examples/hello\",\"Filename\":\"hello\",\"Basename\":\"hello\",\"Extension\":\"\",\"Shebang\":\"#!/bin/sh\",\"Interpreter\":\"sh\",\"LineEnding\":\"\\n\",\"FinalEOL\":false,\"ContainsCR\":false\n,\"Permissions\":509,\"Directory\":false,\"OwnerExecutable\":true,\"BOM\":false,\"POSIXy\":true,\"AltShellScript\":false}\n\n$ stink -pp examples/hello\n{\n  \"Path\": \"examples/hello\",\n  \"Filename\": \"hello\",\n  \"Basename\": \"hello\",\n  \"Extension\": \"\",\n  \"Shebang\": \"#!/bin/sh\",\n  \"Interpreter\": \"sh\",\n  \"LineEnding\": \"\\n\",\n  \"FinalEOL\": false,\n  \"ContainsCR\": false,\n  \"Permissions\": 509,\n  \"Directory\": false,\n  \"OwnerExecutable\": true,\n  \"BOM\": false,\n  \"POSIXy\": true,\n  \"AltShellScript\": false\n}\n\n$ stink -pp examples/hello.py\n{\n  \"Path\": \"examples/hello.py\",\n  \"Filename\": \"hello.py\",\n  \"Basename\": \"hello.py\",\n  \"Extension\": \".py\",\n  \"Shebang\": \"#!/usr/bin/env python\",\n  \"Interpreter\": \"python\",\n  \"LineEnding\": \"\\n\",\n  \"FinalEOL\": false,\n  \"ContainsCR\": false,\n  \"Permissions\": 420,\n  \"Directory\": false,\n  \"OwnerExecutable\": false,\n  \"BOM\": false,\n  \"POSIXy\": false,\n  \"AltShellScript\": false\n}\n\n$ stink -help\n  -cr\n        Report presence/absence of any CR/CRLF's\n  -eol\n        Report presence/absence of final end of line sequence\n  -help\n        Show usage information\n  -pp\n        Prettyprint smell records\n  -version\n        Show version information\n```\n\nThe included `examples/` directory demonstrates many edge cases, such as empty scripts, shebang-less scripts, extensioned and extensionless scripts, and various Hello World applications in across many programming languages. Some files, such as `examples/goodbye` may contain 100% valid POSIX shell script content, but fail to self-identify with either shebangs or relevant file extensions. In a large project, such files may be mistakenly treated as whoknowswhat format, or simply plain text. Perhaps statistical methods could help identify POSIX grammars, but even an empty file is technically POSIX, which is unhelpful from a reliable classification standpoint. In any case, `examples/` hopefully covers the more common edge cases.\n\nOne way to think of `stank` is a bounty hunter for shell scripts.\n\nGiven that shell tends to be more fragile than higher level programming languages, then it is a good idea to rewrite shell code as dedicated applications. Go and Rust are especially good choices for application languages.\n\nThe Rust programming language has best in class performance, reliability, and security. The Go programming language has comparable performance, reliability, and security in most contexts. Both Rust and Go support cross-compilation and static executables, so that it's much easier to develop, test, package, and distribute Rust/Go applications compared to flaky shell scripts. Most shell coders neglect to consider subtle vendor locking problems with shell syntax and the flags used for individual commands. Rust has a steeper learning curve than some coders are willing to devote time for. Often, Go can serve as a compromise. Being compiled languages, both Rust and Go are protected from many runtime pitfalls that shells and other interpreted languages invite.\n\nRegardless, the particular programming language is a less important, concern, as long as it is not shell. Notoriously hazardous programming languages like JavaScript and Perl, are still safer than shell. Shell (any flavor) is a trash fire waiting for a spark.\n\nFortunately, the list of shell scripts that `stank` emits, can help engineers to identify program candidates to rewrite in more mature programming languages.\n\n# WARNING ON FALSE NEGATIVES\n\nNote that very many software components have a bad habit of encouraging embedded, inline shell script snippets into non-shell script files. For example, CI/CD job configurations, Dockerfile RUN steps, Kubernetes resources, and make. Most linter tools (for shell scripts and other languages) have very limited or nonexistent support for linting inline shell script snippets.\n\nAccordingly, move shell script snippets to a dedicated shell script file. And then have the software component execute the shell script. Then you will be able to lint the shell code with more tools, and thereby raise the quality level of your system.\n\n# WARNING ON FALSE POSITIVES\n\nSome rather obscure files, such as Common Lisp source code with multiline, polyglot shebangs and no file extension, may falsely trigger the stank library, and the stink and stank applications, which short-circuit on the first line of the hacky shebang. Such files may be falsely identified as \"POSIX\" code, which is actually the intended behavior! This is because the polyglot shebang is a hack to work around limitations in the Common Lisp language, which ordinarily does not accept POSIX shebang comments, in order to get Common Lisp scripts to be dot-slashable in bash. For this situation, it is best to supply a proper file extension to such files.\n\n```console\n$ head examples/i-should-have-an-extension\n#!/usr/bin/env sh\n#|\nexec clisp -q -q $0 $0 ${1+\"$@\"}\n|#\n\n(defun hello-main (args)\n  (format t \"Hello from main!~%\"))\n\n;;; With help from Francois-Rene Rideau\n;;; http://tinyurl.com/cli-args\n\n$ stink -pp examples/i-should-have-an-extension\n{\n  \"Path\": \"examples/i-should-have-an-extension\",\n  \"Filename\": \"i-should-have-an-extension\",\n  \"Basename\": \"i-should-have-an-extension\",\n  \"Extension\": \"\",\n  \"BOM\": false,\n  \"Shebang\": \"#!/usr/bin/env sh\",\n  \"Interpreter\": \"sh\",\n  \"LineEnding\": \"\\n\",\n  \"POSIXy\": true\n}\n```\n\nPerhaps append a `.lisp` extension to such files. Or separate the modulino into clear library vs. command line modules. Or extract the shell interaction into a dedicated script. Or convince the language maintainers to treat shebangs as comments. Write your congressman. However you resolve this, know that the current situation is far outside the norm, and likely to break in a suitably arcane and dramatic fashion. With wyverns and flaming seas and portents of all ill manner.\n\n# RESOURCES\n\nPrior art, personal plugs, and tools for developing applications (including non-shell projects)!\n\n* [jq](https://jqlang.org/) - JSON transformer\n* [mcandre/linters](https://github.com/mcandre/linters) - curated linter collection\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcandre%2Fstank","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmcandre%2Fstank","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcandre%2Fstank/lists"}