Level Up Your Shell Game

The Viget dev team was recently relaxing by the fireplace, sipping a fine cognac out of those fancy little glasses, when the conversation turned (as it often does) to the Unix command line. We have good systems in place for sharing Ruby techniques (pull request code reviews) and Git tips, but everyone seemed to have a simple, useful command-line trick or two that the rest of the team had never encountered. Here are a few of our favorites:

Ready to get your on? Good. Let’s go.

Keyboard Shortcuts

Mike: I recently discovered a few simple Unix keyboard shortcuts that save me some time:

Shortcut Result
ctrl + u Deletes the portion of your command before the current cursor position
ctrl + w Deletes the word preceding the current cursor position
ctrl + left arrow Moves the cursor to the left by one word
ctrl + right arrow Moves the cursor to the right by one word
ctrl + a Moves the cursor to the beginning of your command
ctrl + e Moves the cursor to the end of your command

Thanks to Lawson Kurtz for pointing out the beginning and end shortcuts


Eli: Sick of typing bundle exec rake db:test:prepare or other long, exhausting lines of terminal commands? Me too. Aliases can be a big help in alleviating the pain of typing common commands over and over again.

They can be easily created in your ~/.bash_profile file, and have the following syntax:

alias gb="git branch"

I’ve got a whole slew of git and rails related ones that are fairly straight-forward:

alias ga="git add .; git add -u ."
alias glo='git log --pretty=format:"%h%x09%an%x09%s"'
alias gpro="git pull --rebase origin"
alias rs="rails server"

And a few others I find useful:

alias editcommit="git commit --amend -m"
alias pro="cd ~/Desktop/Projects/"
alias s.="subl ."
alias psgrep="ps aux | grep"
alias cov='/usr/bin/open -a "/Applications/Google Chrome.app" coverage/index.html'

If you ever notice yourself typing these things out over and over, pop into your .bash_profile and whip up some of your own! If ~/.bash_profile is hard for you to remember like it is for me, nothing an alias can’t fix: alias editbash="open ~/.bash_profile".

Note: you’ll need to open a new Terminal window for changes in ~/.bash_profile to take place.

History Expansions

Chris: Here are some of my favorite tricks for working with your history.

!! - previous command

How many times have you run a command and then immediately re-run it with sudo? The answer is all the time. You could use the up arrow and then Mike’s ctrl-a shortcut to insert at the beginning of the line. But there’s a better way: !! expands to the entire previous command. Observe:

$ rm path/to/thing
 Permission denied
$ sudo !!
 sudo rm path/to/thing

!$ - last argument of the previous command

How many times have you run a command and then run a different command with the same argument? The answer is all the time. Don’t retype it, use !$:

$ mkdir path/to/thing
$ cd !$
 cd path/to/thing

!<string> - most recent command starting with

Here’s a quick shortcut for running the most recent command that starts with the provided string:

$ rake db:migrate:reset db:seed
$ rails s
$ !rake # re-runs that first command

!<number> - numbered command

All of your commands are stored in ~/.bash_history, which you can view with the history command. Each entry has a number, and you can use !<number> to run that specific command. Try it with grep to filter for specific commands:

$ history | grep heroku
 492 heroku run rake search:reindex -r production
 495 heroku maintenance:off -r production
 496 heroku run rails c -r production
$ !495

This technique is perfect for an alias:

$ alias h?="history | grep"
$ h? heroku
 492 heroku run rake search:reindex -r production
 495 heroku maintenance:off -r production
 496 heroku run rails c -r production
$ !495


Argument Expansion

Ryan: For commands that take multiple, similar arguments, you can use {old,new} to expand one argument into two or more. For example:

mv app/models/foo.rb app/models/foobar.rb

can be

mv app/models/{foo,foobar}.rb

or even

mv app/models/foo{,bar}.rb

Customizing .inputrc

Brian: One of the things I have found to be a big time saver when using my terminal is configuring keyboard shortcuts. Luckily if you’re still using bash (which I am), you can configure shortcuts and use them in a number of other REPLs that all use readline. You can configure readline keyboard shortcuts by editing your ~/.inputrc file. Each line in the file defines a shortcut. It’s made up of two parts, the key sequence, and the command or macro. Here are three of my favorites:

  1. "\ep": history-search-backward: This will map to escape-p and will allow you to search for completions to the current line from your history. For instance, it will allow you to type “git” into your shell and then hit escape-p to cycle through all the git commands you have used recently looking for the correct completion.
  2. "\t": menu-complete: I always hated that when I tried to tab complete something and then I’d get a giant list of possible completions. By adding this line you can instead use tab to cycle through all the possible completions stopping on which ever one is the correct one.
  3. "\C-d": kill-whole-line: There’s a built-in key command for killing a line after the cursor (control-k), but no way to kill the whole line. This solves that. After adding this to your .inputrc just type control-d from anywhere on the line and the whole line is gone and you’re ready to start fresh.

Don’t like what I mapped these commands to? Feel free to use different keyboard shortcuts by changing that first part in quotes. There’s a lot more you can do, just check out all the commands you can assign or create your own macros.

Viewing Processes on a Given Port with lsof

Zachary: When working on projects, I occassionally need to run the application on port 80. While I could use a tool like Pow to accomplish this, I choose to use Passenger Standalone. However, when trying to start Passenger on port 80, I will get a response that looks something like “The address is already in use by another process”. To easily view all processes communicating over port 80, I use lsof like so:

sudo lsof -i :80

From here, I can pin-point who the culprit is and kill it.

SSH Configuration

Patrick: SSH is a simple tool to use when you need shell access to a remote server. Everyone is familiar with the most basic usage:

$ ssh production.host

Command-line options give you control over more options such as the user and private key file that you use to authenticate:

$ ssh -l www-data -i /Users/preagan/.ssh/viget production.host

However, managing these options with the command-line is tedious if you use different private keys for work-related and personal servers. This is where your local .ssh/config file can help – by specifying the host that you connect to, you can set specific options for that connection:

# ~/.ssh/config
Host production.host
 User www-data
 IdentityFile /Users/preagan/.ssh/viget

Now, simply running ssh production.host will use the correct username and private key when authenticating. Additionally, services that use SSH as the underlying transport mechanism will honor these settings – you can use this with Github to send an alternate private key just as easily:

Host github.com
 IdentityFile /Users/preagan/.ssh/github

Bonus Tip

This isn’t limited to just setting host-specific options, you can also use this configuration file to create quick aliases for hosts that aren’t addressable by DNS:

Host prod
 Port 6000
 User www-data
 IdentityFile /Users/preagan/.ssh/production-key

All you need to do is run ssh prod and you’re good to go. For more information on what settings are available, check out the manual (man ssh_config).

Invoking Remote Commands with SSH

David: You’re already using SSH to launch interactive sessions on your remote servers, but DID YOU KNOW you can also pass the commands you want to run to the ssh program and use the output just like you would a local operation? For example, if you want to pull down a production database dump, you could:

  1. ssh into your production server
  2. Run mysqldump to generate the data dump
  3. Run gzip to create a compressed file
  4. Log out
  5. Use scp to grab the file off the remote server

Or! You could use this here one-liner:

ssh user@host.com "mysqldump -u db_user -h db_host -pdb_password db_name | gzip" > production.sql.gz

Rather than starting an interactive shell, you’re logging in, running the mysqldump command, piping the result into gzip, and then taking the result and writing it to a local file. From there, you could chain on decompressing the file, importing it into your local database, etc.

Bonus tip: store long commands like this in boom for easy recall.

Well, that’s all we’ve got for you. Hope you picked up something useful along the way. What are your go-to command line tricks? Let us know in the comments.

David is Viget's managing development director. From our Durham, NC, office, he builds high-quality, forward-thinking software for PUMA, the World Wildlife Fund, ID.me, and many others.

More posts by David