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
# Create symlinks
for i in ${dotFiles[*]}; do
dotFileLink="dotfiles/"$i
ln -s $dotFileLink $i
done
3 Answers 3
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.
I’m not sure why you’re using arrays for
dotFiles
andtoArchives
here. Using plain variables should work just as well.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
# Create symlinks
for i in $dotFiles; do
dotFileLink="dotfiles/"$i
ln -s $dotFileLink $i
done
-
\$\begingroup\$ Awesome! Thank you :) I used arrays because that's the type I'm used to iterating through. \$\endgroup\$Xophmeister– Xophmeister2012年03月31日 14:28:11 +00:00Commented Mar 31, 2012 at 14:28
-
\$\begingroup\$ @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. \$\endgroup\$Konrad Rudolph– Konrad Rudolph2012年03月31日 15:02:38 +00:00Commented Mar 31, 2012 at 15:02
-
\$\begingroup\$ @Konrad Rudolph +1 for
set -a
andset -u
. Apparently I need to get more familiar with theset
builtin. \$\endgroup\$Barton Chittenden– Barton Chittenden2012年04月14日 16:57:14 +00:00Commented Apr 14, 2012 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
# Create symlinks
cur=$(pwd)
for i in $(dotfiles); do
{cd ~; ln -s $cur/$i $i}
done