aliaszshzshrc

Print executed alias in zsh or bash


The scenario is currently, I have defined some aliases in .zshrc like

alias gco='git checkout'
alias cdp='cd ..'

and lots like that. My question is How to print out the command each time I typed an alias and press enter?

ex:

$> gco master
> Command: git checkout master
> Git process ...

something like that, if the solution also works in bash would be better! Thanks!


Solution

  • This is a neat question. We can do it by defining a couple of functions to expand out the aliases, and then use a preexec hook to run the functions before we execute them.

    I've taken the answer from here.


    1. Evaluate all the aliases

    _aliases="$(alias -Lr 2>/dev/null || alias)"
    
    alias_for() {
      [[ $1 =~ '[[:punct:]]' ]] && return
      local found="$( echo "$_aliases" | sed -nE "/^alias ${1}='?(.+)/s//\\1/p" )"
      [[ -n $found ]] && echo "${found%\'}"
    }
    

    First, store all aliases in a variable. alias -r prints all the regular aliases (not global or suffix), and alias -L prints them "in a manner suitable for use in startup scripts". The alias_for() function does some cleaning, removing quotes and putting alias in front of the lines. When we do echo ${_aliases}, we get something like this:

    alias history='fc -l 1'
    alias ls='ls -F -G'
    alias lsdf='ls -1l ~/.*(@)'
    alias mv='mv -v'
    

    Compare this to the output of alias:

    history='fc -l 1'
    ls='ls -F -G'
    lsdf='ls -1l ~/.*(@)'
    mv='mv -v'
    

    2. Function to check if there was an alias entered.

    If there was an alias entered, we can now detect it, and thus print it:

    expand_command_line() {
      [[ $# -eq 0 ]] && return         # If there's no input, return. Else... 
      local found_alias="$(alias_for $1)"    # Check if there's an alias for the comand.
      if [[ -n $found_alias ]]; then         # If there was
        echo ${found_alias}                  # Print it. 
      fi
    }
    

    3. Getting this to run every time a command is entered

    The preexec function is perfect for this. It's a function that is:

    Executed just after a command has been read and is about to be executed. If the history mechanism is active (and the line was not discarded from the history buffer), the string that the user typed is passed as the first argument, otherwise it is an empty string. The actual command that will be executed (including expanded aliases) is passed in two different forms: the second argument is a single-line, size-limited version of the command (with things like function bodies elided); the third argument contains the full text that is being executed.

    from the zsh Manual, chapter 9.

    Note, we could probably just use the preeexec function to display what's being run.

    To add our function to the preexec, we use a hook using this example:

    autoload -U add-zsh-hook        # Load the zsh hook module. 
    add-zsh-hook preexec expand_command_line      # Adds the hook 
    

    To remove the hook later, we can use:

    # add-zsh-hook -d preexec expand_command_line # Remove it for this hook.
    

    My shell

    This is what my shell looks like when I run it:

    $ 1
    cd -
    $ rake
    bundle exec rake
    ^C
    $ chmod
    usage:  chmod [-fhv] [-R [-H | -L | -P]] [-a | +a | =a  [i][# [ n]]] mode|entry file ...
        chmod [-fhv] [-R [-H | -L | -P]] [-E | -C | -N | -i | -I] file ...
    $ git lg1
    fatal: Not a git repository (or any of the parent directories): .git
    

    Bugs (or 'features')

    As we can see from my shell example, when a command that is not aliased is run (like chmod), the full command is not displayed. When an aliased command (like 1 or rake) is run, the full command is displayed.

    When a git alias is run (git lg1, for example), the git alias is not expanded. If you look at my first link, the full example there does use git alias expansion - you should take that and modify if git aliases are vital to you.