DEV Community

Martin Frost
Martin Frost

Posted on

Fish-style abbreviations in Zsh

A few years ago, I tried using The Friendly Interactive SHell as my default shell.

It has a lot of nice things, like syntax highlighting on the command line, reverse-history-search by pressing the up/down arrows, and a few more things, but it kept rubbing me the wrong way that it is not POSIX compatible (like bash or zsh), so I ended up going back to zsh.

I did, however, bring a few features with me because they are awesome, and abbreviations is one of those features. They are sort of like aliases but better, at least in my opinion.

Abbreviations vs aliases

In all shells there is something called aliases.
Aliases work like this, you define some alias, like alias gst="git status". After this, if I type gst into my terminal and hit enter, the terminal will run the git status command and display the output as expected.

In fish, I can also define an abbreviation like this:

abbr -a -g gst git status
Enter fullscreen mode Exit fullscreen mode

It works mostly the same way as an alias but with one very important exception: if I type gst in my terminal and hit space, the terminal will expand the abbreviation. If I hit enter instead of space, it will expand the abbreviation and run the command.

This means that it's easier to see what commands have been run by looking at the history and seeing a bunch of lines with git status instead of just gst, and it also means that I can copy commands that I ran directly from my terminal, send them to a collegue, and they can just paste and run them directly if they want to. They don't need to know anything about what aliases I happen to have in my shell config.

How to get this in Zsh?

I simply add these functions in my ~/.zshrc and then I can start listing abbreviations:

# declare a list of expandable aliases to fill up later
typeset -a ealiases
ealiases=()

# write a function for adding an alias to the list mentioned above
function abbrev-alias() {
    alias $1
    ealiases+=(${1%%\=*})
}

# expand any aliases in the current line buffer
function expand-ealias() {
    if [[ $LBUFFER =~ "\<(${(j:|:)ealiases})\$" ]]; then
        zle _expand_alias
        zle expand-word
    fi
    zle magic-space
}
zle -N expand-ealias

# Bind the space key to the expand-alias function above, so that space will expand any expandable aliases
bindkey ' '        expand-ealias
bindkey '^ '       magic-space     # control-space to bypass completion
bindkey -M isearch " "      magic-space     # normal space during searches

# A function for expanding any aliases before accepting the line as is and executing the entered command
expand-alias-and-accept-line() {
    expand-ealias
    zle .backward-delete-char
    zle .accept-line
}
zle -N accept-line expand-alias-and-accept-line
Enter fullscreen mode Exit fullscreen mode

Then I can start adding abbreviations like so:

abbrev-alias g="git"
abbrev-alias gst="git status"
abbrev-alias gcb="git checkout --branch"
abbrev-alias ll="ls -l"
Enter fullscreen mode Exit fullscreen mode

Top comments (6)

Collapse
 
axlefublr profile image
Axlefublr

Created an account just to reply to this. Incredible! Tried out fish just now and had a horrendous experience trying to set things up. The only positive of fish for me were the abbreviations. I always write out stuff like dotnet new sln -n and so on, because relying on aliases too much might make me forget the actual command lol. Now with this code, I can continue using zsh while having the amazing feature of abbreviations, thanks to you and to noahdotpy for sharing!

Collapse
 
noahdotpy profile image
noahdotpy

Amazing! I'm going full ZSH babyyy.

Collapse
 
niranjangopal profile image
Niranjan Gopal

Thank you for this amazing feature, a good friend recommended this, now I don't use any alias only abbrev-alias 😊

Collapse
 
noahdotpy profile image
noahdotpy • Edited

I will just be changing this a bit so that I can do <command> to cancel expand just like I can when I do aliases. Here is my changed code that I did to achieve this. I also added so that it makes a variable so that if you want, you can do something like (with abbrev-alias $nvimconf="$HOME/.config/nvim/") cd $nvimconf

New code:

# declare a list of expandable aliases to fill up later
typeset -a ealiases
ealiases=()

# write a function for adding an alias to the list mentioned above
function abbrev-alias() {
    alias $1
    export $1
    ealiases+=(${1%%\=*})
}

# expand any aliases in the current line buffer
function expand-ealias() {
    if [[ $LBUFFER =~ "\<(${(j:|:)ealiases})\$" ]] && [[ "$LBUFFER" != "\\"* ]]; then
        zle _expand_alias
        zle expand-word
    fi
    zle magic-space
}
zle -N expand-ealias

# Bind the space key to the expand-alias function above, so that space will expand any expandable aliases
bindkey ' '        expand-ealias
bindkey -M isearch " "      magic-space     # normal space during searches

# A function for expanding any aliases before accepting the line as is and executing the entered command
expand-alias-and-accept-line() {
    expand-ealias
    zle .backward-delete-char
    zle .accept-line
}
zle -N accept-line expand-alias-and-accept-line
Enter fullscreen mode Exit fullscreen mode
Collapse
 
swindlesmccoop profile image
Swindles McCoop

Yo, thank you!!!!!!!! This works perfectly! Abbreviations were the last things keeping me on fish, now I'm permanently going to zsh.

Collapse
 
frost profile image
Martin Frost

Nice to hear. I’m happy that it helped someone else :)