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

Tunnelblick 3.7.4b - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
Carbon Copy Cloner 5.0.5 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
Postbox 5.0.22 - Powerful and flexible e...
Postbox is a new email application that helps you organize your work life and get stuff done. It has all the elegance and simplicity of Apple Mail, but with more power and flexibility to manage even... Read more
Ortelius 2.0.8 - Vector drawing app espe...
Ortelius is a full-featured vector drawing application especially for map design. Draw directly with features such as roads, rivers, coastlines, buildings, symbols and contours. Ortelius is known for... Read more
Bartender 3.0.32 - Organize your menu-ba...
Bartender lets you organize your menu-bar apps by hiding them, rearranging them, or moving them to Bartender's Bar. You can display the full menu bar, set options to have menu-bar items show in the... Read more
Adobe Animate CC 2018 18.0.1.115 - Anima...
Animate CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Flash Professional customer). Animate CC 2018 (was Flash CC) lets you... Read more
Adobe Lightroom Classic CC 7.1 - Import,...
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
ExpanDrive 6.1.8 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
ExpanDrive 6.1.8 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
DiskCatalogMaker 7.2.7 - Catalog your di...
DiskCatalogMaker is a simple disk management tool which catalogs disks. Simple, light-weight, and fast Finder-like intuitive look and feel Super-fast search algorithm Can compress catalog data for... Read more

Latest Forum Discussions

See All

Amazing Katamari Damacy guide - beginner...
Amazing Katamari Damacy brings the bizarro world of the original games to mobile and shifts them into an endless format that's just as addictive as the PlayStation entries. Your goal is still to roll as much random stuff as you possibly can, though... | Read more »
Portal Knights guide - crafting tips and...
In Portal Knights, you're only as strong as the items you have at your disposal. This sandbox adventure is all about crafting and building up the next big thing. Whether you're an avid explorer or collector, crafting will likely play a large part... | Read more »
The best deals on the App Store this wee...
A new week means new discounts on the App Store. This week's deals run the gamut of action-adventure titles, puzzle games, and one of the best narrative adventure series out there. If you're looking to fill out your mobile gaming library on a... | Read more »
What you need to know about Animal Cross...
We hope you've been hard at work on collecting all of those holiday items in Animal Crossing: Pocket Camp, because you're about to get a whole new list of fun things to do as the game receives its first big update sometime soon. There are a lot of... | Read more »
Reigns: Her Majesty guide - how to use e...
Ruling a kingdom isn't easy--doubly so for a queen whose every decision is questioned by the other factions seeking a slice of power. Reigns: Her Majesty builds on the original game's swipey tactics, adding items that you can use to move the story... | Read more »
The best new games we played this week -...
Friday has crept up on us once again, so it's time to honor the best new games we've played over the past few days. This past week was a pretty exciting one, with the debut of lots of beautiful new indies and some familiar faces returning to the... | Read more »
Portal Knights guide- beginner tips and...
Portal Knights is finally making the jump to iOS and Android, and it's already climbing the ranks to become the next big MMO experience on mobile. This sprawling sandbox game will let you pursue any adventure you wish, whether you want to sling... | Read more »
Reigns: Her Majesty guide - how to swipe...
Reigns: Her Majesty is storming the App Store this week, bringing more tinder-esque kingdom building to eager players everywhere. If you've played the original Reigns, you'll know that leading a kingdom is never easy. It's a careful balancing act... | Read more »
Getting Over It (Games)
Getting Over It 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: A game I madeFor a certain kind of person To hurt them. • Climb up an enormous mountain with nothing but a hammer and a pot.•... | Read more »
Reigns: Her Majesty (Games)
Reigns: Her Majesty 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »

Price Scanner via MacPrices.net

Apple Watch Series 2, Certified Refurbished,...
Apple has Certified Refurbished Apple Watch Nike+ Series 2s, 42mm Space Gray Aluminum Case with Anthracite/Black Nike Sport Bands, available for $249 (38mm) or $279 (42mm). The 38mm model was out of... Read more
Apple offers Certified Refurbished 2016 12″ R...
Apple has Certified Refurbished 2016 12″ Retina MacBooks available starting at $949. Apple will include a standard one-year warranty with each MacBook, and shipping is free. The following... Read more
B&H drops price on 13″ 256GB MacBook Air...
B&H has the 13″ 1.8GHz/256GB Apple MacBook Air (MQD42LL/A) now on sale for $1079 including free shipping plus NY & NJ sales tax only. Their price is $120 off MSRP, and it’s the lowest price... Read more
Holiday sale: 9″ iPads starting at $299, take...
MacMall has 9″ WiFi iPads on sale for $30 off including free shipping: – 9″ 32GB WiFi iPad: $299 – 9″ 128GB WiFi iPad: $399 Read more
Green Monday deal: 15″ 2.8GHz MacBook Pro on...
B&H Photo has the 15″ 2.8GHz Space Gray MacBook Pro on sale for $250 off MSRP for today only as part of their Green Monday/Holiday sale. Shipping is free, and B&H charges sales tax for NY... Read more
Green Monday sale: B&H offers 12″ Apple i...
B&H Photo has 12″ iPad Pros on sale for up to $150 off MSRP as part of their Green Monday/Holiday sale. Shipping is free, and B&H charges sales tax in NY & NJ only: – 12″ 64GB WiFi iPad... Read more
Holiday deal: 21″ and 27″ Apple iMacs on sale...
MacMall has 2017 21″ and 27″ Apple iMacs on sale for up to $200 off MSRP. Shipping is free: – 21″ 2.3GHz iMac: $999 $100 off MSRP – 21″ 3.0GHz iMac: $1199 $100 off MSRP – 21″ 3.4GHz iMac: $1379 $120... Read more
Holiday deal: Apple Mac minis for up to $150...
MacMall has Mac minis on sale for up to $100 off MSRP, each including free shipping: – 1.4GHz Mac mini: $399 $100 off MSRP – 2.6GHz Mac mini: $599 $100 off MSRP – 2.8GHz Mac mini: $949 $50 off MSRP... Read more
Beats by Dr. Dre – BeatsX Earphones on sale f...
Best Buy has BeatsX Earphones on sale for $109, $40 off, on their online store. Sale price for online orders only. Choose free store pickup, if available, or choose free shipping. Read more
10″ 64GB WiFi Apple iPad Pros on sale for $59...
MacMall has 10.5″ 64GB Apple iPad Pros on sale for $599 including free shipping. That’s $50 off MSRP and among the lowest prices available for these iPads from any Apple reseller. Read more

Jobs Board

QA Automation Engineer, *Apple* Pay - Apple...
# QA Automation Engineer, Apple Pay Job Number: 113202642 Santa Clara Valley, California, United States Posted: 11-Dec-2017 Weekly Hours: 40.00 **Job Summary** At Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions - Apple,...
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* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Information Security - Security Data...
# Apple Information Security - Security Data Analyst Job Number: 113119545 Austin, Texas, United States Posted: 10-Nov-2017 Weekly Hours: 40.00 **Job Summary** This Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.