Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IDEA: Prevent incorrect CLI calls by automatic ShellCheck validation (REPL) #1535

Closed
1 of 2 tasks
HenrikBengtsson opened this issue Mar 26, 2019 · 4 comments
Closed
1 of 2 tasks

Comments

@HenrikBengtsson
Copy link

For new checks and feature suggestions

I'm a big user of ShellCheck (thanks - it's brilliant) and it just struck me: would it be possible to run ShellCheck on the fly at the Bash prompt preventing incorrect calls from being evaluated?

For example,

$ rm  $files
      ^-- SC2086: Double quote to prevent globbing and word splitting.
ERROR: Command not evaluated because of above ShellCheck issue

$ rm "$files"

$

Is that possible to in the Bash read-eval-print loop (REPL)? ... in other shells?

@xPMo
Copy link

xPMo commented Mar 28, 2019

I've been mulling this over for a few days. I've been meaning to learn how to write bash readline widgets, and well, here I am!

This overwrites the default enter (\C-m) binding to run \C-x\C-b1\C-x\C-b2.
The reason for this method is that bash widgets can do exactly one of: press keys, call external commands, or call readline commnads. So, we press a bunch of keys, which we then bind to other commands.

  • \C-x\C-b1 is bound to the checking function. If the check fails, it will rebind \C-x\C-b2 to a function to bind it to accept-line.
  • \C-x\C-b2 is bound by default to accept-line (i.e.: run the command). When shellcheck succeeds, it will stay that way.

I recommend adding a bind '"\C-j": accept-line' or similar so you can bypass the check if need be.

sc_verify_or_unbind(){
	shellcheck -S "${SC_VERIFY_LEVEL:=info}" -s bash -x \
		--exclude=2154 \
		<(printf '%s\n' "$READLINE_LINE") ||
		bind -x '"\C-x\C-b2": sc_verify_bind_accept'
}
sc_verify_bind_accept(){
	bind '"\C-x\C-b2": accept-line'
}
sc_verify_bind_accept
bind -x '"\C-x\C-b1": sc_verify_or_unbind'
bind '"\C-m":"\C-x\C-b1\C-x\C-b2"'

TODO: do some awk or something to shellcheck's output so that it points to the right place.


EDIT: Added --exclude=2154 since shellcheck doesn't know what variables are set.

@HenrikBengtsson
Copy link
Author

Thank you - this is brilliant. I'll take it for a ride locally and see if there's anything that needs to be tweaked. I'm forced to use older versions of ShellCheck on different systems (e.g. Ubuntu 18.04 is still ShellCheck 0.4.6) and noticed that -S was introduced in 0.6.0 (2018-12-02). So, I tweaked your code to be:

#!/usr/bin/env bash

## Source: https://github.com/koalaman/shellcheck/issues/1535
function sc_version() {
    if [ -z "${SHELLCHECK_VERSION+x}" ]; then
        # Example: '0.4.6'
        SHELLCHECK_VERSION=$(shellcheck --version | grep version: | sed -E 's/version:[ ]+//')
        # Example: '0.4'
	SHELLCHECK_VERSION_X_Y="${SHELLCHECK_VERSION%.*}"
    fi
}

function version_gt() {
    test "$(printf '%s\n' "$@" | sort --version-sort | head -n 1)" != "$1"
}

function sc_repl_verify_or_unbind() {
    local opts=("--shell=bash" "--external-sources")
    if [ ! -z "${SHELLCHECK_REPL_EXCLUDE+x}" ]; then
        opts+=("--exclude=${SHELLCHECK_REPL_EXCLUDE}")
    fi
    # Option -S/--severity requires ShellCheck (>= 0.6.0)
    if version_gt "${SHELLCHECK_VERSION_X_Y}" 0.5; then
        opts+=("--severity=\"${SC_VERIFY_LEVEL:=info}\"")
    fi
    shellcheck "${opts[@]}" <(printf '%s\n' "$READLINE_LINE") ||
        bind -x '"\C-x\C-b2": sc_repl_verify_bind_accept'
}

function sc_repl_verify_bind_accept() {
    bind '"\C-x\C-b2": accept-line'
}


function sc_repl_setup() {
    SHELLCHECK_REPL_EXCLUDE=2154
    sc_version
    sc_repl_verify_bind_accept
    bind -x '"\C-x\C-b1": sc_repl_verify_or_unbind'
    bind '"\C-m":"\C-x\C-b1\C-x\C-b2"'
}


sc_repl_setup

@xPMo
Copy link

xPMo commented Mar 30, 2019

Even though I'm not going to be using this, I figure I'd throw some feedback your way.

SHELLCHECK_VERSION=$(shellcheck --version | grep version: | sed -E 's/version:[ ]+//')

No need for grep | sed:

SHELLCHECK_VERSION="$(shellcheck --version | sed -nE 's/version: +(.+)/\1/p')"

You also probably don't want to pass null exclude value, so I'd drop the ${ +x} and just do

if [[ -n "${SHELLCHECK_REPL_EXCLUDE}" ]]; then

I'd change SC_VERIFY_LEVEL to SHELLCHECK_REPL_SEVERITY or similar to go with the rest of your variables. Also, using function can reduce compatibility with other versions of bash.

HenrikBengtsson pushed a commit to HenrikBengtsson/shellcheck-repl that referenced this issue Mar 30, 2019
HenrikBengtsson pushed a commit to HenrikBengtsson/shellcheck-repl that referenced this issue Mar 30, 2019
* No need for grep | sed
* don't want to pass null exclude value, so I'd drop the ${ +x}
* Harmonize env variables
* Drop 'function' to support older versions of Bash, cf. https://github.com/dylanaraps/pure-bash-bible#function-declaration
@HenrikBengtsson
Copy link
Author

HenrikBengtsson commented Mar 30, 2019

Thank you for this feedback - learning new things every day ... just hope I'm still learning faster than I forget ;) I've incorporated it. So, I ended up setting up

https://github.com/HenrikBengtsson/shellcheck-repl

to host the code etc.

PS. I'm closing this one to prevent more "noise" hitting this repos. Anyone interested, feel free to continue any discussions in the 'shellcheck-repl' repos.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants