PWD in the Title Bar (or, “A Regex Adventure in BASH”)

PWDInTheTitleBar.png

In which a five-minute hack to put the current working directory in the title bar of the Terminal descends into hours learning the surprising arcanities of BASH.

Like most developers, I spend a lot of time at my computer living in Terminal (and the rest Xcode)*. I have what might be a strange tendency to open a new Terminal window for each new task, I think so that I don’t have to lose the context in my current window. This means that after I’ve finished doing something (or in the middle, when I look at the screen and my obsessive-compulsive side grabs me and says “Wow, that’s messy. I need to tidy up my desktop before I can do more work”) I often find myself with a vast number of Terminals open, with the majority sitting minimised in the Dock, and no idea which ones are actually still useful.

In the past, I found a cool script snippet that I added to my BASH .profile that would put the current working directory in the title bar of the Terminal. This is a huge help. It means that I can quickly choose the ‘right’ window for whatever I’m doing just by looking at the title bar, or by selecting it from Terminal’s Window menu, and when I’m on one of my cleaning frenzies I don’t have to go through the windows trying to work out what I’m using them for; I can just mouse over the minimised window, see the title and (almost always) close it with the context menu.

I didn’t have that snippet any more though (the hardware it’s possibly even still on belonged to Apple, not me, and I didn’t think about retrieving it in time), but I was sure that Google would provide the answer to me.

And it did, sort of. There lots of pages on the web about configuring your BASH prompt, and even a full ‘HOWTO’. None of them gave a cut-and paste example of exactly what I wanted though. This section of the HOWTO came pretty close, but didn’t quite do it. After reading it though, my demands increased. Seeing what others had done, it seemed like it would be pretty cool if, besides having the directory name in the title bar, it was also formatted nicely. I now wanted it to be truncated to size (with ellipses, not just any old truncation), and to replace my home directory with “~/”. The snippet given there almost does this, but it smelled a bit off to me. It uses grep, awk, wc, tr, sed - a veritable cornucopia of shell commands - to do its thing. Although I’m all for the UNIX philosophy of small tools doing what they do well, it doesn’t seem like so many external processes should be required just for a bit of string manipulation (from the page: “Relative speed: the first version takes about 0.45 seconds on an unloaded 486SX25. Risto’s version takes about 0.80 to 0.95 seconds.” - of course, on my modern machine they were instant, but it’s the principle…)

So, I did some research and found something that pleasantly surprised me. Did you know that BASH actually now supports in-process regexes? Maybe I’m late to the party, bit I didn’t. You can (in the form here, since version 3.2) do this:

[[ string =~ regex ]]

And the regex will be executed for you, a 0 (which, in shell land, indicates success) returned if the string matches, a 1 if it doesn’t, and a 2 if you screwed up and gave it an invalid regex. Matches for any parenthesised parts of the regex are placed in an array called BASH_REMATCH (item 0 is the entire match, items 1+ are the parenthesised expressions). It’s all even there in the BASH man page (look under “Compound Commands”). The regexes are not in the Perl format most people expect nowadays, they’re in POSIX format, as described by man re_format, but that’s not hard to pick up.

Using my new-found shell-script-regex powers, and some more of BASH’s string manipulation routines, I wrote my own pwd-to-title-bar routine, taking inspiration from the second one on the HOWTO page I mentioned before, but using all in-process string manipulation (hopefully, this will run faster on Risto’s 486SX25). Here it is:

