Recursive symbolic link creation script

This might be useful if you keep your settings (.bashrc, .subversion/config, etc.) outside the home directory, like in a Subversion repository. Just copy the following script to the repository directory corresponding to $HOME and run
/path/to/make-links.sh
to create symlinks from ~ to each file in the repository, recursively.

If you don’t want to use meld to see differences, just change the diff variable near the top of the code.

Edit 2: Now actually does something useful if the target file already exists or the target directory does not exist.

make-links.sh

#!/bin/sh
#
# $Id: make-links.sh 1870 2009-11-10 08:56:35Z vengmark $
#
# NAME
#    make-links.sh - Make symlinks to all user settings in repository
#
# SYNOPSIS
#    make-links.sh [options]
#
# OPTIONS
#    -v,--verbose    Verbose output
#    -d              Specify the source and target directories for the symlinks
#
# EXAMPLE
#    /path/to/make-links.sh -d ~/settings/user ~
#
#    Create links in the home directory based on files in ~/settings/user
#
#    /path/to/make-links.sh -v
#
#    Create links in / based on files in the directory of make-links.sh
#
# DESCRIPTION
#    If the file in the source directory doesn't exist in the target directory,
#    a symlink is created directly.
#    If the file exists, or the target directory does not exist, the user is
#    given options to continue.
#
# BUGS
#    Email bugs to victor dot engmark at gmail dot com. Please include the
#    output of running this script in verbose mode (-v).
#
# COPYRIGHT AND LICENSE
#    Copyright (C) 2008-2009 Victor Engmark
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
################################################################################

# Output error message with optional error code
error()
{
    if [ -z "$2" ]
    then
        error_code=$EX_UNKNOWN
    else
        error_code=$2
    fi
    echo "$1" >&2
    exit $error_code
}

usage()
{
    error "Usage: ${cmdname} [-v|--verbose] [-d source target]" $EX_USAGE
}

verbose_echo()
{
    if [ $verbose ]
    then
        echo "$*"
    fi
}

# Use for mandatory directory checks
# $1 is the directory path
# $2 is the (optional) error message
directory_exists()
{
    if [ ! -d $1 ]
    then
        error "No such directory '${1}'
$2" $EX_NO_SUCH_DIR
    fi
}

# Make sure an executable is available
# $1 is the path to the executable
# $2 is the (optional) error message
executable_exists()
{
    if [ ! -x $1 ]
    then
        error "No such executable '${1}'
$2" $EX_NO_SUCH_EXEC
    fi
}

ifs_original="$IFS" # Reset when done
IFS="
" # Make sure paths with spaces don't make any trouble when looping

PATH="/usr/bin:/bin"
cmdname=`basename $0`
directory=$(dirname $(readlink -f $0))

diff=/usr/bin/meld
source_base="${directory}/home/$(whoami)/"
target_base="$HOME"

# Exit codes from /usr/include/sysexits.h, as recommended by
# http://www.faqs.org/docs/abs/HTML/exitcodes.html
EX_OK=0           # successful termination
EX_USAGE=64       # command line usage error
EX_DATAERR=65     # data format error
EX_NOINPUT=66     # cannot open input
EX_NOUSER=67      # addressee unknown
EX_NOHOST=68      # host name unknown
EX_UNAVAILABLE=69 # service unavailable
EX_SOFTWARE=70    # internal software error
EX_OSERR=71       # system error (e.g., can't fork)
EX_OSFILE=72      # critical OS file missing
EX_CANTCREAT=73   # can't create (user) output file
EX_IOERR=74       # input/output error
EX_TEMPFAIL=75    # temp failure; user is invited to retry
EX_PROTOCOL=76    # remote error in protocol
EX_NOPERM=77      # permission denied
EX_CONFIG=78      # configuration error

# Custom errors
EX_UNKNOWN=1
EX_NO_SUCH_DIR=91
EX_NO_SUCH_EXEC=92

# Process parameters
until [ $# -eq 0 ]
do
    case $1 in
        -v|--verbose)
            verbose=1
            shift
            ;;
	-d)
	    if [ -z "$2" ] || [ -z "$3" ]
	    then
		usage_error
	    fi
	    source_base=$2
	    target_base=$3
	    shift 3
	    ;;
        *)
            # Unknown parameter
            usage
            ;;
    esac
done

verbose_echo "Running $cmdname at `date`."

# Make sure the directory paths don't end with a slash
source_base="${source_base%\/}"
target_base="${target_base%\/}"

# Preliminary checks
directory_exists "$source_base"
directory_exists "$target_base"

verbose_echo "Source directory: '${source_base}'"
verbose_echo "Target directory: '${target_base}'"

# Find files excluding .svn directories
for source_path in `find "$source_base" -mindepth 1 -type f | grep -v "/.svn/"`
do
    target_path="${target_base}${source_path#${source_base}}"
    target_dir="$(dirname "${target_path}")"
    unset replace_file
    unset create_dir

    verbose_echo ""
    verbose_echo "Source file: \"${source_path}\"."
    verbose_echo "Target file: \"${target_path}\"."

    # Trivial case
    if [ -L "$target_path" ]
    then
        verbose_echo "\"${target_path}\" is already a symlink; skipping."
        continue
    fi

    # File exists
    if [ -f "$target_path" ]
    then
        # Make sure we skip or replace in the end
        while ! expr "$replace_file" : "^[SsRr]$" > /dev/null
        do
            echo "\"${target_path}\" is a proper file. What do you want to do?"
            read -p "[S]kip, [D]iff, [R]eplace: " replace_file
            if expr "$replace_file" : "^[Dd]$" > /dev/null
            then
                verbose_echo "Diffing \"${source_path}\" and \"${target_path}\""
                $diff "$source_path" "$target_path"
            fi
        done

        if expr "$replace_file" : "^[Rr]$"
        then
            verbose_echo "Removing ${target_path}"
            rm "$target_path"
        else
            continue
        fi
    fi

    # Not a proper file
    if [ -e "$target_path" ]
    then
        echo "\"${target_path}\" exists but is not a file; skipping."
        continue
    fi

    # Directory missing; might have to create it
    if [ ! -e "$target_dir" ]
    then
        while ! expr "$create_dir" : "^[SsCc]$" > /dev/null
        do
            echo "\"${target_dir}\" doesn't exist. What do you want to do?"
            read -p "[S]kip or [C]reate: " create_dir
        done

        if expr "$create_dir" : "^[Cc]$" > /dev/null
        then
            mkdir -p "$target_dir"
        else
            continue
        fi
    fi

    echo "Creating symlink at \"${target_path}\"."
    ln -s "$source_path" "$target_path"
done

verbose_echo "Cleaning up."
IFS="$ifs_original"

verbose_echo "${cmdname} completed at `date`."
exit $EX_OK
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s