├── .gitignore ├── WORDLIST ├── Makefile ├── LICENSE ├── .github └── workflows │ └── shellcheck.yml ├── NEWS.md ├── README.md └── shellcheck-repl.bash /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | **/*~ 3 | -------------------------------------------------------------------------------- /WORDLIST: -------------------------------------------------------------------------------- 1 | Bengtsson 2 | cd 3 | eval 4 | reenable 5 | repl 6 | REPL 7 | shellcheck 8 | ShellCheck 9 | xPMo 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=bash 2 | 3 | check: shellcheck spelling 4 | 5 | shellcheck: 6 | @echo "Validating shell scripts using ShellCheck $$(shellcheck --version | grep -iE "^version" | sed 's/://'):" 7 | @shellcheck shellcheck-repl.bash 8 | @echo "All OK" 9 | 10 | spelling: 11 | Rscript -e "spelling::spell_check_files('README.md', ignore=readLines('WORDLIST'))" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2019, Henrik Bengtsson, xPMo 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: shellcheck 4 | 5 | jobs: 6 | checks: 7 | if: "! contains(github.event.head_commit.message, '[ci skip]')" 8 | 9 | timeout-minutes: 5 10 | 11 | runs-on: ubuntu-latest 12 | 13 | name: shellcheck 14 | 15 | strategy: 16 | fail-fast: false 17 | 18 | steps: 19 | - name: Checkout git repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Install dependencies 23 | run: | 24 | sudo apt-get install -y shellcheck 25 | shellcheck --version 26 | curl -L -O https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz 27 | tar Jxf shellcheck-v0.11.0.linux.x86_64.tar.xz 28 | mv shellcheck-v0.11.0/shellcheck . 29 | PATH=".:$PATH" shellcheck --version 30 | 31 | - name: ShellCheck 32 | run: | 33 | make shellcheck 34 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # shellcheck-repl 2 | 3 | ## Version 0.5.0 (2025-10-16) 4 | 5 | * Now `source shellcheck-repl.bash` asserts that it is called from an 6 | interactive shell. If not, an informative error message is produced. 7 | 8 | 9 | ## Version 0.4.4 (2024-04-25) 10 | 11 | * Now ignoring [SC1044], because it would produce `SC1044 (error): 12 | Couldn't find end token 'EOF' in the here document.` after entering 13 | the first line of a here document. 14 | 15 | 16 | ## Version 0.4.3 (2023-04-16) 17 | 18 | ### New Features 19 | 20 | * Now ignoring [SC2096], because commenting an _absolute_ path 21 | (e.g. `# /path/to/something --foo --bar`), would produce `SC2096 22 | (error): On most OS, shebangs can only specify a single parameter.` 23 | 24 | 25 | ## Version 0.4.2 (2023-02-15) 26 | 27 | ### Bug Fixes 28 | 29 | * ShellCheck REPL failed when using Bash (>= 5.1.0 & < 5.2) giving 30 | `bash: bash_execute_unix_command: cannot find keymap for command`. 31 | This was because of a bug in the Bash 5.1 suite, which I work 32 | around by removing internal keybinding sequences before trying to 33 | re-assign them. 34 | 35 | 36 | ## Version 0.4.1 (2023-02-14) 37 | 38 | ### New Features 39 | 40 | * Add environment variable `SHELLCHECK_REPL_ACTION`. If `enable`, 41 | then ShellCheck REPL is enabled and if `disable`, it is enabled. if 42 | `sessioninfo`, then session information is displayed. 43 | 44 | * Now validation is skipped immediately if input is empty, which 45 | happens if we just press ENTER. 46 | 47 | * Error messages now also include the Bash version. 48 | 49 | ### Known issues 50 | 51 | * Due to a bug Bash (>= 5.1 & < 5.2), ShellCheck REPL will not work 52 | correctly. ShellCheck REPL will produce an informative warning, if 53 | enabled on a buggy Bash version. The Bash bug results in error 54 | `bash: bash_execute_unix_command: cannot find keymap for command`. 55 | 56 | ### Defunct 57 | 58 | * Environment variable `SHELLCHECK_REPL_INIT` is defunct. Use 59 | `SHELLCHECK_REPL_ACTION` instead. 60 | 61 | 62 | ## Version 0.4.0 (2022-12-17) 63 | 64 | ### Significant Changes 65 | 66 | * Environment variables `SC_REPL_INIT` and `SC_REPL_DEBUG` have been 67 | renamed to `SHELLCHECK_REPL_INIT` and `SHELLCHECK_REPL_DEBUG`. 68 | 69 | ### New Features 70 | 71 | * The explaination how to exclude a specific type of ShellCheck check 72 | that appears after an issue is detected can be disabled by setting 73 | environment variable `SHELLCHECK_REPL_VERBOSE` to `false`. 74 | 75 | 76 | ## Version 0.3.0 (2022-11-21) 77 | 78 | ### New Features 79 | 80 | * Now ignoring [SC1113], because commenting an _absolute_ path 81 | (e.g. `# /path/to/something` to be used later), would produce 82 | `SC1091 (error): Use #!, not just #, for the shebang.` 83 | 84 | 85 | ## Version 0.2.1 (2022-04-16) 86 | 87 | ### Bug Fixes 88 | 89 | * Now ignoring [SC1091], because `p=/path/to; source "$p/foo.sh"` 90 | would produce `SC1091 (info): Not following: ./bin/activate: 91 | openBinaryFile: does not exist (No such file or directory)`. 92 | 93 | 94 | ## Version 0.2.0 (2022-02-18) 95 | 96 | ### New Features 97 | 98 | * Now checking with rule [SC2154] ('var' is referenced but not 99 | assigned). This is achieved by providing ShellCheck with a preamble 100 | of `declare -p` specifications for any variables of in the 101 | expression. 102 | 103 | * Now rule [SC2178] (Variable was used as an array but is now assigned 104 | a string.) works. This is achieved by providing ShellCheck with a 105 | preamble of `declare -p` specifications for any variables assigned 106 | in the expression. 107 | 108 | 109 | ## Version 0.1.4 (2022-02-17) 110 | 111 | ### Bug Fixes 112 | 113 | * `scl_enable()` would output `ERROR: No such keybinding: \C-x\C-b2`, 114 | which was harmless. 115 | 116 | 117 | ## Version 0.1.3 (2022-02-17) 118 | 119 | ### New Features 120 | 121 | * Ignore also [SC2155] by default to allow for `export 122 | FOO=$(something)`. 123 | 124 | * The tool now asserts that Bash (>= 4.4) and `shellcheck` are 125 | available. 126 | 127 | * Add internal assertions. 128 | 129 | * Add a debug mechanism for troubleshooting purposes. 130 | 131 | 132 | ## Version 0.1.2 (2021-01-16) 133 | 134 | ### New Features 135 | 136 | * ShellCheck validation can be disable for the current line by adding 137 | two or more trailing spaces, i.e. `ls -l` will be validated but `ls 138 | -l ` will not. In contrast, using space at the beginning, causes 139 | Bash to skip adding the call to its command-line history. 140 | 141 | * `SHELLCHECK_REPL_SKIP_PATTERN` can now be regular expression. It's 142 | new default is now `"(^\!|[[:space:]][[:space:]]$)"`. 143 | 144 | * Ignore also [SC1090] by default to avoid ShellCheck error when 145 | trying to source a file. 146 | 147 | ### Defunct 148 | 149 | * Support for disabling of ShellCheck validation by adding a 150 | *leading* space has been removed in favor of *two trailing spaces*. 151 | The reason for this change is because the use leading spaces for 152 | this purpose conflicts with how `HISTCONTROL=ignorespace`, or 153 | `ignoredups`, prevents the call from being added to the 154 | command-line history. 155 | 156 | 157 | ## Version 0.1.1 (2019-09-09) 158 | 159 | ### New Features 160 | 161 | * ShellCheck validation can be disable for the current line by adding 162 | one or more leading spaces, i.e. `ls -l` will be validated but ` ls 163 | -l` will not. This skip rule can be customize via 164 | `SHELLCHECK_REPL_SKIP_PATTERN` which defaults to `"[[:space:]\!]"`, 165 | i.e. a leading space or exclamation mark. 166 | 167 | * ShellCheck validation is now skipped for history expansion via 168 | exclamation marks (!), e.g. `!1984` will neither be validated nor 169 | give an error. 170 | 171 | 172 | ## Version 0.1.0 (2019-04-17) 173 | 174 | ### Significant Changes 175 | 176 | * The license is ISC (by the Internet Software Consortium). 177 | 178 | ### New Features 179 | 180 | * Use colored output if the ShellCheck version supports it. 181 | 182 | * Cleaner output by no longer displaying "In /dev/fd/63 line 1:". 183 | 184 | * ShellCheck output can be controlled by setting environment variable 185 | `SHELLCHECK_REPL_INFO` to `raw`, `note`, `full` or `clean` 186 | (default). 187 | 188 | 189 | ## Version 0.0.4 (2019-03-30) 190 | 191 | ### New Features 192 | 193 | * Add support for older version of Bash. 194 | 195 | 196 | ## Version 0.0.3 (2019-03-29) 197 | 198 | ### New Features 199 | 200 | * Skipping more ShellCheck checks by default. 201 | 202 | 203 | ## Version 0.0.2 (2019-03-28) 204 | 205 | ### New Features 206 | 207 | * Add support for ShellCheck (< 0.6.0). 208 | 209 | * `SHELLCHECK_REPL_EXCLUDE` controls which ShellCheck checks to skip. 210 | 211 | 212 | ## Version 0.0.1 (2019-03-28) 213 | 214 | ### New Features 215 | 216 | * First implementation by xPMo in response Henrik Bengtsson's inquiry 217 | at [ShellCheck Issue #1535]. 218 | 219 | 220 | [SC1044]: https://github.com/koalaman/shellcheck/wiki/SC1044 221 | [SC1090]: https://github.com/koalaman/shellcheck/wiki/SC1090 222 | [SC1091]: https://github.com/koalaman/shellcheck/wiki/SC1091 223 | [SC1113]: https://github.com/koalaman/shellcheck/wiki/SC1113 224 | [SC2096]: https://github.com/koalaman/shellcheck/wiki/SC2096 225 | [SC2154]: https://github.com/koalaman/shellcheck/wiki/SC2154 226 | [SC2155]: https://github.com/koalaman/shellcheck/wiki/SC2155 227 | [SC2178]: https://github.com/koalaman/shellcheck/wiki/SC2178 228 | [ShellCheck Issue #1535]: https://github.com/koalaman/shellcheck/issues/1535 229 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![shellcheck](https://github.com/HenrikBengtsson/shellcheck-repl/actions/workflows/shellcheck.yml/badge.svg)](https://github.com/HenrikBengtsson/shellcheck-repl/actions/workflows/shellcheck.yml) 2 | 3 | # ShellCheck REPL: Validation of Shell Commands Before Evaluation 4 | 5 | [ShellCheck] is a great tool for validating your Unix shell scripts. 6 | It will parse the scripts and warn about mistakes, errors, and 7 | potential problems. This tool - **shellcheck-repl** - brings 8 | ShellCheck validation to the [Bash] read-eval-print loop (REPL), 9 | i.e. the [Bash] prompt. Getting this type of validation and feedback 10 | at the prompt lowers the risk of damaging mistakes and will help you 11 | become a better Bash user and developer. 12 | 13 | The **shellcheck-repl** tool injects itself into the Bash REPL where 14 | it intercepts the read command line, validates the content via 15 | ShellCheck, and if it is all OK, then the command is evaluated and 16 | printed as usual. However, if there is a mistake, then the command 17 | will _not_ be evaluated and an informative error message is displayed 18 | instead. For example, assume we do: 19 | 20 | ```sh 21 | $ words="lorem ipsum dolor" 22 | $ for w in "$words"; do echo $w; done 23 | ``` 24 | 25 | Although this looks like a simple for loop, it might not be clear to 26 | you what the outcome of it will be. What values will `$w` take? 27 | However, with **shellcheck-repl** enabled, we will get the following 28 | if we try call it: 29 | 30 | ```sh 31 | $ for w in "$words"; do echo "$w"; done 32 | ^-- SC2066: Since you double quoted this, it will 33 | not word split, and the loop will only run once. 34 | ``` 35 | 36 | So, what [SC2066] suggests is that the output will be a single line 37 | `lorem ipsum dolor` and not three words on three separate lines. We 38 | probably meant to use: 39 | 40 | ```sh 41 | $ for w in $words; do echo "$w"; done 42 | lorem 43 | ipsum 44 | dolor 45 | ``` 46 | 47 | 48 | ## Bypassing the ShellCheck validation 49 | 50 | You can bypass the ShellCheck validation by appending two spaces to 51 | the command with. For instance, assume we get: 52 | 53 | ```sh 54 | $ words="lorem ipsum dolor" 55 | $ echo $words 56 | ^-- SC2086: Double quote to prevent globbing and word splitting. 57 | ``` 58 | 59 | Ideally we should call: 60 | 61 | ```sh 62 | $ echo "$words" 63 | lorem ipsum dolor 64 | ``` 65 | 66 | but if we find that too tedious, we can skip the validation by 67 | appending two or more spaces at the end: 68 | 69 | ```sh 70 | $ echo $words␣␣ 71 | lorem ipsum dolor 72 | ``` 73 | 74 | By the way, one example where [SC2086] is crucial is when you work 75 | with filenames. Using: 76 | 77 | ```sh 78 | $ rm $file␣␣ 79 | ``` 80 | 81 | can be very risky if `$file` contains spaces - in addition to not 82 | removing the file intended, you might end up removing files that you 83 | did not intend to remove. 84 | 85 | 86 | ## Disable and enable checks 87 | 88 | To disable the ShellCheck REPL tool, do: 89 | 90 | ```sh 91 | $ sc_repl_disable 92 | ``` 93 | 94 | To reenable it, do: 95 | 96 | ```sh 97 | $ sc_repl_enable 98 | ``` 99 | 100 | 101 | ## Settings 102 | 103 | ### ShellCheck rules to ignore 104 | 105 | Some of the ShellCheck rules may be too tedious to follow when on the 106 | command line. For example, when trying to change directory to a 107 | non-existing directory, `cd` will produce a non-zero exit code. If 108 | you do not handle this type of error in a script, ShellCheck will 109 | report on [SC2164]; 110 | 111 | ```sh 112 | cd /path/to 113 | ^-- SC2164: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. 114 | ``` 115 | 116 | The suggestion is really valid for scripts, but for the command line 117 | it is just annoying. Because of this, **shellcheck-repl** disables 118 | the check for SC2164 by default. In addition, it also disables the 119 | validation of other ShellCheck rules that are too tedious or simply 120 | false-positives when used at the command line. Here is the complete 121 | list: 122 | 123 | * [SC1001]: This \= will be a regular '=' in this context. 124 | * [SC1090]: Can't follow non-constant source. Use a directive to 125 | specify location. 126 | * [SC1091]: Not following: (error message here). 127 | * [SC1113]: Use #!, not just #, for the shebang. 128 | * [SC2034]: 'var' appears unused. Verify it or export it. 129 | * [SC2096]: On most OS, shebangs can only specify a single parameter. 130 | * [SC2155]: Declare and assign separately to avoid masking return 131 | values. 132 | * [SC2164]: Use 'cd ... || exit' or 'cd ... || return' in case cd 133 | fails. 134 | 135 | This set of rules that are disabled by default can be configured via 136 | environment variable `SHELLCHECK_REPL_EXCLUDE` by specifying rules 137 | (without `SC` prefix) as a comma-separated list. The default 138 | corresponds to 139 | `SHELLCHECK_REPL_EXCLUDE=1001,1090,1091,1113,2034,2155,2164`. 140 | 141 | 142 | ### Disable and enable hints 143 | 144 | When there's a ShellCheck issue, a hint on how to disable that issue 145 | is also outputted, e.g. 146 | 147 | ```sh 148 | $ msg="Value: $value" 149 | ^----^ SC2154 (warning): value is referenced but not assigned. 150 | 151 | To skip a check, add its SC number to 'SHELLCHECK_REPL_EXCLUDE', e.g. 152 | 153 | export SHELLCHECK_REPL_EXCLUDE="${SHELLCHECK_REPL_EXCLUDE},4038" 154 | 155 | Currently, SHELLCHECK_REPL_EXCLUDE=1001,1090,1091,1113,2034,2155,2164 156 | 157 | To skip ShellCheck validation for this call, append two spaces 158 | ``` 159 | 160 | This message can be disabled by setting: 161 | 162 | ```sh 163 | SHELLCHECK_REPL_VERBOSE=false 164 | ``` 165 | 166 | 167 | 168 | ## Requirements 169 | 170 | * [ShellCheck] 171 | * [Bash] (>= 4.4) 172 | 173 | Bash is the only supported shell right now. 174 | 175 | 176 | ## Installation 177 | 178 | Download the `shellcheck-repl.bash` script and source it in your 179 | `~/.bashrc` startup script, e.g. 180 | 181 | ```sh 182 | $ curl -L -O https://github.com/HenrikBengtsson/shellcheck-repl/archive/refs/tags/0.4.3.tar.gz 183 | $ tar xf 0.4.3.tar.gz 184 | $ echo ". /path/to/software/shellcheck-repl-0.4.3/shellcheck-repl.bash" >> ~/.bashrc 185 | ``` 186 | 187 | Or, similarly, via Git: 188 | 189 | ```sh 190 | $ cd /path/to/software 191 | $ git clone https://github.com/HenrikBengtsson/shellcheck-repl.git 192 | $ echo ". /path/to/software/shellcheck-repl/shellcheck-repl.bash" >> ~/.bashrc 193 | ``` 194 | 195 | 196 | ## Authors 197 | 198 | * GitHub user [xPMo](https://github.com/xPMo) - [original code from 2019-03-28](https://github.com/koalaman/shellcheck/issues/1535#issuecomment-477633465) 199 | * Henrik Bengtsson 200 | 201 | 202 | ## Appendix 203 | 204 | ### Example: ShellCheck REPL prevents Bash fork bomb 205 | 206 | A well-known [fork bomb](https://en.wikipedia.org/wiki/Fork_bomb) in Bash is `:(){ :|:& };:`. If launched, it will recursively relaunch itself via piping and background jobs. With ShellCheck REPL enable, we will be prevented from executing the call, e.g. 207 | 208 | ```sh 209 | $ :(){ :|:& };: 210 | ^-- SC2264 (error): This function unconditionally re-invokes itself. Missing 'command'? 211 | ^-- SC2264 (error): This function unconditionally re-invokes itself. Missing 'command'? 212 | ``` 213 | 214 | 215 | [ShellCheck]: https://github.com/koalaman/shellcheck 216 | [Bash]: https://www.gnu.org/software/bash/ 217 | [SC2066]: https://github.com/koalaman/shellcheck/wiki/SC2066 218 | [SC2086]: https://github.com/koalaman/shellcheck/wiki/SC2086 219 | [SC1001]: https://github.com/koalaman/shellcheck/wiki/SC1001 220 | [SC1090]: https://github.com/koalaman/shellcheck/wiki/SC1090 221 | [SC1091]: https://github.com/koalaman/shellcheck/wiki/SC1091 222 | [SC1113]: https://github.com/koalaman/shellcheck/wiki/SC1113 223 | [SC2034]: https://github.com/koalaman/shellcheck/wiki/SC2034 224 | [SC2096]: https://github.com/koalaman/shellcheck/wiki/SC2096 225 | [SC2155]: https://github.com/koalaman/shellcheck/wiki/SC2155 226 | [SC2164]: https://github.com/koalaman/shellcheck/wiki/SC2164 227 | [#21]: https://github.com/HenrikBengtsson/shellcheck-repl/issues/21 228 | -------------------------------------------------------------------------------- /shellcheck-repl.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #' ShellCheck Read-Eval-Print Loop (REPL) 4 | #' 5 | #' Validation of Shell Commands Before Evaluation 6 | #' 7 | #' Usage/Install: 8 | #' source shellcheck-repl.bash 9 | #' 10 | #' License: ISC 11 | #' Source code: https://github.com/HenrikBengtsson/shellcheck-repl 12 | 13 | sc_repl_version() { 14 | echo "0.5.0" 15 | } 16 | 17 | 18 | ## Source: https://github.com/koalaman/shellcheck/issues/1535 19 | sc_version() { 20 | sc_repl_debug "sc_version() ..." 21 | if [ -z "${SHELLCHECK_VERSION+x}" ]; then 22 | # Example: '0.4.6' 23 | SHELLCHECK_VERSION=$(shellcheck --version | sed -nE 's/version: +(.+)/\1/p') 24 | # Example: '0.4' 25 | SHELLCHECK_VERSION_X_Y="${SHELLCHECK_VERSION%.*}" 26 | fi 27 | sc_repl_debug " - SHELLCHECK_VERSION: ${SHELLCHECK_VERSION}" 28 | sc_repl_debug " - SHELLCHECK_VERSION_X_Y: ${SHELLCHECK_VERSION_X_Y}" 29 | sc_repl_debug "sc_version() ... done" 30 | } 31 | 32 | version_gt() { 33 | test "$(printf '%s\n' "$@" | sort --version-sort | head -n 1)" != "$1" 34 | } 35 | 36 | SHELLCHECK_REPL_DEBUG=${SHELLCHECK_REPL_DEBUG:-false} 37 | SHELLCHECK_REPL_VERBOSE=${SHELLCHECK_REPL_VERBOSE:-true} 38 | 39 | sc_repl_debug() { 40 | $SHELLCHECK_REPL_DEBUG || return 0 41 | echo >&2 "DEBUG: ${*}" 42 | } 43 | 44 | sc_repl_debug_shell_command_keybindings() { 45 | $SHELLCHECK_REPL_DEBUG || return 0 46 | sc_repl_debug "All active shell-command keybindings per 'bind -X':" 47 | { bind -X 1>&2; } > /dev/null 48 | } 49 | 50 | sc_repl_debug_function_keybindings() { 51 | $SHELLCHECK_REPL_DEBUG || return 0 52 | sc_repl_debug "All active shell-command keybindings per 'bind -P':" 53 | { bind -P | grep -F "can be found on" 1>&2; } > /dev/null 54 | } 55 | 56 | sc_repl_debug_sequence_keybindings() { 57 | $SHELLCHECK_REPL_DEBUG || return 0 58 | sc_repl_debug "All active sequence keybindings per 'bind -s':" 59 | { bind -s | grep -F "can be found on" 1>&2; } > /dev/null 60 | } 61 | 62 | sc_repl_sessioninfo() { 63 | sc_version 64 | echo "ShellCheck REPL: $(sc_repl_version)" 65 | echo "ShellCheck: ${SHELLCHECK_VERSION}" 66 | echo "Bash: ${BASH_VERSION}" 67 | # echo "Bash key sequences bound to shell commands:" 68 | # bind -X 69 | # echo "Bash key sequences bound to functions:" 70 | # bind -P | grep "can be found" 71 | } 72 | 73 | sc_is_interactive() { 74 | [[ $- == *i* ]] && [[ -t 0 && -t 1 ]] 75 | } 76 | 77 | sc_repl_warning() { 78 | echo >&2 "WARNING: ${*} [shellcheck-repl $(sc_repl_version); bash ${BASH_VERSION}]" 79 | return 0 80 | } 81 | 82 | sc_repl_error() { 83 | echo >&2 "ERROR: ${*} [shellcheck-repl $(sc_repl_version); bash ${BASH_VERSION}]" 84 | return 1 85 | } 86 | 87 | sc_repl_assert_bash_version() { 88 | if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then 89 | sc_repl_error "ShellCheck REPL requires Bash (>= 4.4): ${BASH_VERSION}" 90 | return 1 91 | fi 92 | if [[ "${BASH_VERSINFO[0]}" -eq 4 ]] && [[ "${BASH_VERSINFO[1]}" -lt 4 ]]; then 93 | sc_repl_error "ShellCheck REPL requires Bash (>= 4.4): ${BASH_VERSION}" 94 | return 1 95 | fi 96 | return 0 97 | } 98 | 99 | sc_repl_assert_shellcheck() { 100 | if ! command -v shellcheck &> /dev/null; then 101 | sc_repl_error "'shellcheck' not found" 102 | return 1 103 | fi 104 | return 0 105 | } 106 | 107 | sc_repl_assert_readline_fcn_exists() { 108 | if ! bind -l | grep -q -E "^${1:?}$"; then 109 | sc_repl_error "No such bash function: ${1}" 110 | return 1 111 | fi 112 | return 0 113 | } 114 | 115 | ## Function to check whether 'bind -X' is available 116 | ## It available in Bash 4.3 (2014-02-26) 117 | ## It is not available in 4.2.53 (2014-11-07) 118 | sc_repl_bind_has_option_X() { 119 | bind -X &> /dev/null 120 | } 121 | 122 | sc_repl_assert_shell_command_keybinding_exists() { 123 | sc_repl_debug "sc_repl_assert_shell_command_keybinding_exists('${1}') ..." 124 | ## Skip tests if 'bind -X' is not supported 125 | if ! sc_repl_bind_has_option_X; then 126 | sc_repl_debug "sc_repl_assert_shell_command_keybinding_exists('${1}') ... done" 127 | return 0 128 | fi 129 | 130 | if ! bind -X | grep -q -F '"'"${1:?}"'":'; then 131 | sc_repl_debug_shell_command_keybindings 132 | sc_repl_error "No such shell-command keybinding: ${1}" 133 | sc_repl_debug "sc_repl_assert_shell_command_keybinding_exists('${1}') ... ERROR" 134 | return 1 135 | fi 136 | sc_repl_debug "sc_repl_assert_shell_command_keybinding_exists('${1}') ... OK" 137 | return 0 138 | } 139 | 140 | ## Function to check whether 'bind -P' is available 141 | sc_repl_bind_has_option_P() { 142 | bind -P &> /dev/null 143 | } 144 | 145 | sc_repl_assert_function_keybinding_exists() { 146 | sc_repl_debug "sc_repl_assert_function_keybinding_exists('${1}') ..." 147 | ## Skip tests if 'bind -P' is not supported 148 | if ! sc_repl_bind_has_option_P; then 149 | sc_repl_debug "sc_repl_assert_function_keybinding_exists('${1}') ... done" 150 | return 0 151 | fi 152 | 153 | if ! bind -P | grep -q -F '"'"${1:?}"'"'; then 154 | sc_repl_debug_function_keybindings 155 | sc_repl_error "No such function keybinding: ${1}" 156 | sc_repl_debug "sc_repl_assert_function_keybinding_exists('${1}') ... ERROR" 157 | return 1 158 | fi 159 | sc_repl_debug "sc_repl_assert_function_keybinding_exists('${1}') ... OK" 160 | return 0 161 | } 162 | 163 | ## Function to check whether 'bind -s' is available 164 | sc_repl_bind_has_option_s() { 165 | bind -s &> /dev/null 166 | } 167 | 168 | sc_repl_assert_sequence_keybinding_exists() { 169 | sc_repl_debug "sc_repl_assert_sequence_keybinding_exists('${1}') ..." 170 | ## Skip tests if 'bind -P' is not supported 171 | if ! sc_repl_bind_has_option_s; then 172 | sc_repl_debug "sc_repl_assert_sequence_keybinding_exists('${1}') ... done" 173 | return 0 174 | fi 175 | 176 | if ! bind -s | grep -q -F '"'"${1:?}"'":'; then 177 | sc_repl_debug_sequence_keybindings 178 | sc_repl_error "No such sequence keybinding: ${1}" 179 | sc_repl_debug "sc_repl_assert_sequence_keybinding_exists('${1}') ... ERROR" 180 | return 1 181 | fi 182 | sc_repl_debug "sc_repl_assert_sequence_keybinding_exists('${1}') ... OK" 183 | return 0 184 | } 185 | 186 | sc_repl_asserts() { 187 | sc_repl_debug "sc_repl_asserts() ..." 188 | 189 | sc_repl_assert_bash_version && 190 | sc_repl_assert_shellcheck && 191 | sc_repl_assert_readline_fcn_exists "accept-line" 192 | res=$? 193 | sc_repl_debug "sc_repl_asserts() ... done" 194 | return ${res} 195 | } 196 | 197 | sc_repl_verify_or_unbind() { 198 | local input 199 | local opts 200 | local skip_pattern 201 | local style 202 | local start_time 203 | local end_time 204 | local vars 205 | local tmp 206 | 207 | sc_repl_debug "sc_repl_verify_or_unbind() ..." 208 | 209 | ## Nothing to do? 210 | if [[ -z "$READLINE_LINE" ]]; then 211 | sc_repl_debug " - empty input" 212 | sc_repl_debug "sc_repl_verify_or_unbind() ... done" 213 | return 214 | fi 215 | 216 | ## Skip ShellCheck? Default is to skip with leading: 217 | ## * ^! (history expansion) 218 | ## * DOUBLESPACE$ (in-house rule) 219 | skip_pattern=${SHELLCHECK_REPL_SKIP_PATTERN:-(^\!|[[:space:]][[:space:]]$)} 220 | if [[ "$READLINE_LINE" =~ $skip_pattern ]]; then 221 | sc_repl_debug " - skip pattern matched: ${skip_pattern}" 222 | sc_repl_debug "sc_repl_verify_or_unbind() ... done" 223 | return 224 | fi 225 | 226 | opts=("--shell=bash" "--external-sources") 227 | if [[ -n "${SHELLCHECK_REPL_EXCLUDE}" ]]; then 228 | opts+=("--exclude=${SHELLCHECK_REPL_EXCLUDE}") 229 | fi 230 | # Option -C/--color requires ShellCheck (>= 0.4.2) 231 | if version_gt "${SHELLCHECK_VERSION}" 0.4.1; then 232 | opts+=("--color=always") 233 | fi 234 | # Option -S/--severity requires ShellCheck (>= 0.6.0) 235 | if version_gt "${SHELLCHECK_VERSION_X_Y}" 0.5; then 236 | opts+=("--severity=${SHELLCHECK_REPL_VERIFY_LEVEL:=info}") 237 | fi 238 | # Filter the output of shellcheck by removing filename 239 | style=${SHELLCHECK_REPL_INFO:-""} 240 | style=${style,,} 241 | if [[ -z "${style}" ]]; then style="clean"; fi 242 | sc_repl_debug " - style: ${style}" 243 | sc_repl_debug " - ShellCheck options: ${opts[*]}" 244 | sc_repl_debug " - READLINE_LINE: ${READLINE_LINE}" 245 | 246 | ## Identify variables to be included in preamble 247 | vars=() 248 | if [[ ! "$SHELLCHECK_REPL_EXCLUDE" == *2154* ]]; then 249 | sc_repl_debug " - Special case: Checking with rule SC2154" 250 | ## Ask shellcheck to identify variables of interest 251 | mapfile -t vars < <(shellcheck --shell=bash --format=gcc --exclude="${SHELLCHECK_REPL_EXCLUDE}" <(echo "$READLINE_LINE") | grep -F "[SC2154]" | sed -E 's/.*warning: ([^ ]+) .*/\1/') 252 | fi 253 | 254 | if [[ ! "$SHELLCHECK_REPL_EXCLUDE" == *2178* ]]; then 255 | sc_repl_debug " - Special case: Checking with rule SC2178" 256 | ## Ask shellcheck to identify variables of interest 257 | mapfile -t tmp < <(shellcheck --shell=bash --format=gcc <(echo "$READLINE_LINE") | grep -F "[SC2034]" | sed -E 's/.*warning: ([^ ]+) .*/\1/') 258 | vars+=("${tmp[@]}") 259 | fi 260 | 261 | sc_repl_debug " - Variables: [n=${#vars[@]}] ${vars[*]}" 262 | 263 | ## Are there any variables involved? 264 | if [[ ${#vars[@]} -gt 0 ]]; then 265 | ## 'declare -p' dump only those 266 | input=$(declare -p "${vars[@]}" 2> /dev/null) 267 | input=$(printf "#dummy to disable does not apply to everything\ntrue\n#shellcheck disable=all\n{\ntrue\n%s\n}\n\n%s\n" "${input}" "$READLINE_LINE") 268 | else 269 | input=$READLINE_LINE 270 | fi 271 | 272 | 273 | sc_repl_debug " - ShellCheck input: $(echo "${input}" | wc -l) lines" 274 | start_time=$(date +%s%N) 275 | case ${style} in 276 | raw-tty) 277 | shellcheck "${opts[@]}" --format=tty <(echo "${input}") 278 | ;; 279 | raw-gcc) 280 | shellcheck "${opts[@]}" --format=gcc <(echo "${input}") 281 | ;; 282 | full) 283 | shellcheck "${opts[@]}" <(echo "${input}") | tail -n +2 284 | ;; 285 | clean) 286 | shellcheck "${opts[@]}" <(echo "${input}") | sed -n '1,2b; /^$/q; p' 287 | ;; 288 | note) 289 | shellcheck "${opts[@]}" --format=gcc <(echo "${input}") | cut -d : -f 4- ;; 290 | *) 291 | sc_repl_error "Unknown value for shellcheck-repl variable 'SHELLCHECK_REPL_INFO' (valid values are 'raw', 'full', 'short' and 'clean' [default]): '${SHELLCHECK_REPL_INFO}'" 292 | ;; 293 | esac 294 | 295 | if [[ "${PIPESTATUS[0]}" != 0 ]]; then 296 | if ${SHELLCHECK_REPL_VERBOSE}; then 297 | >&2 echo 298 | >&2 echo "To skip a check, add its SC number to 'SHELLCHECK_REPL_EXCLUDE', e.g." 299 | >&2 echo 300 | >&2 echo " export SHELLCHECK_REPL_EXCLUDE=\"\${SHELLCHECK_REPL_EXCLUDE},4038\"" 301 | >&2 echo 302 | >&2 echo "Currently, SHELLCHECK_REPL_EXCLUDE=${SHELLCHECK_REPL_EXCLUDE}" 303 | >&2 echo 304 | >&2 echo "To skip ShellCheck validation for this call, append two spaces" 305 | >&2 echo 306 | fi 307 | 308 | sc_repl_assert_shell_command_keybinding_exists "\C-x\C-b1" 309 | 310 | ## Avoid Bash 5.1.* bug 311 | bind -r "\C-x\C-b2" 312 | sc_repl_assert_shell_command_keybinding_exists "\C-x\C-b1" 313 | 314 | ## Key sequence: {Ctrl-x Ctrl-b 2} 315 | ## Executes shell command: sc_repl_verify_bind_accept 316 | bind -x '"\C-x\C-b2": sc_repl_verify_bind_accept' 317 | sc_repl_assert_shell_command_keybinding_exists "\C-x\C-b2" 318 | sc_repl_assert_shell_command_keybinding_exists "\C-x\C-b1" 319 | fi 320 | 321 | end_time=$(date +%s%N) 322 | 323 | sc_repl_debug " - check time: $(((end_time - start_time) / 1000000)) ms" 324 | 325 | sc_repl_debug "sc_repl_verify_or_unbind() ... done" 326 | } 327 | 328 | sc_repl_verify_bind_accept() { 329 | sc_repl_debug "sc_repl_verify_bind_accept() ..." 330 | ## Avoid Bash 5.1.* bug 331 | bind -r "\C-x\C-b2" 332 | 333 | ## Key sequence: {Ctrl-x Ctrl-b 2} 334 | ## Executes function: accept-line 335 | bind '"\C-x\C-b2": accept-line' 336 | sc_repl_assert_function_keybinding_exists "\C-x\C-b2" 337 | sc_repl_debug "sc_repl_verify_bind_accept() ... done" 338 | } 339 | 340 | sc_repl_enable() { 341 | sc_repl_debug "sc_repl_enable() ..." 342 | 343 | sc_repl_verify_bind_accept 344 | 345 | ## Avoid Bash 5.1.* bug 346 | bind -r "\C-x\C-b1" 347 | bind -r "\C-m" 348 | 349 | ## Key sequence: {Ctrl-x Ctrl-b 1} 350 | ## Executes shell command: sc_repl_verify_or_unbind() 351 | bind -x '"\C-x\C-b1": sc_repl_verify_or_unbind' 352 | sc_repl_assert_shell_command_keybinding_exists "\C-x\C-b1" 353 | 354 | ## Key sequence: Ctrl-m (Carriage Return) 355 | ## Executes keystrokes: {Ctrl-x Ctrl-b 1} {Ctrl-x Ctrl-b 2} 356 | bind '"\C-m": "\C-x\C-b1\C-x\C-b2"' 357 | sc_repl_assert_sequence_keybinding_exists "\C-m" 358 | sc_repl_assert_shell_command_keybinding_exists "\C-x\C-b1" 359 | sc_repl_debug "sc_repl_enable() ... done" 360 | } 361 | 362 | sc_repl_disable() { 363 | sc_repl_debug "sc_repl_disable() ..." 364 | bind -r "\C-m" 365 | 366 | ## Key sequence: Ctrl-m (Carriage Return) 367 | ## Executes function: accept-line 368 | bind '"\C-m": accept-line' 369 | sc_repl_assert_function_keybinding_exists "\C-m" 370 | sc_repl_debug "sc_repl_disable() ... done" 371 | } 372 | 373 | sc_repl_init() { 374 | local defaults 375 | 376 | sc_repl_debug "sc_repl_init() ..." 377 | sc_version 378 | if ! sc_repl_asserts; then 379 | sc_repl_error "ShellCheck REPL startup assertions failed" 380 | return 1 381 | fi 382 | 383 | ## Ignore some ShellCheck issues: 384 | ## SC1001: This \= will be a regular '=' in this context. 385 | ## SC1044: Couldn't find end token `EOF' in the here document. 386 | ## SC1090: Can't follow non-constant source. Use a directive to specify 387 | ## location. 388 | ## SC1091: Not following: (error message here). 389 | ## SC1113: Use #!, not just #, for the shebang. 390 | ## SC2034: 'var' appears unused. Verify it or export it. 391 | ## SC2096: On most OS, shebangs can only specify a single parameter. 392 | ## SC2155: Declare and assign separately to avoid masking return values. 393 | ## SC2164: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. 394 | defaults=1001,1044,1090,1091,1113,2034,2096,2155,2164 395 | sc_repl_debug "- defaults: ${defaults}" 396 | SHELLCHECK_REPL_EXCLUDE=${SHELLCHECK_REPL_EXCLUDE:-${defaults}} 397 | sc_repl_debug "- SHELLCHECK_REPL_EXCLUDE: ${SHELLCHECK_REPL_EXCLUDE}" 398 | sc_repl_enable 399 | sc_repl_debug "sc_repl_init() ... done" 400 | } 401 | 402 | sc_wiki_url() { 403 | echo "https://github.com/koalaman/shellcheck/wiki/$1" 404 | } 405 | 406 | if ! sc_is_interactive; then 407 | sc_repl_error "ShellCheck REPL works only in interactive mode and not in batch mode" 408 | exit 1 409 | fi 410 | 411 | ## Deprecation warning 412 | if [[ -n ${SHELLCHECK_REPL_INIT} ]]; then 413 | sc_repl_error "SHELLCHECK_REPL_INIT is defunct. Use SHELLCHECK_REPL_ACTION=init or SHELLCHECK_REPL_ACTION=none instead" 414 | fi 415 | 416 | case ${SHELLCHECK_REPL_ACTION:-"enable"} in 417 | disable) 418 | sc_repl_disable "";; 419 | enable) 420 | sc_repl_init "";; 421 | sessioninfo) 422 | sc_repl_sessioninfo;; 423 | *) 424 | sc_repl_error "Unknown value on SHELLCHECK_REPL_ACTION: '${SHELLCHECK_REPL_ACTION}'" 425 | esac 426 | --------------------------------------------------------------------------------