【问题标题】:Convert absolute to relative symbolic link将绝对符号链接转换为相对符号链接
【发布时间】:2019-04-04 10:37:49
【问题描述】:

我同步目录“Promotion”,其中包含具有不同目录结构的两台机器之间的绝对符号链接。因此绝对符号链接在两台机器上都不起作用。为了使它们起作用,我想将它们转换为相对链接。 目录结构是

Machine 1: /home/user/Privat/Uni Kram/Promotion/
Machine 2: /homes/user/Promotion/

这里有两个示例符号链接:

 4821      1 lrwxrwxrwx   1 manu  users         105 Nov 17  2014 ./Planung\ nach\ Priorit\303\244ten.ods -> /home/manu/Dokumente/Privat/Uni\ Kram/Promotion/Pl\303\244ne\ und\ Ideen/Pl\303\244ne/Planung\ nach\ Priorit\303\244ten.ods  
37675      1 lrwxrwxrwx   1 manu  users         102 Aug  3  2015 ./Kurs/Lab\ Course\ Somewhere -> /home/manu/Dokumente/Privat/Uni\ Kram/Promotion/Workshops\ &\ Fortbildungen/Kurs\ Lab\ Course\ Somewhere

我的非工作尝试是(基于示例this):

find * -type l -print | while read l; do 
ln -srf $(cut -c 24- < $(readlink $l)) $l;
done

【问题讨论】:

标签: bash sh symlink


【解决方案1】:

在包含正确符号链接文件的机器上,运行以下命令:

find . -type l -exec ln -sfr {} . \;

