1

I'm trying to modify the behaviour of the Zsh (or Bash) shell to automatically prepend a custom command, named myapp for example, to all input before it's executed. Essentially, I want to intercept and modify the user's input, and when they press the ENTER_KEY, the modified command should be executed.

Any command I type in the shell, for instance:

grep -rn hello

Should be processed as if I typed

$ myapp grep -rn hello

Another example, if I typed ls, it should be executed as myapp ls.

GOAL

I want to try to use vim as viewer automatically for some cli-tools:

# myapp
vim -c "term $*"
3

2 Answers 2

1

Considering the Goal, The easiest solution would be to pipe the result of your command to vim like below.

$ yourcommand | vim -

For example,

$ grep -rn hello | vim -
1
  • no one happy with write this pipe evey everytime, or make allias to anything.
    – nextloop
    Oct 17, 2023 at 16:24
1

I will assume you will want to enter and exit out of this arrangement so I made the following. For zsh, the DEBUG trap handler can skip the execution of a submitted command by setting the ERR_EXIT option, and instead run your custom command

# this is run before every command
debug_trap() {
    # get last submitted command
    cmd="${history[$HISTCMD]}";
    if [ "$cmd" ]; then
        # catch the unwrap command and remove the trap
        if [ "$cmd" = "unwrap" ]; then
            print 'Unwrapping command-line';
            trap - DEBUG;
            return;
        fi
        # make sure multiple trap triggers only handle this once
        if [ "$handled" != "$HISTCMD;$cmd" ]; then
            # when either history index or command text
            # changes, we can assume its a new command
            handled="$HISTCMD;$cmd";
            # do whatever with $cmd
            myapp $cmd;
        fi
        # optionally skip the raw execution
        setopt ERR_EXIT;
    fi
}

# start the debug trap
wrap() {
    print 'Wrapping command-line';
    trap 'debug_trap' DEBUG;
}

# this is just defined in order to avoid errors
# the unwrapping happens in the trap handler
unwrap() {}

Something equivalent in bash:

# bash requires this option in order to skip execution
# inside the debug trap
shopt -s extdebug;

# this is run before every command
debug_trap() {
    cmd="$BASH_COMMAND";
    # catch the unwrap command and remove the trap
    # notice how the alias is expanded here and
    # the command is no longer 'unwrap'
    if [[ "$cmd" == 'trap - DEBUG' ]]; then
        echo 'Unwrapping command-line';
        # the trap is unset by the submitted command
    else
        # do whatever with $cmd
        myapp $cmd;
        # optionally skip the raw execution
        return 1;
    fi
}

# start the debug trap
wrap() {
    echo 'Wrapping command-line';
    trap debug_trap DEBUG;
}

# we can't unset global traps inside a function
# so we use an alias instead
alias unwrap='trap - DEBUG';

Usage example:

> myapp() { echo "### $1"; }
> wrap
Wrapping command-line
> echo 123
### echo 123
> unwrap
Unwrapping command-line
> echo 123
123

To achieve the vim functionality, use your 'vim -c' in the myapp function accordingly or change the 'myapp $cmd;' line in the trap function.

A warning before using, however. Debug traps are finicky and I've tried to reduce bugs with the zsh version. Using plugins like oh-my-*sh can introduce hooks, traps and other mechanics that greatly increase the difficulty of achieving a stable implementation. The zsh version is tested against oh-my-zsh and should work. The bash version is only tested on bash v4.2 without adornments.

Other potential implementations in zsh: You may override the default zle widget 'accept-line' bound to the return-key that can manipulate your text-buffer before submitting it for execution, which might be a cleaner solution. There's also the 'preexec' hook function wich may be useful, however it seems it has no method of changing or skipping the command by itself.

Other usage ideas: You can use this to send (ssh) the commands to one or more remote machines, write them to a script file and defer execution, do static analysis before executing, ask for confirmation, or ask to fix typos.

2
  • That i need, thanks for your great job, i think you spend much time to helps me, i appreciated. Could you change the myapp function to myapp() { echo "### $1"; } for who want to copy pasting.
    – nextloop
    Nov 16, 2023 at 23:11
  • This function with your solution is very cool, myapp(){nvim -c 'set buftype=nofile' -c "r \! $*";};. ALL OUTPUT NOW WILL BE SEEING IN VIM BUFFER
    – nextloop
    Nov 16, 2023 at 23:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .