TweetFollow Us on Twitter

Mac in The Shell: Customizing the bash Experience Further

Volume Number: 24
Issue Number: 10
Column Tag: Mac in The Shell

Mac in The Shell: Customizing the bash Experience Further

Changing the bash shell to suit your needs

by Edward Marczakr

Introduction

Last month, I launched into a different kind of introduction to bash. It concerns making you, the end user, comfortable and efficient. This month wraps up this topic with a look at more ways to wring magic out of bash.

History

For once, I'm not talking about "history" as in a "history lesson." This time, I'm talking about history in bash. bash history is both a command, and a function of the bash shell. bash very nicely will keep history (a log, or journal) of the commands you execute. Open up a shell (probably using Terminal.app), and type ls -l. Now, change into the /Users/Shared directory using the cd command: cd /Users/Shared. From here, press the up arrow on your keyboard. The current input line should recall the previous command (cd /Users/Shared). Pressing the up arrow again will recall the command before that. Do so, and when "ls -l" appears, press the return key to execute the ls command and list the contents of the directory you're currently in. Now, type history, and press return. You should see a list of commands previously executed, ending with "ls -l", "cd /Users/Shared" and another "ls -l".

The simple fact that we can scroll through our session's history with the up and down arrow, and retrieve an entire listing is pretty amazing all on its own. Not surprisingly, it gets better!

First, let's make sure we capture all of the history we want. There are several shell variables that configure the behavior of bash history. Here's what I use in my ~/.bash_profile:

export HISTSIZE=12000
export HISTFILESIZE=12000
export HISTCONTROL='ignoreboth'
export HISTTIMEFORMAT='%b %d %H:%M:%S: '

(there's actually a little more to it, but I'll cover that in due time).

The HISTSIZE variable sets the number of commands to remember in the command history. If not set, it defaults to 500. HISTFILESIZE, when set, will tell bash to truncate the history file, if necessary, by removing the oldest entries, to contain no more than that number of lines. Again, the default value is 500. The history file is also truncated to this size after writing it when an interactive shell exits.

HISTCONTROL is a very useful variable. Assign HISTCONTROL a colon separated list of the following options to alter how history is saved:

ignorespace: lines which begin with a space character are not saved in the history list.

ignoredups: lines matching the previous history entry are not saved.

ignoreboth: enforces both ignorespace and ignoredups.

erasedups: causes all previous lines matching the current line to be removed from the history list before that line is saved.

If HISTCONTROL is not set, all lines read by the shell parser are saved in the history list. If you like to watch a directory by executing "ls -l" and then repeatedly pressing up-arrow-return, ignoredups is for you! This way, you'll only see one "ls -l" in history no matter how many times you repeat the command.

HISTTIMEFORMAT is a variable I see get very little use, but I find immensely useful. Simply, when set, each entry in the history list will have a timestamp save with the entry. With the example setting I give above, my history looks like this:

580  Jul 29 08:25:26: cd dev/objc/
581  Jul 29 08:25:27: ls -la
582  Jul 29 08:34:29: cd dumb

Yes, I have a directory named "dumb." I also have several shell options defined, but the most important relating to history is this:

shopt -s histappend

The histappend option appends the history from the current shell when it exits to the history file. This is useful - I'd claim critical - when using multiple shells. To explain further, shell history is not immediately written to the history file, but is maintained per shell, and written on shell exit. If I have two shells running, the last to exit writes the history file that it maintained. If you only even use a single shell at a time, you wouldn't notice this behavior, but I think it's a nice option to have enabled regardless.

As an aside: I actually use two other shell options: histverify and histreedit. Both affect substitution and shell expansion. Both are also a little outside the scope of this article, however, the bash man page has more information if you're so inclined.

Another aside: I really like setting the cmdhist option, too using:

set cmdhist

This option will save multi-line entries as a single line in history. Multi-line entries are created using the backslash character at the end of a line. For example, with cmdhist set, if I were to enter the following:

$ for i in `ls`; do \
> echo $i; \
> done

...my history would actually contain:

for i in `ls`; do echo $i; done

It's all up to personal taste if you prefer this or not, so, use what works best for you.

More History

Just investigating bash history functionality alone is a deep topic. Mainly because it exists to save you work; always remember that. So, now you can up-and-down-arrow through history, and use the history command to list all history. If you typed a command that you want to recall, you can certainly press up-arrow until you find it. What if that command is way back? Well, you could remember part of the command and use grep to find it:

$ history | grep ssh
272  Jul 25 10:09:56: ssh 192.168.76.84
285  Jul 25 14:39:53: ssh marczak@192.168.76.84
287  Jul 26 09:06:04: ssh 192.168.125.164
289  Jul 26 11:10:58: ssh marczak@192.168.76.84
295  Jul 26 16:27:13: man ssh
296  Jul 26 23:10:19: ssh mm@db.wheresspot.com
298  Jul 27 09:19:18: ssh mm@db.wheresspot.com
330  Jul 27 09:52:21: ssh minoc
331  Jul 27 09:17:26: ssh serveradmin@ballast

(remember - I'm showing an option timestamp, which you likely won't see). From this list, you could copy and paste the line you'd like to repeat (yuck). Or, you can ask bash to expand by the history id. This is called history expansion. For example, if I wanted to repeat the "ssh marczak@192.168.76.84" line - history id 285 - I can type this:

!285

and press return. Much easier than retyping the entire line. While using grep to find things in history is OK for a grand overview, there are other, more refined ways of recalling history.

To broaden the scope of the exclamation point operator, in general, after the exclamation point you specify an event designator. A number will recall a specific numbered event in history. Here's a handy list of useful designators:

(numerical value) - recall specific event in history

! - previous line

(text) - previous line starting with given text.

(?text) - previous line containing text

Examples are always best, so, here's one of each (save "numerical value," which you've already seen in action).

If you ssh into a remote machine, do your work, get out and then realize, "oops - I need to do one more thing," simply type !! and press return. This will run the previous command. Remember: this is a substitution, so, you can use "!!" wherever you want to substitute the previous command. An example of this would be:

$ ps ax | grep [W]ord | awk '{print $1}'
28976
$ kill $(!!)

The text designator substitutes the previous match. For example, if I wanted re-run the ps command from the previous example - you know, to make sure it's really dead - typing !ps and pressing return will match that ps, and execute the entire line.

The final designator that I'm going to show matches text anywhere in the event, not just the beginning. To once again rely on the previous example, to now match the ps event, you could type !?ord, or even !?awk, press return and re-execute the entire ps expression.

Readline

Developed by GNU, bash uses the readline library for its command input. Simply stated, readline provides a set of functions that allow users to edit command lines as they are typed in. Readline is also responsible for providing the history function to bash (and other applications that choose to include it). Readline is a bit of a discussion in and of itself, so, I'm just going to show one immediately available function, and one tweak that makes all the difference to my history experience.

Readline runs in one of two modes: emacs or vi. The default is emacs mode. This allows you to use emacs key bindings to move about the command line. It's actually the source of the keyboard commands shown last month. From this, we gain reverse search through our history. Press ctrl-r, and your prompt will change to "(reverse-i-search)`':", alerting you that what you type is a search back through history. Each key press finds the best previous match. If what you type fails to match an event in history, you receive a bell (visual or audible, depending on how your session is configured). If you have more than one match, press ctrl-r again to cycle through matches.

Readline is configured using the file .inputrc that it finds in your home directory (you'll need to create this file, as it's not there by default). This allows for further customization and ways to implement features that match the way you work. Here's the .inputrc file that I use:

"\e[B": history-search-forward
"\e[A": history-search-backward
Space: magic-space

The first two lines bind the up and down arrow to searching forward and back through history. You may think the arrows already so this - and they do to a degree - however, with this addition, a little typing goes a long way. With these lines in your ~/.inputrc, if you were to type ssh, and then press the up arrow, you'll navigate back through history only seeing events that match the beginning "ssh". The "magic-space" line causes readline perform expansion each time the space bar is pressed.

One final note: bash isn't the only application that uses readline. This means that changes to .inputrc will affect all applications relying on it. For example, the command-line MySQL client uses the readline library to implement command-line editing and history retrieval. If you want a particular setting to apply only to one environment, .inputrc does understand a limited form of conditionals. So, to cause settings to only apply to bash, use this in .inputrc:

$if bash
Space: magic-space
$endif

This conditional will cause the magic-space behavior to apply only to bash. Naturally, any settings can be placed in the conditional.

CDPATH

CDPATH is simply a shell variable. Like the PATH variable, which tells the shell which paths to search for an executable, CDPATH informs the cd command which paths to look in when changing directories. This is a simple way to reduce your typing. CDPATH is specified just like PATH: a list of directories, separated by a colon. If, for example, to search your home directory and /Users/Shared, specify this:

CDPATH=".:~:/Users/Shared"

The "dot" up front in that specification tells cd to search the current directory. You probably want to do that, right? Using the CDPATH shown, if there's a directory named "photo" under /Users/Shared, and my present working directory (pwd) is ~/Library (or anywhere, really), simply typing cd photo will change my present working directory to /Users/Shared/photo.

