Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Sadly, Mark Rushakoff's answer (now deleted - it referenced the code from <a href="http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html" rel="nofollow noreferrer">here</a>) does not seem to work correctly when adapted to:</p> <pre><code>source=/home/part2/part3/part4 target=/work/proj1/proj2 </code></pre> <p>The thinking outlined in the commentary can be refined to make it work correctly for most cases. I'm about to assume that the script takes a source argument (where you are) and a target argument (where you want to get to), and that either both are absolute pathnames or both are relative. If one is absolute and the other relative, the easiest thing is to prefix the relative name with the current working directory - but the code below does not do that.</p> <hr> <h3>Beware</h3> <p>The code below is close to working correctly, but is not quite right.</p> <ol> <li>There is the problem addressed in the comments from Dennis Williamson.</li> <li>There is also a problem that this purely textual processing of pathnames and you can be seriously messed up by weird symlinks.</li> <li>The code does not handle stray 'dots' in paths like '<code>xyz/./pqr</code>'.</li> <li>The code does not handle stray 'double dots' in paths like '<code>xyz/../pqr</code>'.</li> <li>Trivially: the code does not remove leading '<code>./</code>' from paths.</li> </ol> <p>Dennis's code is better because it fixes 1 and 5 - but has the same issues 2, 3, 4. Use Dennis's code (and up-vote it ahead of this) because of that.</p> <p>(NB: POSIX provides a system call <code>realpath()</code> that resolves pathnames so that there are no symlinks left in them. Applying that to the input names, and then using Dennis's code would give the correct answer each time. It is trivial to write the C code that wraps <code>realpath()</code> - I've done it - but I don't know of a standard utility that does so.)</p> <hr> <p>For this, I find Perl easier to use than shell, though bash has decent support for arrays and could probably do this too - exercise for the reader. So, given two compatible names, split them each into components:</p> <ul> <li>Set the relative path to empty.</li> <li>While the components are the same, skip to the next.</li> <li>When corresponding components are different or there are no more components for one path:</li> <li>If there are no remaining source components and the relative path is empty, add "." to the start.</li> <li>For each remaining source component, prefix the relative path with "../".</li> <li>If there are no remaining target components and the relative path is empty, add "." to the start.</li> <li>For each remaining target component, add the component to the end of the path after a slash.</li> </ul> <p>Thus:</p> <pre><code>#!/bin/perl -w use strict; # Should fettle the arguments if one is absolute and one relative: # Oops - missing functionality! # Split! my(@source) = split '/', $ARGV[0]; my(@target) = split '/', $ARGV[1]; my $count = scalar(@source); $count = scalar(@target) if (scalar(@target) &lt; $count); my $relpath = ""; my $i; for ($i = 0; $i &lt; $count; $i++) { last if $source[$i] ne $target[$i]; } $relpath = "." if ($i &gt;= scalar(@source) &amp;&amp; $relpath eq ""); for (my $s = $i; $s &lt; scalar(@source); $s++) { $relpath = "../$relpath"; } $relpath = "." if ($i &gt;= scalar(@target) &amp;&amp; $relpath eq ""); for (my $t = $i; $t &lt; scalar(@target); $t++) { $relpath .= "/$target[$t]"; } # Clean up result (remove double slash, trailing slash, trailing slash-dot). $relpath =~ s%//%/%; $relpath =~ s%/$%%; $relpath =~ s%/\.$%%; print "source = $ARGV[0]\n"; print "target = $ARGV[1]\n"; print "relpath = $relpath\n"; </code></pre> <p>Test script (the square brackets contain a blank and a tab):</p> <pre><code>sed 's/#.*//;/^[ ]*$/d' &lt;&lt;! | /home/part1/part2 /home/part1/part3 /home/part1/part2 /home/part4/part5 /home/part1/part2 /work/part6/part7 /home/part1 /work/part1/part2/part3/part4 /home /work/part2/part3 / /work/part2/part3/part4 /home/part1/part2 /home/part1/part2/part3/part4 /home/part1/part2 /home/part1/part2/part3 /home/part1/part2 /home/part1/part2 /home/part1/part2 /home/part1 /home/part1/part2 /home /home/part1/part2 / /home/part1/part2 /work /home/part1/part2 /work/part1 /home/part1/part2 /work/part1/part2 /home/part1/part2 /work/part1/part2/part3 /home/part1/part2 /work/part1/part2/part3/part4 home/part1/part2 home/part1/part3 home/part1/part2 home/part4/part5 home/part1/part2 work/part6/part7 home/part1 work/part1/part2/part3/part4 home work/part2/part3 . work/part2/part3 home/part1/part2 home/part1/part2/part3/part4 home/part1/part2 home/part1/part2/part3 home/part1/part2 home/part1/part2 home/part1/part2 home/part1 home/part1/part2 home home/part1/part2 . home/part1/part2 work home/part1/part2 work/part1 home/part1/part2 work/part1/part2 home/part1/part2 work/part1/part2/part3 home/part1/part2 work/part1/part2/part3/part4 ! while read source target do perl relpath.pl $source $target echo done </code></pre> <p>Output from the test script:</p> <pre><code>source = /home/part1/part2 target = /home/part1/part3 relpath = ../part3 source = /home/part1/part2 target = /home/part4/part5 relpath = ../../part4/part5 source = /home/part1/part2 target = /work/part6/part7 relpath = ../../../work/part6/part7 source = /home/part1 target = /work/part1/part2/part3/part4 relpath = ../../work/part1/part2/part3/part4 source = /home target = /work/part2/part3 relpath = ../work/part2/part3 source = / target = /work/part2/part3/part4 relpath = ./work/part2/part3/part4 source = /home/part1/part2 target = /home/part1/part2/part3/part4 relpath = ./part3/part4 source = /home/part1/part2 target = /home/part1/part2/part3 relpath = ./part3 source = /home/part1/part2 target = /home/part1/part2 relpath = . source = /home/part1/part2 target = /home/part1 relpath = .. source = /home/part1/part2 target = /home relpath = ../.. source = /home/part1/part2 target = / relpath = ../../../.. source = /home/part1/part2 target = /work relpath = ../../../work source = /home/part1/part2 target = /work/part1 relpath = ../../../work/part1 source = /home/part1/part2 target = /work/part1/part2 relpath = ../../../work/part1/part2 source = /home/part1/part2 target = /work/part1/part2/part3 relpath = ../../../work/part1/part2/part3 source = /home/part1/part2 target = /work/part1/part2/part3/part4 relpath = ../../../work/part1/part2/part3/part4 source = home/part1/part2 target = home/part1/part3 relpath = ../part3 source = home/part1/part2 target = home/part4/part5 relpath = ../../part4/part5 source = home/part1/part2 target = work/part6/part7 relpath = ../../../work/part6/part7 source = home/part1 target = work/part1/part2/part3/part4 relpath = ../../work/part1/part2/part3/part4 source = home target = work/part2/part3 relpath = ../work/part2/part3 source = . target = work/part2/part3 relpath = ../work/part2/part3 source = home/part1/part2 target = home/part1/part2/part3/part4 relpath = ./part3/part4 source = home/part1/part2 target = home/part1/part2/part3 relpath = ./part3 source = home/part1/part2 target = home/part1/part2 relpath = . source = home/part1/part2 target = home/part1 relpath = .. source = home/part1/part2 target = home relpath = ../.. source = home/part1/part2 target = . relpath = ../../.. source = home/part1/part2 target = work relpath = ../../../work source = home/part1/part2 target = work/part1 relpath = ../../../work/part1 source = home/part1/part2 target = work/part1/part2 relpath = ../../../work/part1/part2 source = home/part1/part2 target = work/part1/part2/part3 relpath = ../../../work/part1/part2/part3 source = home/part1/part2 target = work/part1/part2/part3/part4 relpath = ../../../work/part1/part2/part3/part4 </code></pre> <hr> <p>This Perl script works fairly thoroughly on Unix (it does not take into account all the complexities of Windows path names) in the face of weird inputs. It uses the module <code>Cwd</code> and its function <code>realpath</code> to resolve the real path of names that exist, and does a textual analysis for paths that don't exist. In all cases except one, it produces the same output as Dennis's script. The deviant case is:</p> <pre><code>source = home/part1/part2 target = . relpath1 = ../../.. relpath2 = ../../../. </code></pre> <p>The two results are equivalent - just not identical. (The output is from a mildly modified version of the test script - the Perl script below simply prints the answer, rather than the inputs and the answer as in the script above.) <em>Now: should I eliminate the non-working answer? Maybe...</em></p> <pre><code>#!/bin/perl -w # Based loosely on code from: http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html # Via: http://stackoverflow.com/questions/2564634 use strict; die "Usage: $0 from to\n" if scalar @ARGV != 2; use Cwd qw(realpath getcwd); my $pwd; my $verbose = 0; # Fettle filename so it is absolute. # Deals with '//', '/./' and '/../' notations, plus symlinks. # The realpath() function does the hard work if the path exists. # For non-existent paths, the code does a purely textual hack. sub resolve { my($name) = @_; my($path) = realpath($name); if (!defined $path) { # Path does not exist - do the best we can with lexical analysis # Assume Unix - not dealing with Windows. $path = $name; if ($name !~ m%^/%) { $pwd = getcwd if !defined $pwd; $path = "$pwd/$path"; } $path =~ s%//+%/%g; # Not UNC paths. $path =~ s%/$%%; # No trailing / $path =~ s%/\./%/%g; # No embedded /./ # Try to eliminate /../abc/ $path =~ s%/\.\./(?:[^/]+)(/|$)%$1%g; $path =~ s%/\.$%%; # No trailing /. $path =~ s%^\./%%; # No leading ./ # What happens with . and / as inputs? } return($path); } sub print_result { my($source, $target, $relpath) = @_; if ($verbose) { print "source = $ARGV[0]\n"; print "target = $ARGV[1]\n"; print "relpath = $relpath\n"; } else { print "$relpath\n"; } exit 0; } my($source) = resolve($ARGV[0]); my($target) = resolve($ARGV[1]); print_result($source, $target, ".") if ($source eq $target); # Split! my(@source) = split '/', $source; my(@target) = split '/', $target; my $count = scalar(@source); $count = scalar(@target) if (scalar(@target) &lt; $count); my $relpath = ""; my $i; # Both paths are absolute; Perl splits an empty field 0. for ($i = 1; $i &lt; $count; $i++) { last if $source[$i] ne $target[$i]; } for (my $s = $i; $s &lt; scalar(@source); $s++) { $relpath = "$relpath/" if ($s &gt; $i); $relpath = "$relpath.."; } for (my $t = $i; $t &lt; scalar(@target); $t++) { $relpath = "$relpath/" if ($relpath ne ""); $relpath = "$relpath$target[$t]"; } print_result($source, $target, $relpath); </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload