# Shell script to sync dotfiles repository

I'm doing the git dot files thing and created a script -- my first Unix shell script -- to synchronise the repo dot files with those in the user's home. It checks which dot files exist in the repo folder (except for .git) and then archives any clashes in the home directory before symlinking to the repo... I wrote this largely by experimentation and Googling. It works, but I'm just checking to see if there's anything I missed or if there are better solutions?

#!/bin/sh

# Archive existing dot files and symlink to the new ones
# Run from ~/dotfiles

# All dot files apart from .git, . and ..
dotFiles=(ls -da .* | grep -Pxv '\.git|\.+')

# Existing dot files to archive
toArchive=()

cd ~

# Archive: Find clashes and tarball+gzip them
for i in ${dotFiles[*]}; do toArchive=(${toArchive[@]} ls -d $i) done if [${#toArchive[*]} != 0 ]; then
archiveFile="dotfiles"date +%Y%m%d".tar.gz"
tar czf $archiveFile${toArchive[*]} --remove-files
fi

for i in ${dotFiles[*]}; do dotFileLink="dotfiles/"$i
ln -s $dotFileLink$i
done


1. Consider putting the following at the beginning of every script:

set -e
set -u


The first makes your script abort as soon as the first command exits with an error. This ensures that your script doesn’t try to operate after it’s failed. The second yields an error whenever you try to access an empty variable.

2. I’m not sure why you’re using arrays for dotFiles and toArchives here. Using plain variables should work just as well.

3. Instead of … it’s generally advised to use $(…). This makes the expressions nestable and arguably more readable. Well … This leaves us with: set -e set -u dotFiles=$(ls -da .* | grep -Pxv '\.git|\.+')

# Existing dot files to archive
toArchive=''

cd ~

# Archive: Find clashes and tarball+gzip them
for i in $dotFiles; do toArchive=$toArchive $(ls -d$i)
done
if [ "$toArchive" != "" ]; then archiveFile="dotfiles"$(date +%Y%m%d)".tar.gz"
tar czf $archiveFile$toArchive --remove-files
fi

for i in $dotFiles; do dotFileLink="dotfiles/"$i
ln -s $dotFileLink$i
done

• Awesome! Thank you :) I used arrays because that's the type I'm used to iterating through. – Xophmeister Mar 31 '12 at 14:28
• @Xophmeister Bash can iterate just fine through space-delimited strings. But I agree that it takes some getting used to when you come from any other language. – Konrad Rudolph Mar 31 '12 at 15:02
• @Konrad Rudolph +1 for set -a and set -u. Apparently I need to get more familiar with the set builtin. – Barton Chittenden Apr 14 '12 at 16:57

I have have an issue with the use of ls in

 dotFiles=$(ls -da .* | grep -Pxv '\.git|\.+')  The problem is that ls is difficult to parse correctly. It doesn't handle spaces, newlines or non-ascii characters in a portable way. Also, it is often aliased by users in different ways. See http://mywiki.wooledge.org/ParsingLs for a comprehensive list of the pitfalls of parsing ls. In your case, I think that using find would be a better choice: dotDirs=$(IFS=$\000; find . -maxdepth 1 -type d -name ".*" ! -name ".git" ! -name ".." -print0 )  I also changed the name of the variable from dotFiles to dotDirs, because that's what you're actually testing for... it's true that directories are files in Unix, but it pays to be specific. In reality, your script is actually creating an array of directories which reside under dot directories in your current working directory. You can do this in one step using find, although it must be done using '-prune' to exclude the directories that you're not looking for, and that has a bit of a learning curve. For a first attempt, your effort is pretty reasonable. Here is another take on the same. An alternative way of collecting dot files We have a function that generates the filenames. dotfiles() { for i in .*; do echo$i; done | sed -e '^\.*$/d' } archivefile="dotfiles"$(date +%Y%m%d)".tar.gz"


We can depend upon tar to not create empty archives. So if the filelist is empty no tar files are created.

tar -czf --remove-files $archivefile$(dotfiles | comm -12 - <(cd ~; dotfiles) ) || exit 0

cur=$(pwd) for i in$(dotfiles); do
{cd ~; ln -s $cur/$i \$i}