This should tie into good usage of the PS1 variable as described in the previous column, as you should always have a visual clue as to which directory you actually currently in.

The order of CDPATH is important. Just like the PATH variable, first match "wins." Using the previous example, if there was a directory named "photo" in both my home directory and /Users/Shared/, typing cd photo would bring me to the photo directory in my home. Thanks to the current directory specification (".") up front, the current directory always takes precedence, so, you'll always have 'normal' behavior.

Bash Programmable Completion

Modern versions of bash allow programmable completion, extending the standard completion that the Tab key normally provides. Again, this is a topic that can probably take up its own article or chapter in a book. Investigating every aspect of programmable bash completion is beyond the scope of this article, but I need to point out that it exists, and how to get some useful completions running in your shell.

Here's what the bash man page says about standard completion (pressing the Tab key):

"Attempt to perform completion on the text before point. Bash attempts completion treating the text as a variable (if the text begins with $), username (if the text begins with ~), hostname (if the text begins with @), or command (including aliases and functions) in turn. If none of these produces a match, filename completion is attempted."

However, this basic setup can be improved. For example, on an unadorned bash-shell-out-of-the-box on OS X, if you were to type man dsed and press Tab, you'll just receive a bell. Wouldn't it be nice if you could press tab and have the line completed with man dseditgroup? Here's what the bash man page says about programmable completion:

"When word completion is attempted for an argument to a command for which a completion specification (a compspec) has been defined using the complete builtin, the programmable completion facilities are invoked.

First, the command name is identified. If a compspec has been defined for that command, the compspec is used to generate the list of possible completions for the word. If the command word is a full pathname, a compspec for the full pathname is searched for first. If no compspec is found for the full pathname, an attempt is made to find a compspec for the portion following the final slash.

Once a compspec has been found, it is used to generate the list of matching words. If a compspec is not found, the default bash completion as described above under Completing is performed."

A bit wordy, perhaps. However, the upshot is this: you can have bash complete on just about anything. If your company has custom command-line utilities, you could create a completion specification for the valid switches and completions for your specific utility! The key to it all is the complete built-in command.

Last month, we touched on aliases and functions. As a quick refresher, a function is a subroutine, or, a code block that implements a set of operations. This function will add two numbers together and print their result:

#!/bin/bash
function sum() {
  total=$(($1+$2))
  echo "The sum of $1 and $2 is $total"
}
sum 5 12

As you can see, calling a function is just like executing any bash script: arguments are passed in order into the function. Programmable completion takes advantage of this, allowing you to specify functions that will determine completions for matching text.

As a short example, imagine that we want completions for the dscl command. In the interest of space and clarity, our function will only complete on these options: -read, -readall, -readpl, -readpli and -list. A function that can do this for us would look something like this:

_dscl() 
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="-read -readall -readpli -list"
    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" — ${cur}) )
        return 0
    fi
}
complete -F _dscl dscl

If you're anxious to try this now, save this in a file named "dscl" and source it by typing source dscl. Type dscl -r at the command prompt and press the Tab key. bash should complete the "-r" as "-read" for you. Press the Tab key twice, and you'll see that it'll match the other "-read*" entries we supplied. Very cool. How does it work?

We start out by defining some variables: cur, the current match (being typed at the command-line), prev, the previous word typed and opts, which is the list of options to complete. The actual completion is handled by the compgen bash built-in. compgen is used to fill in the COMPREPLY variable. COMPREPLY has special meaning to bash in that it holds the output of a completion.

You can certainly use this function as a template. However, while it works for simple completions, a function could certainly be much more powerful and complex. What if you're just trying to get your completion fix and don't have the time/skills/energy to write an in-depth function for completion?

Fortunately, there are many good bash completion examples full scripts that you can download. One of the best is created by Ian Macdonald, and is available from http://www.caliban.org/files/bash/bash-completion-20060301.tar.gz. While originally created with Linux in mind, this comprehensive completion file is ideal for OS X, too. Even better: any Linux-specific commands that it can't find on OS X are simply not loaded, saving memory.

To use it, download the file, unarchive it, and find the bash_completion file at the top level of the resulting directory. Copy that into /etc. Have your bash initialization script source it:

. /etc/bash_completion

("." is a synonym for the source command). If you don't want to wait to login to a new shell, source it right at the command line. You can test it with one of the completions it brings: the cd completion. Wait...doesn't cd already complete with the built-in completion? Well, yes, but it's free to do so in meaningless ways. If you have a file in your home directory named "current-list", and a directory named "current_projects", the unmodified completion will complete the word "current", but then wait for you to clarify. With the programmed behavior, we realize that with the cd command, only one of the choices makes sense - the directory. Now you should notice that typing cd cur followed by the Tab key, you'll get the correct, full completion.

