hxwalker
hxwalker

Reputation: 115

How to prevent execution of command in ZSH?

I wrote hook for command line:

# Transforms command 'ls?' to 'man ls'

function question_to_man() {
    if [[ $2 =~ '^\w+\?$' ]]; then 
        man ${2[0,-2]}
    fi
}

autoload -Uz add-zsh-hook

add-zsh-hook preexec question_to_man

But when I do:

> ls?

After exiting from man I get:

> zsh: no matches found: ls?

How can I get rid of from message about wrong command?

Upvotes: 3

Views: 1547

Answers (1)

Adaephon
Adaephon

Reputation: 18399

? is special to zsh and is the wildcard for a single character. That means that if you type ls? zsh tries find matching file names in the current directory (any three letter name starting with "ls").

There are two ways to work around that:

  1. You can make "?" "unspecial" by quoting it: ls\?, 'ls?' or "ls?".

  2. You make zsh handle the cases where it does not match better:

    The default behaviour if no match can be found is to print an error. This can be changed by disabling the NOMATCH option (also NULL_GLOB must not be set):

    setopt NO_NOMATCH
    setopt NO_NULL_GLOB
    

    This will leave the word untouched, if there is no matching file.

    Caution: In the (maybe unlikely) case that there is a file with a matching name, zsh will try to execute a command with the name of the first matching file. That is if there is a file named "lsx", then ls? will be replaced by lsx and zsh will try to run it. This may or may not fail, but will most likely not be the desired effect.

Both methods have their pro and cons. 1. is probably not exactly what you are looking for and 2. does not work every time as well as changes your shells behaviour.


Also (as @chepner noted in his comment) preexec runs additionally to not instead of a command. That means you may get the help for ls but zsh will still try to run ls? or even lsx (or another matching name).

To avoid that, I would suggest defining a command_not_found_handler function instead of preexec. From the zsh manual:

If no external command is found but a function command_not_found_handler exists the shell executes this function with all command line arguments. The function should return status zero if it successfully handled the command, or non-zero status if it failed. In the latter case the standard handling is applied: ‘command not found’ is printed to standard error and the shell exits with status 127. Note that the handler is executed in a subshell forked to execute an external command, hence changes to directories, shell parameters, etc. have no effect on the main shell.

So this should do the trick:

command_not_found_handler () {
    if [[ $1 =~ '\?$' ]]; then
        man ${1%\?}
        return 0
    else
        return 1
    fi
}

If you have a lot of matching file names but seldomly misstype commands (the usual reason for "Command not found" errors) you might want to consider using this instead:

command_not_found_handler () {
    man ${1%?}
}

This does not check for "?" at the end, but just cuts away any last character (note the missing "\" in ${1%?}) and tries to run man on the rest. So even if a file name matches, man will be run unless there is indeed a command with the same name as the matched file.

Note: This will interfere with other tools using command_not_found_handler for example the command-not-found tool from Ubuntu (if enabled for zsh).


That all being said, zsh has a widget called run-help which can be bound to a key (in Emacs mode it is by default bound to Alt+H) and than runs man for the current command.

The main advantages of using run-help over the above are:

  1. You can call it any time while typing a longer command, as long as the command name is complete.
  2. After you leave the manpage, the command is still there unchanged, so you can continue writing on it.

You can even bind it to Alt+? to make it more similar: bindkey '^[?' run-help

Upvotes: 5

Related Questions