From: simon Date: Mon, 8 Sep 2008 18:26:51 +0000 (+0000) Subject: Add a "-r" option to lns, which causes it to mirror an entire X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/utils/commitdiff_plain/53ddbe1a6b31f834ed918d41fcecbb76e08f87bd Add a "-r" option to lns, which causes it to mirror an entire directory tree in symlinks by constructing a parallel directory hierarchy and populating it with symlinks to all the files in the original one. I've wanted a tool that does that for ages, but never got round to sitting down and writing it; today I suddenly realised it's almost trivial to add as an extra option in lns, because nearly all the required infrastructure is already present. git-svn-id: svn://svn.tartarus.org/sgt/utils@8171 cda61777-01e9-0310-a592-d414129be87e --- diff --git a/lns/lns b/lns/lns index db35f9a..c8f20a7 100755 --- a/lns/lns +++ b/lns/lns @@ -25,6 +25,7 @@ # even if file2 is a link to a directory. This option implies -f. use Cwd; +use POSIX; # for opendir and friends $usage = "usage: lns [flags] srcfile destfile\n". @@ -32,6 +33,8 @@ $usage = "where: -a create symlinks with absolute path names\n". " -f overwrite existing symlink at target location\n". " -F like -f, but works even if target is link to dir\n". + " -r recursively construct a directory tree which\n". + " mirrors the source, with symlinks to all files\n". " -v verbosely log activity (repeat for more verbosity)\n". " -q suppress error messages on failure\n". " also: lns --version report version number\n" . @@ -61,7 +64,7 @@ $licence = "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n" . "SOFTWARE.\n"; -$abs=$force=$quiet=$verbose=$FILE=0; +$abs=$force=$quiet=$verbose=$recurse=$FILE=0; while ($_=shift @ARGV) { last if /^--$/; unshift (@ARGV, $_), last unless /^-(.*)/; @@ -83,6 +86,7 @@ while ($_=shift @ARGV) { if ($opt eq "a") { $abs=1; } elsif ($opt eq "f") { $force=1; } elsif ($opt eq "q") { $quiet=1; } + elsif ($opt eq "r") { $recurse=1; } elsif ($opt eq "v") { $verbose++; } elsif ($opt eq "F") { $force=$FILE=1; } else { die "lns: unrecognised option '-$1'\n"; } @@ -130,10 +134,17 @@ sub makelink { # name we will reference the source by. $sourcename = $abs ? $source : &relname($source, $target); + my $donothing = 0; + my $recursing = $recurse && -d $source; + my $ok; # If the target exists... if (-e $target || readlink $target) { - # If it's a symlink and we're in Force mode, remove it and carry on. - if ($force && readlink $target) { + if ($recursing && -d $target) { + # If it's a directory and we're in recursive mode, just do nothing + # and work around it. + $donothing = 1; + } elsif ($force && readlink $target) { + # If it's a symlink and we're in Force mode, remove it and carry on. unlink $target || die "lns: unable to remove link $target\n"; # Report that if in Verbose mode. warn "lns: removing existing target link $target\n" if $verbose; @@ -145,9 +156,37 @@ sub makelink { } } - # Make the link. - warn "lns: linking $source: $target -> $sourcename\n" if $verbose; - symlink($sourcename, $target) || die "lns: unable to make link to $target\n"; + if ($recursing) { + # Make the directory. + if ($donothing) { + warn "lns: directory $target already exists, no need to create it\n" + if $verbose; + $ok = 1; + } else { + warn "lns: making directory $target\n" + if $verbose; + if (mkdir $target) { + $ok = 1; + } else { + warn "lns: unable to make directory '$target': $!\n"; + } + } + # Now recurse into it. + if ($ok) { + my $dh = POSIX::opendir($source); + my @files = POSIX::readdir($dh); + my $f; + POSIX::closedir($dh); + foreach $f (@files) { + next if $f eq "." or $f eq ".."; + &makelink("$source/$f", "$target/$f"); + } + } + } else { + # Make the link. + warn "lns: linking $source: $target -> $sourcename\n" if $verbose; + symlink($sourcename, $target) || die "lns: unable to make link to $target\n"; + } } sub normalise { diff --git a/lns/lns.but b/lns/lns.but index 8bfd953..5caab26 100644 --- a/lns/lns.but +++ b/lns/lns.but @@ -36,6 +36,17 @@ programs}, exactly as you would have done if the command had been \cw{cp}; and \cw{lns} will figure out for itself that the literal text of the symlink needs to be \c{../hello.c}. +\cw{lns} also has a mode in which it will create a symlink mirror of +an entire directory tree: that is, instead of creating a single +symlink to the root of the tree, it will create \e{directories} in +the same structure as the whole of the original tree, and fill them +with individual symlinks to the files. This is occasionally handy if +you want to work with a slightly modified version of a large file +hierarchy but you don't want to waste the disk space needed to +create an entirely separate copy: you can symlink-mirror the whole +tree, and then just replace one or two of the symlinks with modified +versions of the files they point to. + \U ARGUMENTS If you provide precisely two arguments to \cw{lns}, and the second @@ -93,6 +104,23 @@ second argument as a destination \e{file} name rather than a destination directory. This option is useful for overriding an existing link to one directory with a link to a different one. +\dt \cw{-r} + +\dd Enables recursive link-tree construction. If the source pathname +exists and is a directory, then instead of creating a symlink to it +at the target site, \cw{lns} will create a fresh directory, and then +recursively attempt to link every file inside the source directory +to the inside of the new target directory. + +\lcont{ + +If a directory already +exists at the target site, \cw{lns} will recurse into it; so you +can, for instance, use \cw{lns -r -f} to refresh an existing link +tree. + +} + \dt \cw{-v} \dd Verbose mode: makes \cw{lns} talk about what it is doing. You @@ -191,6 +219,26 @@ In order to overwrite the directory symlink correctly, you need the overwriting. So now you get what you wanted: the previous symlink \cw{subdir3} is replaced with one whose link text reads \cq{subdir}. +Next, a couple of examples with \cw{-r}. Suppose you have your +subdirectory \cw{subdir}. Then running + +\c $ lns -r subdir subdir-mirror +\e bbbbbbbbbbbbbbbbbbbbbbbbbbb + +will create a new subdirectory called \c{subdir-mirror}, containing +symlinks to everything in \c{subdir}. + +If the directory \c{subdir-mirror} already existed, however, +\cw{lns}'s command-line processing will notice that it's a +directory, and will assume things are supposed to be copied \e{into} +it, so that your mirror of \c{subdir} will end up at +\c{subdir-mirror/subdir}. To fix this, you can again use \cw{-F}, to +tell \cw{lns} to literally create its output at the precise location +you specify rather than inside it: + +\c $ lns -rF subdir subdir-mirror +\e bbbbbbbbbbbbbbbbbbbbbbbbbbbb + \U LICENCE \cw{lns} is free software, distributed under the MIT licence. Type