This bash completion file provides a framework for creating completions. If you want to finish off the dscl example above, or, create one for a custom binary or script, create an /etc/bash_completion.d directory, and drop your completion file in there. The /etc/bash_completion script is designed to look in /etc/bash_completions.d and source each file it containes.

One last tip that, although it gets put into ~/.inputrc, it ties in with tab completion: A helpful completion trick is to drop the following line into your .inputrc file:

set show-all-if-ambiguous on

This addition causes readline to show alternate matches immediately, rather than make you press Tab twice.

Wrapping Up

I hope the tips from this and last month have had an impact on your work inside the bash shell. Many times, it's the little things like this that can make a huge difference in your daily work. I certainly remember the first time I saw these in action after using bash for some time. It was a bit of magic! Quite honestly, while we've covered a lot in this and the previous article, there's even more to explore in bash. If this coverage has piqued your interest, investigate the shopt and bind commands along with the many more options available to the readline library.

Media of the month: Gödel, Escher, Bach: An Eternal Golden Braid, by Douglas R. Hofstadter. I recently unearthed my copy of this venerable tome and remembered what a revelation it is. Granted, it's not for everyone, but, if you've always wondered about it, or (especially) if you've never heard of it, go find a copy and dig it.

This will most likely be the last bash-specific Mac in the Shell column for some time. If there are any bash topics that you'd like to see explored deeper (or initially), let me know, and we can dig back in. Next month, we'll get into the wider world of scripting in OS X.


Ed Marczak is the Executive Editor for MacTech Magazine, and has been lucky enough to have ridden the computing and technology wave from early on. From teletype computing to MVS to Netware to modern OS X, his interest was piqued. He has also been fortunate enough to come into contact with some of the best minds in the business. Ed spends his non-compute time with his wife and two daughters.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Audio Hijack 3.2.0 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
FontExplorer X Pro 5.0.1 - Font manageme...
FontExplorer X Pro is optimized for professional use; it's the solution that gives you the power you need to manage all your fonts. Now you can more easily manage, activate and organize your... Read more
Calcbot 1.0.2 - Intelligent calculator a...
Calcbot is an intelligent calculator and unit converter for the rest of us. Featuring an easy-to-read history tape, expression view, intuitive conversion, and much more! Features History Tape -... Read more
MTR 5.0.0.1 - The Mac's oldest and...
MTR (was MacTheRipper)--the Mac's oldest and smartest DVD-backup app--is now updated to version 5.001 MTR -- the complete toolbox, not a one-trick, point-and-click extractor. MTR is intended for... Read more
LibreOffice 4.4.5.2 - Free, open-source...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
Adobe Lightroom 6.1.1 - Import, develop,...
Adobe Lightroom is available as part of Adobe Creative Cloud for as little as $9.99/month bundled with Photoshop CC as part of the photography package. Lightroom 6 is also available for purchase as a... Read more
File Juicer 4.41 - Extract images, video...
File Juicer is a drag-and-drop can opener and data archaeologist. Its specialty is to find and extract images, video, audio, or text from files which are hard to open in other ways. It finds and... Read more
A Better Finder Rename 9.52 - File, phot...
A Better Finder Rename is the most complete renaming solution available on the market today. That's why, since 1996, tens of thousands of hobbyists, professionals and businesses depend on A Better... Read more
OmniFocus 2.2.3 - GTD task manager with...
OmniFocus helps you manage your tasks the way that you want, freeing you to focus your attention on the things that matter to you most. Capturing tasks and ideas is always a keyboard shortcut away in... Read more
TinkerTool 5.4 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more

