使用 symlink() 函数,BSD (Mac OS X) 手册页说:
int symlink(const char *path1, const char *path2);
描述
符号链接path2 被创建到path1(path2 是创建的文件的名称,path1 是用于创建符号链接的字符串)。任一名称都可以是任意路径名;这些文件不必在同一个文件系统上。
请注意,您指定为path1 的内容将逐字用作符号链接的内容。因此,要准确地制作符号链接,您必须正确获取相对路径。也就是说,作为path1 传递的名称必须是相对于path2 的正确名称。
换句话说,您无法完全避免映射名称的过程,随之而来的困难。
我有一个 Perl 脚本 relpath,我从Convert absolute path into relative path given a current directory 的答案和comp.unix.shell 的一个相当古老的新闻组消息拼凑而成。将其中的一些转换为 C 并不超出人类的智慧。事实上,它肯定已经做过无数次了。困难在于找到代码。
相对路径
#!/usr/bin/env perl
#
# @(#)$Id: relpath.pl,v 1.4 2014/12/08 18:23:17 jleffler Exp $
#
# Usage: relpath source target [...]
#
# Purpose: Print relative path of target w.r.t. source
#
# Based loosely on code from:
# http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html
# Via: https://stackoverflow.com/questions/2564634
use strict;
use warnings;
use File::Basename;
use Cwd qw(realpath getcwd);
if (scalar @ARGV < 2)
{
my $arg0 = basename($0, ".pl");
die "Usage: $arg0 from to [...]\n"
}
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";
}
}
# Nasty!
my($source) = resolve($ARGV[0]);
my(@source) = split '/', $source;
shift @ARGV;
sub relpath
{
my($name) = @_;
my($target) = resolve($name);
print_result($source, $target, ".") if ($source eq $target);
# Split!
my(@target) = split '/', $target;
my $count = scalar(@source);
$count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";
my $i;
# Both paths are absolute; Perl splits an empty field 0.
for ($i = 1; $i < $count; $i++)
{
last if $source[$i] ne $target[$i];
}
for (my $s = $i; $s < scalar(@source); $s++)
{
$relpath = "$relpath/" if ($s > $i);
$relpath = "$relpath..";
}
for (my $t = $i; $t < scalar(@target); $t++)
{
$relpath = "$relpath/" if ($relpath ne "");
$relpath = "$relpath$target[$t]";
}
print_result($source, $target, $relpath);
}
foreach my $target (@ARGV)
{
relpath($target);
}
test.relpath
注意:这需要relpath.pl 而不仅仅是relpath。
#!/bin/ksh
#
# @(#)$Id: test.relpath.sh,v 1.1 2010/04/25 15:19:20 jleffler Exp $
#
# Test relpath Perl script fairly exhaustively
# BUG: should include expected answers!
sed 's/#.*//;/^[ ]*$/d' <<! |
/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
!
{
echo "Relative paths (source, target, relative path)"
while read source target
do
echo "$source $target $(${PERL:-perl} relpath.pl $source $target)"
done |
awk '{ printf("%-20s %-30s %s\n", $1, $2, $3); }'
}
示例输出
请注意,如果/home/part1/part2 是目录或假定为目录,则第一个相对路径../part3 是正确的。此代码在解释输出时需要小心。请注意,symlink() 系统调用不要求 path1 名称引用现有文件或目录(但相比之下,path2 不得引用现有文件或目录,尽管只有叶元素不得存在;所有以前的目录都必须存在)。
Relative paths (source, target, relative path)
/home/part1/part2 /home/part1/part3 ../part3
/home/part1/part2 /home/part4/part5 ../../part4/part5
/home/part1/part2 /work/part6/part7 ../../../work/part6/part7
/home/part1 /work/part1/part2/part3/part4 ../../work/part1/part2/part3/part4
/home /work/part2/part3 ../work/part2/part3
/ /work/part2/part3/part4 work/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3/part4 part3/part4
/home/part1/part2 /home/part1/part2/part3 part3
/home/part1/part2 /home/part1/part2 .
/home/part1/part2 /home/part1 ..
/home/part1/part2 /home ../..
/home/part1/part2 / ../../..
/home/part1/part2 /work ../../../work
/home/part1/part2 /work/part1 ../../../work/part1
/home/part1/part2 /work/part1/part2 ../../../work/part1/part2
/home/part1/part2 /work/part1/part2/part3 ../../../work/part1/part2/part3
/home/part1/part2 /work/part1/part2/part3/part4 ../../../work/part1/part2/part3/part4
home/part1/part2 home/part1/part3 ../part3
home/part1/part2 home/part4/part5 ../../part4/part5
home/part1/part2 work/part6/part7 ../../../work/part6/part7
home/part1 work/part1/part2/part3/part4 ../../work/part1/part2/part3/part4
home work/part2/part3 ../work/part2/part3
. work/part2/part3 work/part2/part3
home/part1/part2 home/part1/part2/part3/part4 part3/part4
home/part1/part2 home/part1/part2/part3 part3
home/part1/part2 home/part1/part2 .
home/part1/part2 home/part1 ..
home/part1/part2 home ../..
home/part1/part2 . ../../..
home/part1/part2 work ../../../work
home/part1/part2 work/part1 ../../../work/part1
home/part1/part2 work/part1/part2 ../../../work/part1/part2
home/part1/part2 work/part1/part2/part3 ../../../work/part1/part2/part3
home/part1/part2 work/part1/part2/part3/part4 ../../../work/part1/part2/part3/part4