Hey all you wonderful GNU folks,

I've written a utility that may be appropriate for future inclusion in the gnu 
core utils.  It assists in the creation of relative-path symlinks, enabling 
tab-completion in a number of cases where that fails for ln -s, and 
preventing the creation of dead links.

For now, the tool is called "rln" ("r" is for relative).  I have attached the 
decently-commented bash source for your amusement!  It seems to work pretty 
well, though admittedly a c implementation would be much better.  For 
convenience, I gave it a .txt extention and removed #!/bin/bash from the top 
(so email clients will treat it as plain text).

It would be fun to continue development of this tool and rewrite it in c, but 
the truth is that I've already fulfilled my personal need for the tool.  
However, if you folks at gnu think it might be useful to others, I would love 
to continue work on this project, which would be my first c contribution to 
the community.  In case it's of any relevance, I'd be happy to release it 
under any version of the GPL, and/or simply donate it to the FSF.

Of course, this may have been tried and rejected dozens of times in the past.  
I don't really know.  In any case, I hope you'll get back to me and tell me 
what you think.

Thanks for your time!
~David.
# rln by David Laurence Emerson <[EMAIL PROTECTED]>
# version 0.01
# 2006-02-21
# 
# This bash script is insanely slow and would be well replaced by a c
# implementation.
# 
# rln stands for "relative (sym)link".  It helps create symlinks by
# intelligently finding an efficient relative path.
# 
# Suppose you have a directory structure "/a/b/c/d/e".
# Within "e" there are several files E1 E2 E3 E4 E5 E6.
# Now, say you want quick access to several "E" files from within "/a"
# One convenient way to achieve this is by making a few symlinks.
# 
# It would be nice to "cd /a/b/c/d/e" and "ln -s E2 E4 E6 /a"
# But that makes links like E4 -> E4 instead of E4 -> b/c/d/e/E4
# With "ln -s" you must explicitly specify each path relative to "/a",
# necessitating many keystrokes:
# "ln -s b/c/d/e/E2 b/c/d/e/E4 b/c/d/e/E6 /a"
# (or something silly like
#      "for i in E2 E3 E4; do ln -s b/c/d/e/$i /a; done")
# 
# rln enables use of the shorter syntax (described first).
# The first clear benefit is that it enables use of tab-completion while
# typing in a directory other than the destination directory.
# Additionally, it lacks the ability to create dead links -- which may be
# viewed as either a feature or a bug ;-)  It uses "realpath" to help find
# efficient relative paths between the "target" files to be linked to
# and the "destination" where the symlink(s) will be stored.
# 
# Bugs/features TODO...
# - option/default to use absolute path when going through /
# - support -f option to overwrite files
# - similar options to overwrite only symlinks or only dead symlinks
# - support --target-directory option for xargs compliance
# - other gnu options, e.g. --version
# - fix error exit codes to be more meaningful or standards-compliant
# - indentation
# - optimization? It'll be easier to write an algorithm using c.
# - option to follow symlinks if possible? (use pwd and realpath)
# - allow linking to files that do not exist, or to dead symlinks?
# - allow defaulting of destination "." ?
# - anything else? more documentation?


USAGE="Usage: rln target1 [target2 target3 ...] destination\nFor each target, a 
relative symlink from destination to target will be placed in destination.\nIf 
there is _only one_ target, destination may be a new file name."

if (( $# < 2 )) || \
   [[ "$1" == "--help" ]] || \
   [[ "$1" == "-h" ]] || \
   [[ "$1" == "-?" ]]; then
 echo -e "$USAGE"
 exit 0
fi

# The final parameter is assumed to be the destination
eval final_dest=\$$#

# It ought to be a directory...
if [ -d "$final_dest" ]; then
 dest_dir=`realpath "$final_dest"`
# but if it's not a directory, it should not exist...
elif [ -e "$final_dest" ]; then
 echo "Destination already exists, and is not a directory: $final_dest"
 exit 1
else
 # we'll need the dest_dir several times...
 dest_dir=`dirname "$final_dest"`
 # The parent directory must exist...
 if [ ! -d "$dest_dir" ]; then
  echo "Parent directory of destination does not exist: $dest_dir"
  exit 1
 # okay, so the destination will be the name of the symlink file
 # however, this means that there can be only one target
 elif (( $# > 2 )); then
  echo -e "$USAGE"
  exit 1
 # And it looks like we have a valid destination...
 else
  dest_dir=`realpath "$dest_dir"`
 fi
fi

while (( $# > 1 )); do
 # grab the first file to be linked...
 target="$1"
 # and shift the list; note we stop when $# == 1 ($1 == destination)
 shift

 # We'll need to work with the absolute path
 true_target=`realpath "$target"`
 if (( $? != 0 )); then
  echo "error... realpath $target"
  realpath "$target"
  exit 1
 fi
 
 cut_target=`echo "$true_target" | sed 's/^\///'`
 cut_dest=`echo "$dest_dir/" | sed 's/^\///'`
 root_target=`echo "$cut_target" | sed -r 's/^([^/]+\/).*/\1/'`
 root_dest=`echo "$cut_dest" | sed -r 's/^([^/]+\/).*/\1/'`
 
 while [[ "$root_target" == "$root_dest" ]]; do
  len_root=`expr length "$root_dest"`
  cut_target=`expr substr "$cut_target" $((len_root+1)) 65535`
  cut_dest=`expr substr "$cut_dest" $((len_root+1)) 65535`
  root_target=`echo "$cut_target" | sed -r 's/^([^/]+\/).*/\1/'`
  root_dest=`echo "$cut_dest" | sed -r 's/^([^/]+\/).*/\1/'`
 done

 # replace each directory level with a "../"
 rel_dest=`echo "$cut_dest" | sed -r 's/[^/]+\//..\//g'`
 # and paste those "../"s in front of the target...
 rel_target="${rel_dest}${cut_target}"
 
 ln -s $rel_target $final_dest
 
done

exit 0
_______________________________________________
Bug-coreutils mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/bug-coreutils

Reply via email to