【讨论】:

    【解决方案2】:

    这是使用 python3 执行此操作的解决方案。

    from pathlib import Path
    
    d = Path("/home/user/Privat/Uni Kram/Promotion/")
    all_symlinks = [p for p in d.rglob("*") if p.is_symlink()]
    
    def make_symlink_relative(p):
        assert p.is_symlink()
        relative_target = p.resolve(strict=True).relative_to(p.absolute().parent)
        p.unlink() 
        # this while-loop protects against race conditions
        while True:
            try:
                p.symlink_to(relative_target)
                break
            except FileExistsError:
                pass
    
    for symlink in all_symlinks:
        make_symlink_relative(symlink)
    
    

    【讨论】:

      【解决方案3】:

      以下可能有效。

      注意

      • 链接已备份,如果不希望将mv .. 更改为rm
      • 查找参数必须是绝对的,否则函数将拒绝链接

      例子

      find /absolute/path/to/root -type l -exec bash -c $'
          abs_to_relative() {
              l=$1
              t=$(readlink "$1")
              [[ $l = /* ]] && [[ $t = /* ]] && [[ $l != "$t" ]] || return
              set -f
              IFS=/
              link=($l)
              target=($t)
              IFS=$\' \\t\\n\'
              set +f
              i=0 f=\'\' res=\'\'
              while [[ ${link[i]} = "${target[i]}" ]]; do ((i+=1)); done
              link=("${link[@]:i}")
              target=("${target[@]:i}")
              for ((i=${#link[@]};i>1;i-=1)); do res+=../; done
              res=${res:-./}
              for f in "${target[@]}"; do res+=$f/; done
              res=${res%/}
              # rm "$1"
              mv "$1" "$1.bak"
              ln -s "$res" "$1"
          }
          for link; do abs_to_relative "$link"; done
      ' links {} +
      

      已完成的测试

      mkdir -p /tmp/testlink/home{,s}/user/abc
      touch /tmp/testlink/home/user/{file0,abc/file1}.txt
      ln -s /tmp/testlink/home/user/abc/file1.txt /tmp/testlink/home/user/link1.txt
      ln -s /tmp/testlink/home/user/file0.txt /tmp/testlink/home/user/abc/link0.txt
      ln -s /tmp/testlink/home/user/    /tmp/testlink/home/user/abc/linkdir
      # ... command
      rm -rf /tmp/testlink
      

      【讨论】:

      • 感谢您的脚本。我没有意识到我的问题需要这么多代码。我对您的解决方案仍有一些问题。它创建指向“../../../home/manu/Studies/somefile”的链接。 “../../../”从何而来?我错过了什么吗?
      • 我认为“../”的数量与引用路径中的空格数相对应。因此,带有 3 个空格的路径在符号链接中引用的路径前面有 3 个“../”
      • 没有确切的树很难说,你能给出find /path -type l -ls的输出吗?
      • 我在文件夹结构中有 130 个符号链接。以下是原始符号链接的两个示例: 124124 1 lrwxrwxrwx 1 manu users 121 Okt 31 13:38 Promotion/CLIB/Patent-\ &\ Innovationsmanagement\ Kurs -> /home/manu/Dokumente/Privat/Uni\ Kram/Promotion/ Workshops\ &\ Fortbildungen/Kurs\ Patent-\ &\ Innovationsmanagement\ Kurs 123572 1 lrwxrwxrwx 1 manu users 113 Okt 31 13:07 Promotion/Planung\ nach\ Priorit\303\244ten.ods -> /home/manu/Dokumente/ Privat/Uni\ Kram/Promotion/Pl\303\244ne\ und\ Ideen/Pl\303\244ne/Planung\ nach\ Priorit\303\244ten.ods
      • 最好用这个来更新问题,从这个评论看来你没有使用绝对路径(以 / 开头)作为 find 的参数,似乎 Promotion 不是子目录的/home 所以没有比使用../.. 直到/ 更好的解决方案
      【解决方案4】:

      感谢大家的帮助。经过一番尝试,我想出了一个基于您的 cmets 和代码的解决方案。 这是解决我的问题的方法:

      #!/bin/bash
      # changes all symbolic links to relative ones recursive from the current directory
      find * -type l -print | while read l; do 
      cp -a "$l" "$l".bak
      linkname="$l"
      linktarget=$(readlink "$l")
      echo "orig linktarget"
      echo $linktarget
      temp_var="${linktarget#/home/user/Privat/Uni Kram/Promotion/}"
      echo "changed linktarget"
      echo $temp_var;
      ln -sfr "$temp_var" "$l"
      echo "new linktarget in symlink"
      readlink "$l";
      done
      

      【讨论】:

        【解决方案5】:

        这是一对双向转换的函数。我现在在我的~/bin 中有这些可以更普遍地使用。链接不必位于当前目录中。只需用作:

        lnsrelative <link> <link> <link> ...
        lnsabsolute <link> <link> <link> ...
        
        #!/bin/bash
        # changes absolute symbolic links to relative
        # will work for links & filenames with spaces
        
        for l in "$@"; do
            [[ ! -L "$l" ]] && echo "Not a link: $l" && exit 1
        done
            
        for l in "$@"; do
            # Use -b here to get a backup.  Unnecessary because reversible.
            ln -sfr "$(readlink "$l")" "$l"
        done
        

        #!/bin/bash
        # changes relative symbolic links to absolute
        # will work for links & filenames with spaces
        
        for l in "$@"; do
            [[ ! -L "$l" ]] && echo "Not a link: $l" && exit 1
        done
            
        for l in "$@"; do
            # Use -b here to get a backup.  Unnecessary because reversible.
            ln -sf "$(realpath "$(readlink "$l")")" "$l"
        done
        

        为了完整起见,这会将符号链接更改为硬链接,用作:

        lnstohard <link> <link> <link>
        
        #!/bin/bash
        # changes symbolic links to hard links
        # This will work for filenames with spaces, 
        # but only for regular files in the same filesystem as the link.
        # Thorough discussion of correct way to confirm devices are the same:
        # https://unix.stackexchange.com/questions/120810/check-if-2-directories-are-hosted-on-the-same-partition-on-linux
        
        for l in "$@"; do
            [[ ! -L "$l" ]] && echo "Not a symbolic link: $l" && exit 1
            
            rl="$(readlink "$l")"
            rld="$(dirname "$l")"
            [[ ! -e "$rl" || -d "$rl" || "$(df --output=target "$rld")" != "$(df --output=target "$rl")" ]] && \
                echo "Target \"$rl\" must exist on same filesystem as link \"$l\", and may not be a directory" && \
                exit 1
        done
        
        for l in "$@"; do
            # Using -b here to get a backup, because it's not easy to revers a soft->hard link conversion
            ln -fb "$(readlink "$l")" "$l"
        done
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-10-07
          • 2011-04-19
          • 1970-01-01
          • 1970-01-01
          • 2015-01-13
          • 2015-06-10
          • 1970-01-01
          • 2013-08-04
          相关资源
          最近更新 更多