function directory_to_titlebar {
    local pwd_length=42  # The maximum length we want (seems to fit nicely
                         # in a default length Terminal title bar).

    # Get the current working directory.  We'll format it in $dir.
    local dir="$PWD"     

    # Substitute a leading path that's in $HOME for "~"
    if [[ "$HOME" == ${dir:0:${#HOME}} ]] ; then
        dir="~${dir:${#HOME}}"
    fi

    # Append a trailing slash if it's not there already.
    if [[ ${dir:${#dir}-1} != "/" ]] ; then 
        dir="$dir/"
    fi

    # Truncate if we're too long.
    # We preserve the leading '/' or '~/', and substitute
    # ellipses for some directories in the middle.
    if [[ "$dir" =~ (~){0,1}/.*(.{${pwd_length}}) ]] ; then  
        local tilde=${BASH_REMATCH[1]}
        local directory=${BASH_REMATCH[2]}

        # At this point, $directory is the truncated end-section of the 
        # path.  We will now make it only contain full directory names
        # (e.g. "ibrary/Mail" -> "/Mail").
        if [[ "$directory" =~ [^/]*(.*) ]] ; then
            directory=${BASH_REMATCH[1]} 
        fi

        # Can't work out if it's possible to use the Unicode ellipsis,
        # '…' (Unicode 2026).  Directly embedding it in the string does not
        # seem to work, and \u escape sequences ('\u2026') are not expanded.
        #printf -v dir "$tilde/\u2026$s", $directory"
        dir="$tilde/...$directory"
    fi

    # Don't embed $dir directly in printf's first argument, because it's 
    # possible it could contain printf escape sequences.
    printf "\033]0;%s\007" "$dir"
}

if [[ "$TERM" == "xterm" || "$TERM" == "xterm-color" ]] ; then
    export PROMPT_COMMAND="directory_to_titlebar"
fi

PROMPT_COMMAND is run by BASH every time it’s about to put up a prompt, so I set it (at the bottom of the snippet) to my title-bar-renaming function, directory_to_titlebar. The actual function gets the current working directory, replaces the home directory path, if it’s there, with “~/”, appends a slash and, if necessary to make the whole string fit in Terminal’s title bar nicely, snips out directories after the initial “/” or “~/”, replacing them with “”. The escape codes in the final printf are what tells Terminal to use the enclosed string as the title instead of printing it to console. The only thing I wanted but was unable to do was to get the string to use the Unicode ellipsis character instead of the three full-stops. It seems weird in these modern times but, unless I’m missing something, BASH’s string entry routines are not Unicode-aware (if it’s possible, please do leave a comment telling me how!)

If you want to use it too, just copy it and paste it into your ~/.profile file. I believe it should work on any system that uses or emulates an XTerm, not just the Mac OS X Terminal.

TerminalSettings.pngThe last thing to do to get what I wanted was to set the Window preferences to show the rest of the information I thought would be useful. I’ve pasted a screenshot of Terminal’s prefs on the left (this is in the left-hand pane of the Settings tab of Terminal’s prefs). The Title setting (here reading “Terminal”) is what’s replaced by the string we’re ‘printing’ in the routine above, the Active Process Name setting enables you to see what’s currently running in that Terminal in the title bar, and the Command Key option puts the window’s command key in the title bar, meaning that you can tell at a glance which window you can bring forward by pressing Cmd-2 at any time. You can see how it all works together in the screenshot at the top of this page.

And with that (phew) my marathon of BASH was over, and I could get back to what I really should have been doing all along, but with my environment made just a little bit more pleasant.


9 Comments

Thank you!


You saved me a lot of time. I have been searching for an exactly similar script. Thank you.


Ammon Skidmore has pointed out that there are problems if your directories have ‘non-roman’ characters in them. The problems actually seem to lie in the regex routines internal to BASH, so I haven’t found a way to work around them. The bug has been reported to Apple (as rdar://problem/6189584, if you’re inside Apple and want to take a look).


Have you tested this with OS 10.6? I get a barrage of syntax errors trying to use this on 10.6.x. I initially thought something in my existing .bash_profile might be causing a conflict so I moved it all aside and started with a blank .bash_profile. Alas, I sill get syntax errors.


@kris Yes, I’m using it on 10.6 right now. What sort of errors are you seeing?


@jamie Sorry, false alarm. I picked up some strange hidden characters in the copy/paste. Got rid of them in TextWrangler and everything is good. Very nice script. Thanks!


This appears to stop working in 10.7. Any thoughts?


Ok, but what about the tabs. How do I set the tabs separately from the title bar.

E.g. I often several tabs with vim running. I want the tab to say vim {filename} without the path.


I found that I was getting the first part of my path up to the space limitation of the tab.

I ended up with this for my titlebar/tabs

PROMPT_COMMAND=’echo -n -e "33]0;basename $PWD07"’

However, when I am editing, then the file name I’m edition becomes important:

function vi { echo -n -e "33]0;vi $107" ; vim $1 ;}

Having even more fun, here’s a way to quickly tell when a long running compile is done.

function watch { $* ; echo -n -e "33[41m" ;
echo $* DONE ; echo -n -e "33]0;DONE07" ; date ; read -s -n 1 ; echo -n -e "33[0m" ; }

Which runs the command turns text background color to red echos the original command now on red, with the word DONE appended. sets the tab to DONE waits for the any key resets the terminal.