Hacker News new | past | comments | ask | show | jobs | submit login
Bash Shortcuts For Maximum Productivity (skorks.com)
181 points by pooriaazimi on Sept 11, 2011 | hide | past | web | favorite | 56 comments



The things he calls "bang commands" are actually "event designators" (!!, !blah), "word designators" ($) and "word modifiers" (:p). There are links to docs for each at the bottom of this page: http://www.gnu.org/s/bash/manual/html_node/History-Interacti...

I use them all the time, and anyone watching over my shoulder always asks what that was. Learn !!, !$, and a few modifiers. You'll be glad you did. Personally, I use !$ constantly (and variations like !-2$, which gets you the last arg from 2 commands ago). The "h" modifier is handy, too. It's like dirname for the word. Consider this example, where you copy a file to some deep path, and then cd to that dir to continue working with the copy:

    $ cp foo.txt /some/really/long/destination/bar.txt
    $ cd !$:h
    $ # (do more with ./bar.txt)
The substitution (:s) and global substitution (:gs) modifiers are also useful:

    $ echo some args
    some args
    $ !!:s/some/more
    more args
(Also, note that his explanation of !* is actually incorrect. He says it leaves off the last arg, but actually leaves out $0 (the command name). !* is all of the previous command's arguments.)

Finally, one really useful thing he doesn't mention at all is brace expansion. For example, these are equivalent:

    $ cp foo.conf foo.conf.bak
    $ cp foo.conf{,.bak}
http://www.gnu.org/s/bash/manual/html_node/Brace-Expansion.h...


I used to use !$ all the time, but these days I tend to go for M-. which inserts the last argument interactively, and cycles through the last argument of all previous commands (but you don't get modifiers like !$:h).

http://www.gnu.org/s/bash/manual/html_node/Commands-For-Hist...

M-C-y (yank-nth-arg) works similarly for the first argument of the previous command, but without cycling through all previous commands.

After the more complex modifiers like !$:p, :e, :s/old/new/, etc, before hitting Return I often use M-C-e (shell-expand-line) just to check I got it right.

http://www.gnu.org/s/bash/manual/html_node/Miscellaneous-Com...


Thank you! The backup tip was great. I never thought of using expansion like that...

You post inspired me to quickly create two (hopefully) handy bash functions:

  # Create a backup of the given file(s)
  # USAGE: bk file1 file2 file3
  #        -> result: file1.bak file2.bak file3.bak
  bk() {
  for file in "$@"
    do
      cp $file{,.bak}
    done
  }

  # Restores the file from backup
  # USAGE: rbk file.bak OR rbk file
  #        -> result: replaces file with file.bak
  rbk() {
    if [[ $1 == *.bak ]]
    then
      backup_file=$1
      old_file=${backup_file:0:$((${#backup_file}-4))}
    else
      old_file=$1
      backup_file=${old_file}".bak"
    fi

    read -p "Replace the old backup? (y/N) " -n 2
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
      `cp $backup_file $old_file`
    fi
  }
I've put them in my newly created .functions file (https://github.com/pooriaazimi/dotfiles/blob/master/.functio...).


A neat thing about csh/tcsh is that you can use history expansion with aliases. There are some legacy aliases at work that allow SQL queries from the command line using this:

  $ alias select='sqlcommand -s "select !*"'
would translate:

  $ select * from tablename
to:

  $ sqlcommand -s "select * from tablename"
(Note: I may be getting the specifics messed up as it's been a couple of years since I looked at the implementation, but the general idea is there.)


A small change that made it much easier for me to use ! commands was enabling magic space, which expands the ! macros out when you press space. This made an enormous difference to me, since I could now be sure I wasn't accidentally completing the wrong command or arguments.

To enable it, edit your .inputrc (this is part of readline) and add this:

  $if Bash
    Space: magic-space
  $endif


!! and !$ are alright, but I really dislike !blah, which is blindly executing the latest command starting with blah, whatever it is. The author of the article should remove this "tip", it is bad advice, or at least make a note that the !blah:p is highly preferable.


I prefer using Ctrl-r <blah>, which searches your history for 'blah' and puts it on the command line. The preferable part is that you can keep hitting Ctrl-r to see previous matches, which is often helpful since you don't want the last match, but the second or third match.


I am using follow function for cd which does basically the same though i can iterate through the last arguments with alt+.

    cd () {
	if [[ -f $1 ]]
 	then
		builtin cd $1:h
    	else
    		builtin cd "$@"
    	fi
    }


A note to Mac users: Instead of Alt, you must press escape key. For example, Esc+b moves the cursor backward one word in bash, but you'll probably want to use option instead, so check 'Use option as meta key' in Terminal's settings (under keyboard tab).

But if it's not very handy, you can set 'option+left arrow' to move cursor one word backward. To do that, open Terminal's settings, go to keyboard and add a shortcut for left arrow (with 'option' as modifier), and leave the action to 'send string to shell'. Then click on the textbox below that and press esc (which should print '\033') and then 'b', or simply type '\033b' in the textbox.

Just this simple shortcut makes Mac's Terminal 1000 times better.


Thanks very, very much. I'm embarrassed to admit I probably spend 3-5 hours a day on a command line, and have just gotten used to set -o vi when I need to bounce around long lines a lot. esc+f/esc+b were always awkward. It should have occurred to me to just map the keys on Terminal.app!

In the interesting news department - Lion already has option left cursor/right cursor already mapped to move a word forward and back - not sure if it was there in snow leopard.


I am iTerm user so for those folks here is the setup you need to make "option" key the 'meta' key in iTerm

http://stackoverflow.com/questions/196357/making-iterm-to-tr...

I still don't like it since I am so used to hitting the Apple key in emacs, but it beats having to use Esc+<letter>


Totally agreed.

I also have 'option+right arrow' mapped to move cursor one word forward (use the same instructions as above, substituting 'f' for 'b').


I am a huge fan of maximizing productivity in the shell. I wrote these three articles a while ago:

Bash emacs-like keyboard shortcuts:

http://www.catonmat.net/blog/bash-emacs-editing-mode-cheat-s...

Bash vi-like keyboard shortcuts:

http://www.catonmat.net/blog/bash-vi-editing-mode-cheat-shee...

Definitive bash guide to command line history:

http://www.catonmat.net/blog/the-definitive-guide-to-bash-co...

And they all come with cheat sheets. See downloads box at the bottom of each post for download links.


Great articles. I'm just starting to get into the hang of vi. I'm giving vi-mode in bash a go (with "set -o vi") but there doesn't seem to be any indication of when I'm in command/insert mode. Is there any way to see this?



The vi-mode is actually provided by readline, so bash has no knowledge of it. So, no.


I don't know of a way to do it.


I'm a zsh user, but here's a couple of things I find pretty invaluable:-

    bindkey '^Q' push-line
The 'push-line' widget allows you to quickly type another command and will restore the previous one once you've entered it.

    # re-run the previous command with sudo
    rerun-with-sudo () {
      LBUFFER="sudo !!"
      zle accept-line
    }
    zle -N rerun-with-sudo
    bindkey '^Xx' rerun-with-sudo
Re-run the previous command prefixed with sudo by typing ^X then x.

And:-

    # build git clone command from clipboard
    git-clone-clipboard() {
        REPO_URL="$($CLIPBOARD)"
        REPO_BASE_NAME=${$(basename $REPO_URL)%.git}
        LBUFFER="nocorrect git clone $REPO_URL $REPO_BASE_NAME"
    }
    zle -N git-clone-clipboard
    bindkey "^Xg" git-clone-clipboard
^Xg prefixes a git url with "git clone" and fills out the command line.

Couple similar things for wget, youtube-dl here: https://github.com/gaving/dotfiles/blob/master/.zsh/config


I'm also a big zsh user, one of the biggest timesavers for me was to get autocompletion of the arguments from the last command working. I use meta-. for the last argument, but !:# syntax is awkward and I don't often remember exactly what number parameter something was. Took a bit of screwing around, but I eventually figured it out and the details are on the unix/linux stack exchange site: http://unix.stackexchange.com/questions/5229/add-arguments-f...


If I have a URL in my clipboard and want to prefix it I use:

    git clone `xclip -o`
Much simpler and no customization needed.


uh, why wouldn't you just actually paste the url into your terminal at that point instead of typing `xclip -o`?

and yeah, typing the actual command into the terminal is always going to be 'simpler'


One of my favorite bash shortcuts is binding the up arrow to history-search-backward. This is the first page I found that concisely explains how; the variant in the second comment is the one actually in my .bashrc:

http://hints.macworld.com/article.php?story=2003102617423686...

So if you've typed `verylongcommand` some indeterminate time in the past, you can probably just type "ver<up>", and you'll have it. Very convenient.


A quick look at the article shows it's binding history search. It's already bind to ctrl-r - you can do a ctrl-r, type a few letters of the long commands, and keep doing a ctrl-r till you find the right match.

Apart from additional bind your setup requires, arrow keys are near impossible to touch type. Even if you can touch type them, that means taking your hands off the home row, and positioning them again.


I think with the parent's setup you can do ver<C-p> to get the same effect.


I use ctrl-P/N for history-beginning-search-{backward,forward} in Zsh. Type the start of a previous command and cycle through the matches.


Great tips. !! is a nice timesaver when you've forgotten to prepend sudo to a command:

  $ /etc/init.d/rabbitmq-server restart
  Failed, need root access
  $ sudo !!
  Success
cd - is also quite cool, it takes you to the previous working directory.

  $ pwd
  /some/really/long/path/goes/here
  $ cd /var/log/app
  Work in this directory for a while, then go to previous dir
  $ cd -
  $ pwd
  /some/really/long/path/goes/here


I have the following in my zshrc for sudo. It adds sudo at the beginning of the current line or writes sudo !! if the current line is empty. I've aliased it to alt+s

    run-with-sudo() {
      if [[ -z $LBUFFER ]]; then
        LBUFFER="sudo !!";
      else
        LBUFFER="sudo $LBUFFER";
      fi
    };
    zle -N run-with-sudo;
    bindkey '\es' run-with-sudo


Nice little timesaver for such a common task, I'd just been doing the following (with vi editing on) but yours is much easier:

  esc-0-i-sudo


It may not be less keystrokes, but for what it's worth, I find using shift+i is easier than 0-i


Didn't know about that one, thanks!


Don't forget pushd and popd.


For Zsh, setopt autopushd


It's well known that !! is a sweet little helper in case where you forgot to sudo a command. However, I find that for those cases I usually use `Up-arrow CTRL-A` to get the previous command and then go to the beginning of the line where I can type `sudo`. Is there any reason to prefer the !! variant?


If you're on a non-interactive terminal. Some of us didn't throw out our teletypes. Also: when rooting boxes often one lacks space in the exploit payload to set up the tty line discipline.


I do too.

I think it's faster than a cycle of :p, verify it's what you want and then reexec. It's also mentally less taxing (albeit a tiny amount). But summed over a day of shell work, I think it makes a difference.


Most of the commands there can be replaced with vi keybindings.

That is, set -o vi

You can type that directly into your terminal for use just in one session or in your .bashrc

Type <C-]> or <Esc> just like in vim to escape into "command" mode. Most command mode commands work. E.g. '^' goes to the beginning of the line '$' goes to the end. 'i' sets bash back into insert mode.


I use `..` to go upper directory, `...` to go up two levels, I added this to bashrc:

  str='..'
  level='./../'
  for i in `seq 1 10`;
  do
      eval "alias '$str=cd $level'"
      level=$level'../'
    str=$str'.'
  done
Note, seq is not available in Mac, should use jot instead.


There's no need for seq or jot. In bash you can replace `seq 1 10` with {1..10}. Or {01..10} if you want zero padding when using $i to label files.

I use an function called "up" that takes an optional integer argument to go up a certain number of levels. It won't necessarily be faster, but somehow I prefer it to typing lots of dots in a row:

   function up () { if test $# = 1 ; then s=$( printf "%$1s" ); s=${s// /..\/}; cd $s ; else cd .. ; fi; }


Thanks for pointing out that


I've been using this[1] page as a cheatsheet for readline commands.

[1] http://jan.tomka.name/sites/default/files/readline-commands....


commandline and vim have easily got the most powerful, yet the most rage-inducing key mappings – ever.

The worst, and most easily rectifiable aspect is the way command pairs (eg move back/forward a word) require two totally separate commands (esc + b/esc + f) instead of having one command + a generic 'reverse' meta key.

In the future I hope someone has the balls to do away with this legacy tripe and popularise some more Donald-Normanesque keyboard shortcuts.


I'm not sure if you're just trolling, but I don't understand your complaints at all. What would your "Donald-Normanesque keyboard shortcuts" even look like? In Windows the arrow keys act pretty similar, don't they? left-arrow, right-arrow for moving by character, Ctrl-left-arrow, Ctrl-right-arrow for moving by word. Windows is about as Donald-Normanesque as you can get...

The bash shortcuts are from readline, which (in this case, but by default?) are the same as Emacs. C-f,C-b for moving by character, M-f,M-b for moving by word. C-n,C-p for next and previous line. I think there's a vi switch for readline which would allow you to have vi style shortcuts in bash. This is nice, because it means you have one set of keyboard shortcuts in your editor and shell. Emacs users in OS X get this in any text box (at least any that supports the same defaults as the system, I'm spitting at you MS Outlook/Entourage). I'm sure you could set something up in other windows systems, like GNOME or KDE.

Sure the keyboard shortcuts might be cryptic, but any keyboard shortcuts will be. Don't slag something just because you don't know the history and background behind the choices.


Sometimes I find myself typing out a long command and then deciding that I don't want to run it quite yet. Normally I just hit C-c run some other command first and then copy paste the long command. Surely there is someway to put the long command in history?


Comment it out (put a # at the front), unless your shell is set to ignore commented lines.


This works great! There is even a shortcut: M-#


You said copy-paste - are you using the OS copy-paste or readline's kill ring? C-a C-k is pretty fast, and then can be retrieved with C-y.


This is another great solution. As a vim user I haven't much looked into the emacs keybindings in readline. I'll have to read up. Also the terminology was a little confusing. I didn't realize that killing a line was the same as "cutting".


Yeah, and of course this does mean you have to be a little careful with M-backspace (backward-kill-word in Emacs), since it will change your kill ring.

But on other hand M-backspace can be useful too if you want to get just part of a line, you can move the cursor to where you want, then M-backspace through words to grab it - if you do M-backspace repeatedly the entire string is pulled into the kill ring until you stop pressing M-BS.


I'm not an Emacs user, but aren't a lot of the text-editing shortcuts straight from Emacs?


The text editing shortcuts are straight from readline http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html emacs bindings, which bash and many other programs use. readline supports both vi and emacs bindings.

I am a dyed in the wool vim enthusiast, but when it comes to command line bindings, emacs' bindings are more convenient.


Yup, but you can also put Bash in a vi mode. Some else will to chip in with the exact incantation, since I'm on my phone now.


  set -o emacs
or

  set -o vi


Cheatsheet for emacs mode (which is what the article presumes): http://www.catonmat.net/download/readline-emacs-editing-mode...


These assume you use default emacs mode. If you are vi/vim user then set -o vi will allow you to get immediately productive in bash, using vi motion commands to edit the command line.


Try Alt-*

    ls /etc/p<Alt-*>


the best bash shortcut you could do

    # ln -s /usr/local/plan9/bin/rc /bin/bash




Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: