5 indispensable bash tricks

Don’t mind the lame Buzzfeed title… Here are a few handy bash tricks and tips that people either use every day or never knew existed. Hopefully I can help move some of you into the first camp!

Introductory notes

A few of these commands involve working with bash history. On the advice of a coworker, I dropped this in my .bashrc to keep tons of history:

HISTSIZE=100000 # keep 100k commands in a session history (memory)
HISTFILESIZE=200000 # store 200k commands in my history file (on disk)

Disk space is cheap, as is memory. The number of times (prior to this change) that I wanted a command that had aged out of my bash history is much greater than the number of times I’ve found bash cumbersome because my history file is almost 1MB in size (when I have a 500GB SSD and 16GB RAM in my 2-year-old laptop).

Meta key

A number of bash commands reference a Meta key. In general, on a Mac, the Escape key fills that roll. On Linux, it’s generally the Alt key. You can change that, but if you’ve done so, you don’t need me to tell you about it. My examples will use Esc for these commands, but if you’re on a Linux box, you will likely want to substitute Alt for it.

Esc-. | Insert last argument

Described in the docs as insert-last-argument (M-., M-_), this keyboard shortcut will spit out the last argument to the previous command. On a Mac, the Meta key is Escape; on Linux, it’s often Alt.

Example usage:

$ mkdir -p long/directory/name/that/would_suck_to/type
$ cd Esc .

The Esc + . will be expanded into long/directory/name/that/would_suck_to/type.

Note that Esc + _ is bound to the same function, but is a bit tougher to type.

Ctrl+R | Reverse history search

This one is tough to explain, but magical. Have you ever hit the up arrow a bunch of times to scroll through history, trying to find something you ran recently? Ctrl + r will open up an interactive search, or reverse-i-search in bash parlance.

Recently used vim on a file with a long filename? Press Ctrl + r and start typing vim. The most recent command matching vim will be showed. Keep typing to make your search more specific, or press Ctrl + r again to scroll to the next-newest one. When you find what you want, press Enter to run it, or the right arrow to start moving the cursor through the command. (Or something like Ctrl+E to jump to the end of the line.)

If you want to be really nutty, you can start commenting your commands at the end. vim /etc/X11/xorg.conf # fix video settings will allow Ctrl + r + video to match a search, for example. I’ve been known to throw in random keywords I think I might try looking for later on.

cd – | Return to previous directory

pushd and popd are awesome and you should use them. But sometimes you forget. bash has got your back. cd - will return you to the previous directory you were in. (This is stored in the OLDPWD environment variable.)

git checkout – | Switch back to the previous branch

If you use git, you’ll be delighted to know that it does something similar. git checkout - will check out the previous branch you were on. I’m often bad at cleaning up topic branches, and will git checkout master to do some catching up, and then realize I don’t remember what my topic branch was called. Sure, it would probably take me all of 30 seconds to figure it out, but checking out - is so much easier.

!! | Re-run the previous command

!! will re-run the command you just ran. Why not just hit the up arrow? Because !! can be combined. The most command usage:

$ cat /root/whatever
Permission denied
$ sudo !!
sudo cat /root/whatever
whatever


Hope you learned something useful! What other neat tricks should I know about?

Counting open files by process

A site I host is offline, throwing the error “Too many open files.” The obvious solution would be to bounce the webserver to release all the file handles, but I wanted to figure out what was using all of them and see if I could figure out why they were leaking in the first place.

I had a few hunches, so I ran lsof -p PID on a few of them. But none of them had excessive files open. After a couple of minutes of guessing, I realized this was stupid, and set out to script things.

I hacked this quick-and-dirty script together:


pids = Dir.entries('/proc/').select{|p| p.match(/\d+/)}
puts "Found #{pids.size} processes..."
pfsmap = {}
pids.each do |pid|
files = Dir.entries("/proc/#{pid}/fd").size
cmdline = File.read("/proc/#{pid}/cmdline").strip
pfsmap[pid] = {
:files => files,
:name => cmdline
}
end

puts pfsmap.sort{|a,b| a[files] <-> b[files]}

There’s got to be a better way to get a process list from procfs than regexp matching directories in /proc that are only numeric. But I do that, and, for each process, count how many entires are in /proc/PID/fd and sort by that. So that the output isn’t just a giant mess of numbers, I also read the /proc/PID/cmdline.

This is hardly a polished script, but it did the job — it identified a script that was hitting the default 1024 FD limit. I was then able to lsof that and find… that they’re all UNIX sockets, so it’s anyone’s guess what they go to. So I just rolled Apache like a chump. Oh well. Maybe it’ll help someone else—or maybe someone knows of a less-ugly way to do some of this?

bashrc vs bash_profile

On the subject of bashrc

I’ve always been confounded by the presence of both .bashrc and .bash_profile and how they related.

It turns out to be simple, but 0% intuitive:

  • .bashrc is invoked for non-login, interactive shells
  • .bash_profile is invoked for login shells

The difference is powerful, surely, but also annoying. Most of the time, you just want something to always run in bash, and you don’t care about login vs. non-login.

Which is why it’s recommended to have .bash_profile merely source .bashrc.

My .bashrc

Not that this is terribly enthralling, but I figured I’d post some of the more useful parts of my .bashrc file.

Colorized prompt: starting with a newline


export PS1='\n[\e[0;32m]\u[\e[m] [\e[1;34m]\w[\e[m] [\e[1;32m]\$[\e[m] '

Putting a newline at the front of your prompt is possibly the best idea I have ever had. It creates just enough room between commands to know what’s what. Seriously, you don’t know what you’re missing.

When it comes to figuring out colors for a prompt, Arch Linux’s Color Bash Prompt wiki page is the most helpful I’ve ever seen.

Keep lots of history

I complained one day to someone that commands I use occasionally would fall out of my bash history. I’d use Ctrl+R to search for them, and when they fell out of history, I was lost.

He pointed out that it’s easy to increase the size of your history. You might as well make it huge:


HISTSIZE=1000000
HISTFILESIZE=200000

I think those values are maybe a bit mismatched; see this explanation. HISTSIZE probably doesn’t need to be 800,000 larger than allowed on-disk.

It’s a small text file. There’s really not a compelling reason to keep the file tiny. It’s possible that 200,000 lines on disk is too high, but it’s guaranteed that 1,000 is too small.

BTW, another tip: if you often rely on history search, you can add a comment on the end to aid in your search. E.g.,


git rebase -p --onto ddc4a6102051^ ddc4a6102051 # remove commit from history

If you can’t remember the command, now I can try to Ctrl+R “remove commit” or the like.

Some simple aliases


alias be='bundle exec'
alias gb='git rev-parse --abbrev-ref HEAD'
alias gpcm='git pull company master'
alias gpom='git push origin master'

gb is a handy trick to show only the current git branch name.

An alias with arguments

You can’t (to my knowledge) pass arguments to an alias. But what you can do is define a function.

For example, in the above example where I use a comment on the end to aid in searching history, because I could never remember what the thing was called?

I made it a function:


git-toss() {
git rebase -p --onto $1^ $1
}

Now git-toss d34db33f will expand to git rebase -p --onto d34db33f^ d34db33f, removing d34db33f from my git history. (I will note that this is probably not a common git task.)

What else am I missing?