Toward a More Powerful Terminal on MAC OS

From korrekt.org

Mac OS comes with a functional terminal that many Linux users will appreciate. Yet, it is soon obvious to power users that Apple's default command line is much less powerful and user-friendly than it is the case in Linux. Below are some walk-through steps on how to change this.

This article is part of my Mac OS Installation Guide for Linux Users. It has been written in August 2010.

For installing Linux tools, I generally use MacPorts that can be installed as described on their homepage. One could also install the respective software in other ways, e.g., by using Fink or by compiling them manually. This may change some of the installation paths below but will otherwise not affect the basic steps.

Selecting a better shell

  • Make sure you have got bash of version 4.0 or greater (check with bash --version). On Snow Leopard, the default bash is still in version 3. If you already have got bash4, you can skip the next step.
  • Install bash4. Using MacPorts, type port install bash. To make this your default login shell, set the path in Terminal.app -> Preferences -> Startup to /opt/local/bin/bash. The default used when typing bash in the command line is already set by MacPorts, as can be confirmed by which bash.
  • Set the shell for your root user by typing sudo chsh (vi editing commands apply). The path in MacPorts is /opt/local/bin/bash.

Running commands: auto completion and more

  • Most likely you want to have smarter bash completion. For example, typing cd TAB should complete only on directories, not on all files, and typing svn TAB should give you a list of SVN commands, not files. To get this (and much more), you first need to install the bash-completion program:
port install bash-completion
  • To enable this feature and further useful settings, you must edit your bash configuration file. I edited the system wide config in /etc/bashrc to contain the following text:
# System-wide .bashrc file for interactive bash(1) shells.
if [ -z "$PS1" ]; then
   return
fi

# Do not put duplicates into history (see bash(1) for more options):
HISTCONTROL=ignoredups:ignorespace

# Store longer bash history (defaults are 500 according to bash(1)):
HISTSIZE=1000
HISTFILESIZE=2000

# A better prompt:
PS1='\u@\h:\w$ '
# Mac default was: PS1='\h:\W \u\$ '

# Make bash check its window size after a process completes
shopt -s checkwinsize

# Define some useful aliases
alias ls='ls -G'
alias la='ls -A'
alias ll='ls -alF'

# Use bash completion
if [ -f /opt/local/etc/bash_completion ]; then
    . /opt/local/etc/bash_completion
fi
This also gives you more colour when using ls, sets a proper prompt with full path, extends your history capacity, and defines some common aliases. The last few lines for enabling bash completion are specific to the MacPorts installation. Alternatives such as Fink (which also can be used to install bash-completion) may use another directory.
  • It is worth noting that the command open in Mac can be used to open any file with the assigned application, or with another Mac app (see man open). This also applies to .app files (applications) themselves.
  • Using open is still much less convenient than simply typing the name of an App. Solutions have been suggested on the Web for solving this by (automatically) creating scripts that are called like your Apps, but this can be cumbersome when installing new applications or uninstalling old ones. My solution to the problem is to insert the following text at the end of /etc/bashrc:
## Default Mac application launcher for bash
## by Markus Kroetzsch, 2010, http://korrekt.org/
## Published under the terms of GPL http://www.gnu.org/licenses/gpl.html
command_not_found_handle() {
	## (1) Try to find a suitable App ##
	appname=${1//_/-}
	appname=${appname//-/[-_\ ]}  # accept "-" and "_" as shortcuts for " "
	# echo "Searching Mac app that matches $appname.app ..."
	# A faster way to find Apps -- avoid searching through .app directories:
	app=`find /Applications -name "*.app" -prune | grep -i -m1 "/$appname.app"`

	## (2) Decide how to launch the App ##
	if [ "$app" != "" ]
	then
		shift 1
		file=$1
		shift 1
		if [ "$file" == "" ]
		then
			echo "Launching $app ..."
                	open -a "$app"
		else
			if [ "$1" == "" ]
			then
				echo "Launching $app on $file ..."
				open -a "$app" "$file"
			else
				echo "Launching $app on $file with parameters $@ ..."
				open -a "$app" "$file" --args "$@"
			fi
		fi
		return 0
	fi

	## Give up ##
	echo $"$1: command not found"
	return 127
}
What this does is the following: whenever a command was given that could not be found, the folder /Applications and its subfolders are searched for a Mac application of that name. If found, the application is launched with the given parameters. Case will be ignored, and empty spaces can be written as - or _. So typing system-preferences works as expected.
This works only with bash version 4.0 or above. The advantage is that it always uses the applications that you actually installed, even if you move them (e.g., I have created various directories to organise /Applications). Another advantage is that you can easily overwrite the behaviour by creating scripts as proposed elsewhere: the above is only used if a given command could not be found. Overall, this is a fast, simple, and absolutely unobtrusive solution that could well be the default for Mac OS.
  • The above way of launching Mac applications is not supported by auto completion, since applications are only looked up when no other match can be found. I suspect that this could be fixed by extending the bash completion rules, but a cheaper solution that still retains the flexibility of not specifying an application's path is to set aliases for your favourite commands. Add entries such as the following to your /etc/bashrc:
alias textedit='textedit'
alias skim='skim'
alias ooffice='openoffice.org'
Note that you can also add more alias names for your applications here. Auto completion will now work even without hand-crafted launch scripts for each application.

Keyboard behaviour and shortcuts

  • To make the Home and End keys work as on all other systems when entering commands, open the Preferences of the Terminal application and go to Settings -> Keyboard. There, find the End key and edit its configuration (double click). Change the action to "Send string to shell" and enter the characters Esc (yes, the Escape key), [ and F. The text field should then show \033[F. Similarly, find the Home key and enter Esc, [ and H, which is displayed as \033[H. The keys now work as expected.
  • I am used to switching between tabs in the terminal application using Ctrl+Arrowkey. The default Mac shortcut is Applekey+{ and Applekey+} which are very cumbersome on non-US keyboards. You can change the shortcut in Preferences -> Keyboard -> Keyboard Shortcuts by adding shortcuts ("+") and typing the name of the Termnial.app menu items ("Select Next Tab", "Select Previous Tab"). I use Applekey+Arrowkey now. Closing and opening tabs works with Appleky+T and Applekey+W just like in Firefox.

Open issues

  • How to make the command-not-found handler play together more nicely with Apple's Spaces (virtual desktops)? Currently, opening a file randomly throws you to some other virtual desktop if the respective application is already running somewhere. Using the "-n" parameter for open is better, but has the severe disadvantage of creating a whole new application instance instead of just opening a new window.
  • How to get PageUp/PageDown and the mouse wheel to work properly when viewing long texts in less or vim?
  • How to get Home and End keys work in vim?

Contact and feedback

Comments and feedback for this article can be sent to me via email: markus at this domain.