A Script to Install My Dotfiles

May 31, 2017 | posted in: nerdliness

A year or so ago I created a script that allowed me to quickly install my dotfiles on a new computer. The script wasn't terribly sophisticated, but it got the job done. Called make.sh it takes an all-or-nothing approach to setting up my configurations.

Recently I ran across a Bash script that serves as a general purpose Yes/No prompt function. Using this script as a jumping off point I was able to create a more sophisticated install.sh script that allows me a more granular approach to setting up my dotfiles.


The ask function lets you create Yes/No prompts quckly and easily. Follow the link above for more details. I was able to default some of my configurations to Yes or install, and others to No or don't install.

Key/Value Pairs

In order to keep my script DRY I needed to have a list of the configuration files paired with their default install/don't install setting. Turns out you can do key/value pairs in Bash. It works like this:

for i in a,b c_s,d ; do 
  echo $KEY" XX "$VAL;

The key/value pairs are comma separated and space delimited, e.g., key,value key,value key,value. By using Bash parameter substitution it's possible to separate the key and value portions of each pair.

My list of pairs looks like this:

tuples="bash,Y gem,Y git,Y openconnect,Y tmux,Y slate,Y hg,N textmate,N"

The loop the processes these pairs looks like this:

for pair in $tuples; do
  if ask "Setup $dir" $flag; then
    echo "Linking $dir files"
    cd $dotfiles_dir/$dir;
    for file in *; do
      ln -sf $dotfiles_dir/$dir/$file ${HOME}/.$file
  echo ""

Each key/value pair is a directory (dir) and a install/don't install flag (flag). My dotfiles repository is organized into directories, one for each tool or utility. The fourth line is where the ask function comes into play. Using the flag from the key/value pair it creates a prompt that is defaulted to either Y/n or y/N so that all I need to do is hit the enter key. Within each directory there are one or more files needing to be symlinked. The inner loop walks through the list of files creating the necessary symlink.

Linking Directories

Some of my configurations have directories or are trageted at locations where simple symlinking won't work.

Neovim, for example, all lives in ~/.config/nvim. Symlinking directories can produce unexpected results. Using the n flag on the symlink statement treats destination that is a symlink to a directory as if it were a normal file. If the ~/.config/nvim directory already exists, ln -sfn ... prevents you from getting ~/.config/nvim/nvim.

My Vim setup contains both directories and individual files.

My ssh config needs to be linked into the ~/.ssh directory.

The linking for each of these three exceptions is handled outside the main loop in the script.

The install.sh script

Here's the entire install.sh script.

Vim Macros Rock

February 11, 2016 | posted in: snippet

Today I had to take a two column list of fully qualified domain names and their associated IP addresses and reverse the order of the columns. Using Vim macros I was able to capture all the steps on the first line and then repeat it for the other 80-odd lines of the file.

Here's a sanitized sample of the data:

as1.example.com , 10.600.40.31 ,
as2.example.com , 10.600.40.32 ,
db1.example.com , 10.600.40.75 ,
db2.example.com , 10.600.40.76 ,
db3.example.com , 10.600.40.79 ,
db4.example.com , 10.600.40.80 ,
db5.example.com , 10.600.40.81 ,
dr-as1.example.com , 10.600.40.43 ,
dr-fmw1.example.com , 10.600.40.44 ,
dr-oid1.example.com , 10.600.40.39 ,
dr-web1.example.com , 10.600.40.45 ,
fmw1.example.com , 10.600.40.33 ,
fmw2.example.com , 10.600.40.34 ,
oid1.example.com , 10.600.40.29 ,
oid2.example.com , 10.600.40.30 ,
web1.example.com , 10.600.40.35 ,
web2.example.com , 10.600.40.36 ,

What I wanted was the IP address first, surrounded in single quotes, follwed by a comma, then follwed by an in-line comment containing the FQDN. This crytpic string of Vim commands does that:

vWWdf1i'<esc>f i', #<esc>pd$

Let's break that down.

v - Enter Visual mode
W - Select a Word, in this case the leading spaces before the FQDN
W - Select a Word, in this case the FQDN, including the trailing comma
d - Put the selection in the cut buffer
f1 - Find the start of the IP address, they all start with 1 in my data set
i'<esc> - Insert a single quote and escape from insert mode
f  - Find the next blank, or the end of the IP address
i', #<esc> - Insert the closing single quote, a space, a comma, and the in-line comment character, escape insert mode
p - Paste the contents of the cut buffer, the FQDN
d$ - Delete to the end of the line to clean up the errant commas from the cut/paste 

To capture this command string in a macro you need to record it. Macros and You is a really nice introduction to Vim macros. To start recording a macro you press the q key. The next key determines the buffer or name for the macro. Then you enter the command string. To stop recording press the q key again. For simplicity sake I tend to name my macros q, so to start recording I press qq and then enter the steps outlined above, followed by q to stop recording.

Playing back the macro is done with the @ command, followed by the letter or number naming the macro. So, @q.

Macros can be proceeded by a number, like regular Vim commands. In my case with 80 lines to data to mangle, I'd record the macro on line one, and then run it against the remaining 79 lines with 79@q. There is a problem with my command string though, it works on one line only. I need to add movement commands to the end of it to position the insertion point to the beginning of the next line. The updated command sting would be this:

vWWdf1i'<esc>f i', #<esc>pd$j0

The j0 jumps down a line and goes to the beginning. Now when the macro is run, it will march down through the file a line at a time, transforming the data. Here's the result.

'10.600.40.31', #   as1.example.com
'10.600.40.32', #   as2.example.com
'10.600.40.75', #   db1.example.com
'10.600.40.76', #   db2.example.com
'10.600.40.79', #   db3.example.com
'10.600.40.80', #   db4.example.com
'10.600.40.81', #   db5.example.com
'10.600.40.43', #   dr-as1.example.com
'10.600.40.44', #   dr-fmw1.example.com
'10.600.40.39', #   dr-oid1.example.com
'10.600.40.45', #   dr-web1.example.com
'10.600.40.33', #   fmw1.example.com
'10.600.40.34', #   fmw2.example.com
'10.600.40.29', #   oid1.example.com
'10.600.40.30', #   oid2.example.com
'10.600.40.35', #   web1.example.com
'10.600.40.36', #   web2.example.com

While it may take a little trial and error to capture the right set of commands in the macro to accomplish the transforms you want, the time and effort saved over a large file is well worth it. That watching your macro work through your file is fun too, is icing on the cake.

Fun With Bash Shell Parameter Expansion

February 08, 2016 | posted in: snippet

Recently I switched back to bash from zsh for my shell environment. I needed a consistent shell on my local machines as well as on remote servers. One aspect of my bash environment that wasn't working the way I wanted was displaying the current Git branch and Git status information when the current directory was Git controlled.

In my original attempt at building my prompt I combined PS1 and prompt_command. This worked on OS X machines, but not on Linux-based operating systems. After splitting apart the line of information I wished to display via the prompt_command from the actual prompt (controlled by PS1), none of the PS1 substituitions were working. Here's the line before:

function prompt_command {
  export PS1="\n\u at \h in \w $(git_prompt_string)\n$ "

And here's the code after:

function prompt_command {
  printf "\n$(id -un) at $(hostname) in ${PWD} $(git_prompt_string)"

The PROMPT_COMMAND is set to the function above, and the PS1 prompt has the $:

export PROMPT_COMMAND=prompt_command
export PS1="\n$ "

Instead of using \u for the current user, I'm using id -un. For the hostname, hostname rather than \h. And PWD displays the current working directory in place of \w.

The problem with PWD is that it displays the full path, and I wanted a ~ when in my $HOME directory. Fortunately Steve Losh has already solved this puzzle in his My Extravagent Zsh Prompt posting.

Here's the solution:


It's deceptively simple, and took me a few minutes to understand, with the help of the Shell Parameter Expansion section of the Bash Manual.

The pattern ${parameter/pattern/string} works in the following manner.

The pattern is expanded to produce a pattern just as in filename expansion. Parameter is expanded and the longest match of pattern against its value is replaced with string. If pattern begins with ‘/’, all matches of pattern are replaced with string. Normally only the first match is replaced. If pattern begins with ‘#’, it must match at the beginning of the expanded value of parameter. If pattern begins with ‘%’, it must match at the end of the expanded value of parameter. If string is null, matches of pattern are deleted and the / following pattern may be omitted. If parameter is ‘@’ or ‘’, the substitution operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with ‘@’ or ‘’, the substitution operation is applied to each member of the array in turn, and the expansion is the resultant list.

What all that means is $HOME is expanded and if it matches the expanded $PWD, starting at the beginning of the string, then the matching characters are replaced with a ~. The key is the # before $HOME.

Here's the final printf line:

printf "\n$(id -un) at $(hostname) in ${PWD/#$HOME/~} $(git_prompt_string)"A

You can see the complete .bashrc file in my dotfiles repository.

Installing My Dotfiles Via A Script

January 30, 2016 | posted in: snippet

For too long now I have been putting off creating a script to setup my collection of dotfiles on a new machine. My excuse has always been, "I don't need to set them up on a new machine that often." Still it would be nice to run one command rather then enter multiple ln -s ~/.dotfiles/... ... commands in a row.

Here's my make.sh script:

#!/usr/bin/env bash

# This script creates symlinks for desired dotfiles in the users home diretory.

# Variables
dirs="bash gem git openconnect tmux"

# Update dotfiles to master branch
echo "Updating $dotfiles_dir to master"
cd $dotfiles_dir;
git pull origin master;

echo ""

function makeLinks() {
  # For each directory in dirs, make a symlink for each file found that starts
  # with a . (dot)
  for dir in $dirs; do
    echo "Linking $dir files"
    cd $dotfiles_dir/$dir;
    for file in *; do
      ln -svf $dotfiles_dir/$dir/$file ~/.$file
    echo ""

  # Handle odd-ball cases
  # Vim files¬
  echo "Linking vim files"
  ln -svf $dotfiles_dir/vim ~/.vim;
  ln -svf $dotfiles_dir/vim/vimrc ~/.vimrc;
  ln -svf $dotfiles_dir/vim/vimrc.bundles ~/.vimrc.bundles;

  # ssh
  echo ""
  echo "Linking ssh configuration."
  ln -svf $dotfiles_dir/ssh/config ~/.ssh/config

  echo ""
  echo "Caveats:"
  echo "Vim:  If remote server, rm .vimrc.bundles"
  echo "Bash: If local server, rm .bashrc.local"

  echo ""

  echo "Finished."

if [ "$1" == "--force" -o "$1" == "-f" ]; then
  read -p "This may overwrite existing files in your home directory. Are you sure? (y/n) " -n 1;
  echo ""
  if [[ $REPLY =~ ^[Yy]$ ]]; then
unset makeLinks;

Some Caveats:

  • This script works for the way I have my dotfiles arranged in ~/.dotfiles. Each tool has a directory containing the file or files that make up the configuration. None of the files are preceeded by a dot (.) in my repository, so the link command adds that.

  • My Vim configurtion and my ssh config don't follow this pattern, so they are handled separately.

The dirs variable has a list of the configurations I want to setup using this script. All of the files in each of those directories is symlinked in turn. I'm using the -svf flags on the ln statement.

  • s for symlink, of course
  • v for verbose
  • f for force if the link already exists

To make the script a scant more friendly it offers a --force option, that eliminates the "Are you sure?" prompt.

As with any script you find laying around on the Internet, read the source and understand what it's doing before unleashing it's awesome powers on your computer.

Bash History Search Bind Keys

January 26, 2016 | posted in: snippet

I recently switched back to bash shell from zsh and in doing so I lost zsh's history search. From your zsh prompt if you type in part of a command and then press the up arrow, you'll be shown the previous occurrence of that command. Repeated up arrows walk you through all previous occurrences. A very handy tool, and one I grew fond of.

Here's how to have this history search in bash.

First use the read command to learn what code is transmitted by the up or down arrow key press.

$ read
^[[A  # up arrow
^[[B  # down arrow

Control-c will return you to your prompt from the read builtin command.

Parsing the up and down arrow strings reveals that they both start with an escape character ^[ and then the key value itself: [A or [B.

The bash function to search history is history-search-backward or history-search-forward. So binding ^[[A to history-search-backward and ^[[B to history-search-forward emulates the arrow key behavior from zsh.

Here is what I have in my .bash_bindkeys file, which is sourced from my .bashrc file.

bind '"\e[A":history-search-backward'
bind '"\e[B":history-search-forward'

The \e is the escape character (^[) from the read builtin output. With these bindings in my .bashrc I can enter part of a command and search back through my history using my arrow keys.

2015 Books

December 30, 2015 | posted in: nerdliness

I read or listened to a total of 123 books in 2015. 40 were brand new to me, the other 83 were rereads or re-listens.

The longest book was Neal Stephenson's "Seveneves: A Novel" at 880 pages.

The shortest was "The Countess of Stanlein Restored: A History of the Countess of Stanlein Ex Paganini Stradivarius Cello of 1707", a history of a Stradivarius cello at 120 pages.

In total I read 39,160 pages, or 108 pages a day average for the year.

17 of the titles on my list were audio books. The longest of these was (again) a Neal Stephenson book, "Reamde" at 38 hours and 34 minutes.

The shortest audiobook was a mere 9 hours; "The Hanged Man's Song" by John Sandford.

In total I listened to 249 hours and 41 minutes of audio books this year. Which works out to 41 minutes per day average.

Ten of the books were non-fiction, eight were science fiction, one was historical fiction, and the rest fiction. Thirty-one of the books were from our local public library, the rest I own in one format or another.

Out of all the books I read or listened to this year, Andy Weir's "The Martian" was far and away my favortie book. Not only did I read it multiple times, I listened to the audio version. And I saw it in the theater when it came out. And I bought a copy on iTunes that I've now watched twice in a row. It's easily one of the very best books I've read in a long, long time.


November 14, 2015 | posted in: life


How to Spell Check with Vim

October 02, 2015 | posted in: nerdliness

I have never been a good speller, therefore I rely on spell check to help ensure that my writing doesn't contain basic spelling errors. Most modern software that is centered around text provides spell checking. However I do most of my writing, including all of the posts on Zanshin.net, using Vim - a decidedly un-modern text editor.

Fortunately Vim is incredibly flexible and it is relatively straight-forward to enable spell checking.

Enable spell checking

You can turn spell checking on or off with

set spell
set nospell

Default language

The default language used is US English. You can change this to another language with

set spelllang=en_gb

The above example sets the language to British English.

In my .vimrc I have a number of file type specific settings, including spell checking. I only enable spell checking for a limited number of file types, as spell checking code isn't very useful. The three autocmd entries I have are:

autocmd FileType mail setlocal spell spelllang=en_us
autocmd BufRead COMMIT_EDITMSG setlocal spell spelllang=en_us
autocmd BufNewFile,BufRead *.md,*.mkd,*.markdown set spell spelllang=en_us

The first is for when I'm using mutt for my mail - it turns spell checking on while I'm composing or replying to messages. The second activates spell check for Git commit messages. The last autocmd set spelling on for Markdown files.

Your own dictionary

It is possible to add words to your own dictionary using the zg key combination. You can undo the add with zug. It is also possible to mark a word as incorrectly spelled using zw. zuw undoes the incorrect marking.

Find misspelled words

You can jump forwards or backwards through the buffer to the next flagged work using


Once you've located a word, z= will bring up the list of suggested words, pick the associated number and press return and the new word will be substituted in for the old one.


All of this and more can be found in the Vim help pages

:help spell

Change sshd Port on Mac OS X El Capitan

October 01, 2015 | posted in: nerdliness

Previously I had written about how to change the sshd port for Mac OS X Lion and Mountain Lion. The basic premise is still the same, but the introduction of El Capitan's System Integrity Protection (SIP) requires a slightly altered approach to having sshd running on an alternate port.

In a nutshell the controlling plist, /System/Library/LaunchDaemons/ssh.plist, can no longer be edited. So instead you need to make a copy of this file and store it elsewhere. In my case I put it in /Library/LaunchDaemons. Then using launchctl you can cause your new plist to be started whenever the computer is booted. The detailed steps follow.

Create a copy of the plist

Copy ssh.plist using sudo and rename it to ssh2.plist. The renaming is important, otherwise launchctl won't discriminate between your plist and the official one, which will result in your alternate port not being visible.

$ sudo cp /System/Library/LaunchDaemons/ssh.plist /Library/LaunchDaemons/ssh2.plist

A new Label and a new port number

There are two changes to make in the plist. First it needs a different label to distinguish it from the original, and second it needs to specify the alternate port you wish to use.

Provide a different Label for the plist by changing this:


to this:


Setting the port number to your alternate by changing the SockSeviceName string (ssh) to the port number you want to use. In other words, change this:


to this:


where 99999 is a valid port number.

Activate your new sshd port

Using launchctl you can launch a new instance of the sshd daemon, one that listens to your alternate port. The launchctl command to do this looks like:

$ sudo launchctl load -w /Library/LaunchDaemons/ssh2.plist

If you ever wanted to unload this plist, run this command:

$ sudo launchctl unload /Library/LaunchDaemons/ssh2.plist

To verify that your new port is being listened to, run

$ netstat -at | grep LISTEN

Your new sshd port should be listed.


As always, making changes to the murky innards of your operating system and its supporting configurations can be risky. Make copies, backup before making changes, and proceed with caution. It is worth noting that this setup does not turn off port 22, it merely allows access on an alternate port. The machine I did this to is behind my employer's boarder and firewall which blocks port 22 traffic.


I made use of the following resources for this posting.

Power to Weight

August 30, 2015 | posted in: nerdliness

With a new 2016 MINI Cooper S making its way to me I've been thinking a lot about cars lately. The Cooper S has a reputation for being quick and nimble. It boasts a sub-seven second zero-to-sixty time. I was curious how the MINI compared to the other cars I've owned. Some Internet searching and a spreadsheet later and I had my answer.

Year Make/Model Engine (cc) Weight (lbs) Horsepower Torque (ft-lb) lb/HP
1984Pontiac Fiero247124629213426.76
1987Honda Prelude1958241011011421.91
1992Mazda Navajo3958398015522025.68
1994BMW 318i1796335213812924.29
1992Lexus ES 3002959336218519518.17
1999Nissan Altima2389285915015419.06
2000Lexus ES 3002995335121022015.96
2001Audi TT Coupe1781320818017317.82
2001Lexus LS 4304300395529032013.64
2010Honda Insight133927349812327.90
2016MINI Cooper S2000278518920714.74

With the exception of the 2001 Lexus LS 430, the MINI Cooper S has the best power-to-weight ratio of any car I've owned. The 430 had a monster 4.3 liter engine, more than twice the size of the MINI's 2 liter engine, but it's also roughly 1200 pounds heavier. I think the MINI will feel faster, and it'll certainly corner better.

The best power-to-weight ratio vehicle I've ever owned was a 1987 Suzuki Katana 1100F. With a dry weight of 537 pounds and 136 horsepower, it's ratio is 3.95 pounds-per-horsepower. Incredibly fast with instant acceleration at any speed. Not much fun in the winter though, and no room for a cello.