Cosmonautica (Games)
Cosmonautica 1.1 Device: iOS Universal Category: Games Price: $6.99, Version: 1.1 (iTunes) Description: Cast off! Are you ready for some hilarious adventures in outer space? | Read more »
Rescue humanity from a Demon horde in An...
Angel Stone is Fincon's follow up to the massively successful Hello Hero and is out now on iOS and Android. You play as a member of The Resistance, a group of mighty human warriors who have risen up in defiance of the Demon horde threatening to... | Read more »
Gallery Doctor (Photography)
Gallery Doctor 1.0 Device: iOS iPhone Category: Photography Price: $2.99, Version: 1.0 (iTunes) Description: Free up valuable iCloud and iPhone storage with Gallery Doctor, the only iPhone cleaner that automatically identifies the... | Read more »
You Against Me (Games)
You Against Me 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: A simple game… You. Me. Claim, steal, lock, score, win! | Read more »
Yep, it's True - Angry Birds 2 is O...
The not exactly rumors were true and the birds are back. Angry Birds 2 has come to the App Store and the world will... well I suppose it'll still be the same, but now we have more bird-flinging options! [Read more] | Read more »
You Could Design Your Own Card for Chain...
If you've ever wanted to create your own item, weapon, trap, or even monster for Chainsaw Warrior: Lords of the Night, this is your chance. Auroch Digital is currently holding a contest so that fans can fight to the death (not really) to see which... | Read more »
Bitcoin Billionaire is Going Back in Tim...
If you thought you managed to buy everything there is to buy in Bitcoin Billionaire and make all the money, well you though wrong. Those of you who made it far enough might remember investing in time travel - and it looks like that investment is... | Read more »
Domino Drop (Games)
Domino Drop 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Domino Drop is a delightful new puzzle game with dominos and gravity!Learn how to play it in a minute, master it day by day.Your... | Read more »
OPERATION DRACULA (Games)
OPERATION DRACULA 1.0.1 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0.1 (iTunes) Description: 25% off launch sale!!! 'Could prove to be one of the most accurate representations of the Japanese bullet hell shmup... | Read more »
Race The Sun (Games)
Race The Sun 1.01 Device: iOS iPhone Category: Games Price: $4.99, Version: 1.01 (iTunes) Description: You are a solar craft. The sun is your death timer. Hurtle towards the sunset at breakneck speed in a futile race against time.... | Read more »

Price Scanner via MacPrices.net

Sale! 13-inch MacBook Pros on sale for $100 o...
B&H Photo has 13″ MacBook Pros on sale for $100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.5GHz/500GB MacBook Pro: $999.99 save $100 - 13″ 2.7GHz/128GB Retina... Read more
Sale! Save $100 on 13-inch MacBook Airs this...
B&H Photo has the 13″ 1.6GHz/128GB MacBook Air on sale for $899.99 including free shipping plus NY tax only. Their price is $100 off MSRP, and it’s the lowest price available for this model.... Read more
Worldwide Tablet Market Decline Continues, Ap...
The worldwide tablet market declined -7.0% year-over-year in the second quarter of 2015 (2Q15) with shipments totaling 44.7 million units according to preliminary data from the International Data... Read more
TP-LINK TL-PA8030P KIT Powerline Featuring Ho...
Consumer and business networking products provider TP-LINK is now shipping its TL-PA8030P KIT AV1200 3-Port Gigabit Passthrough Powerline Starter Kit that expands your home’s network over its... Read more
Apple refurbished iPad Air 2s available for u...
The Apple Store has Apple Certified Refurbished iPad Air 2s available for up to $140 off the price of new models. Apple’s one-year warranty is included with each model, and shipping is free: - 128GB... Read more
Updated Apple iPad Price Trackers
We’ve updated our iPad Air Price Tracker and our iPad mini Price Tracker with the latest information on prices and availability from Apple and other resellers. Read more
Apple refurbished 2014 13-inch 128GB MacBook...
The Apple Store has Apple Certified Refurbished 2014 13″ MacBook Airs available starting at $759. An Apple one-year warranty is included with each MacBook, and shipping is free: - 13″ 1.4GHz/128GB... Read more
Apple’s Education discount saves up to $300 o...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
Save up to $600 with Apple refurbished Mac Pr...
The Apple Store has Apple Certified Refurbished Mac Pros available for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more
Mac Pros on sale for up to $260 off MSRP
B&H Photo has Mac Pros on sale for up to $260 off MSRP. Shipping is free, and B&H charges sales tax in NY only: - 3.7GHz 4-core Mac Pro: $2799, $200 off MSRP - 3.5GHz 6-core Mac Pro: $3719.99... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales. Specialist - Retail Customer Service and Sales. Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Online Store UAT Lead - Apple (Unite...
**Job Summary** The Apple Online Store is a fast paced and ever evolving business environment. The User Acceptance Testing (UAT) lead in this organization is able to Read more
*Apple* MAC Support Services Subject Matter...
Title: Apple MAC Support Services Subject Matter Expert Location: Pleasanton, CA Type of position: Temporary Contract for approximately 6 weeks Tasks The tasks for the Read more
Lead Infrastructure Engineer - *Apple* /Mac P...
…of a team * Requires proven problem solving skills Preferred Additional: * Apple Certified System Administrator (ACSA) * Apple Certified Technical Coordinator (ACTC) Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.