Shell tricks: shorthands

Even with tab completion, typing long commands is tedious. But, there’s something even worst: typing the same long commands again, and again, and again… So how do you solve that? It’s simple: you shorten them. Surprising, uh? Okay enough theory, let me show you some examples.

Here’s a tedious command of Type-A:

% sudo aptitude install zsh

Look at it carefully since you will need to hunt these long commands down until none remains. Now, let me explain how you execute a such command. Open up your personal shell initialization file (e.g. ~/.bashrc for Bash, ~/.zshrc for Zsh, etc). Then, add the following:

alias spkgi="sudo aptitude install"

Reload your shell and finally, enjoy:

% spkgi zsh

Now I can introduce, as you can deduce, other shorten commands that you can produce and reproduce:

# Package Management
alias pkg="aptitude"
alias spkg="sudo aptitude"
alias spkgi="sudo aptitude install"
alias spkgu="sudo aptitude safe-upgrade"
alias spkgr="sudo aptitude remove"
alias spkgd="sudo apt-get build-dep"

# Miscellaneous Helpers
alias nc="rlwrap nc"
alias e=$EDITOR
alias se=sudoedit
alias reload="source ~/.zshrc"
alias g=egrep

Next after Type-A tedious commands, we have the Type-S ones. To execute these, you will you need some sort of special shell support. So, here’s some examples of the Type-S monstrosity:

% find Lib/ -name '*.c' -print0 | xargs -0 grep ^PyErr
% find -name '*.html' -print0 | xargs -0 rename 's/\.html$/.var/'
% find -name '*.patch' -print0 | xargs -0 -I {} cp {} patches/

I hope you start to see some patterns (if you don’t, then try harder). The first one could (and should) be rewritten as:

% rgrep --include='*.c' ^PyErr Lib/

But that isn’t short enough for me, so I have a short helper:

rg()
{
    filepat="$1"
    pat="$2"
    shift 2
    grep -Er --include=$filepat $pat ${@:-.}
}
# In Zsh, 'noglob' turns off globing.
# (e.g, "noglob echo *" outputs "*")
alias rg='noglob rg'

It is lovely to use:

% rg *.c ^PyErr Lib/
% rg *.c PyErr_Restore . -C 10 | less
% rg *.[ch] stringlib
% rg *.c ^[a-zA-Z]*_dealloc Modules/ Objects/

The second example is quite similar to the previous one. However, the find/rename combination is much less common (at least for me) than the find/grep one. This one needs to be broken in pieces. One obvious thing to factor out is the find -name with an alias:

alias fname="noglob find -name"

Using this alias, you can rewrite the second example as:

% fname *.html -print0 | xargs -0 rename 's/\.html$/.var/'

It’s better, but it’s not short enough yet. The ugly part of this command is the -print0 | xargs -0. I hate to type that. Wouldn’t it be nice if we could define an alias for it? How about:

alias each="-print0 | xargs -0"

Unfortunately, that doesn’t work since aliases are only expanded if they are in the command position. Luckly, Zsh has that neat feature called global aliases, which does exactly what we want.

alias -g each="-print0 | xargs -0"

With this feature of Zsh, the second example become:

% fname *.html each rename 's/\.html$/.var/'

Now, we can also attack the third one:

% fname *.patch each -I {} cp {} patches/

It is possible to shorten a bit by defining another alias combining each and -I {}, but that won’t make a big difference.

Finally, there are the Type-R tedious commands. These are hard to avoid, unless you’re careful. Here’s again some ridiculous examples to help you recognize these redundant commands:

% gcc -o stackgrow stackgrow.c
% pkg show emacs-snapshot-bin-common emacs-snapshot-common emacs-snapshot-gtk emacs-snapshot
% cat ../lispref.patch ../lwlib.patch ../etc.patch | patch -p1

To reduce these, you don’t need change your shell configuration; you change your habits instead. Using alternations (which are non-standard, but supported by most shells), you can rewrite the two first example as:

% gcc -o stackgrow{,.c}
% pkg show emacs-snapshot{{-bin,}-common,-gtk,}

Now, you are surely asking yourself: “what is different about the third one?” Well, think about it. Got it? No? Ah, come on, it is easy. Here’s a hint:

% echo 'cat ../{lispref,lwlib,etc}.patch | patch -p1' | wc -c
45
% echo 'cat ../lispref.patch ../lwlib.patch ../etc.patch | patch -p1' | wc -c
61

You like my hint, don’t you? Here’s the answer:

% echo 'cat ../li\t ../lw\t ../et\t | patch -p1' | wc -c
37

Tab completion doesn’t work well with prefix alternations. Even if the command using alternation is shorter, it still doesn’t beat good old tab completion.

And that’s all folks. I surely have plenty of other tricks to show, but that will be for the other posts of this short series.

· RSS feed for comments on this post

7 Comments

  1. Matt Galvin said,

    October 18, 2007 @ 11:47 am

    Great post!

    Something I like to do with Type-A commands is rather then an alias of spkgi, I use an alias of install, upgrade, remove, etc… simple verbs that even less experienced users would easily understand how to use. So we get:

    install banshee install apache2

    and the like. Very easy to understand and remember, IMHO.

  2. bartman said,

    October 18, 2007 @ 11:50 am

    Looks like you’re missing the ‘**’ zsh trick.

    % find Lib/ -name '*.c' -print0 | xargs -0 grep ^PyErr

    can be rewritten as

    % grep \^PyErr Lib/**/*.c

    With ** zsh will do all the hard work of finding the files. Note however that I had to escape ^, because in zsh it means “match everything but …”. Alternatively you can put your match expression in quotes.

  3. Zamber said,

    October 18, 2007 @ 12:41 pm

    Send a info to Lifehacker.com! A couple months ago they had some tweaking like this described but it wasn’t so advanced ;) .

  4. Pete said,

    October 18, 2007 @ 1:44 pm

    One feature I really miss in *buntu is history-search-backward/forward in bash. This is where you are able to start typing a command, press page up or down (default keys I like) and it completes it with commands from your history which match what you’ve typed so far.

    System wide way to enable it is to un-comment the two lines shown below,

    “e[5~”: history-search-backward

    “e[6~”: history-search-forward

    Great feature, not sure why its disabled by default!

  5. Azrael Nightwalker said,

    October 19, 2007 @ 5:43 am

    Use wajig and you won’t have to set up many of those aliases.

  6. Alexandre said,

    October 19, 2007 @ 8:25 am

    bartman, Zsh’s extended globing feature is kinda neat, but it sucks for searching through many files:

    % grep ^malloc src/**/*.[ch]
    zsh: argument list too long: grep
    

    It is basically equivalent to:

    % grep ^malloc `find src/ -name '*.[ch]'`
    

    Personally, I like to keep the amount of meta-characters at a minimum. So, I would prefer using the find version.

  7. Alexandre said,

    October 19, 2007 @ 8:36 am

    Azrael, I think you missed the point of my aliases. It is not for making the command easier to remember, but simply to make them shorter and easier to type. Although, I have to admit wajig looks like neat little wrapper. Thanks.