【问题标题】:How to retrieve absolute path given relative如何检索给定相对路径的绝对路径
【发布时间】:2011-05-09 16:24:14
【问题描述】:

在给定相对路径的情况下,是否有检索绝对路径的命令?

例如,我希望 $line 包含 dir ./etc/ 中每个文件的绝对路径

find ./ -type f | while read line; do
   echo $line
done

【问题讨论】:

标签: bash shell path absolute


【解决方案1】:

试试realpath

~ $ sudo apt-get install realpath  # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc

为避免扩展符号链接,请使用realpath -s

答案来自“bash/fish command to print absolute path to a file”。

【讨论】:

  • realpath 在 Mac 上似乎不可用(OS X 10.11 “El Capitan”)。 :-(
  • realpath 在 CentOS 6 上似乎也不可用
  • 在 osx 上,brew install coreutils 将引入 realpath
  • 在我的 Ubuntu 18.04 上,realpath 已经存在。我不必单独安装它。
  • 令人惊讶的是,realpath 在 Git for Windows 上可用(至少对我而言)。
【解决方案2】:

如果您安装了 coreutils 软件包,您通常可以使用readlink -f relative_file_name 来检索绝对值(解决所有符号链接)

【讨论】:

  • 此行为与用户要求的略有不同,它还将遵循并解析路径中任何位置的递归符号链接。在某些情况下,您可能不希望这样。
  • @BradPeabody 如果您从 homebrew brew install coreutils 安装 coreutils,这确实可以在 Mac 中使用。但是可执行文件前面有一个 g:greadlink -f relative_file_name
  • 请注意,readlink(1) 的手册页描述的第一句话是:“注意 realpath(1) 是用于规范化功能的首选命令。”
  • 你可以使用 -e 代替 -f 来检查文件/目录是否存在
【解决方案3】:
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD一些解释

  1. 此脚本获取相对路径作为参数"$1"
  2. 然后我们得到该路径的 dirname 部分(您可以将 dir 或文件传递给此脚本):dirname "$1"
  3. 然后我们cd "$(dirname "$1") 进入这个相对目录并通过运行pwd shell 命令获取它的绝对路径
  4. 之后我们将 basename 附加到绝对路径:$(basename "$1")
  5. 作为最后一步,我们echo

【讨论】:

  • readlink 是 linux 的简单解决方案,但这个解决方案也适用于 OSX,所以 +1
  • 这个脚本不等同于 realpathreadlink -f 所做的。例如,它不适用于最后一个组件是符号链接的路径。
  • @josch:问题不在于符号链接解析。但是如果你想这样做,你可以为pwd 命令提供-P 选项:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
  • 我喜欢这个答案,但它只有在允许用户 cd 进入目录的情况下才有效。这可能并不总是可行的。
  • 我个人最喜欢的 :) 不幸的是,当我喂它时它失败了“。”和“..”作为相对路径。略微改进的版本:stackoverflow.com/a/51264222/508355
【解决方案4】:

使用:

find "$(pwd)"/ -type f

获取所有文件或

echo "$(pwd)/$line"

显示完整路径(如果相对路径很重要)

【讨论】:

    【解决方案5】:

    对于它的价值,我投票支持所选择的答案,但想分享一个解决方案。缺点是,它仅适用于 Linux - 在堆栈溢出之前,我花了大约 5 分钟试图找到 OSX 等价物。不过我确定它就在那里。

    在 Linux 上,您可以将 readlink -edirname 结合使用。

    $(dirname $(readlink -e ../../../../etc/passwd))
    

    产量

    /etc/
    

    然后你用dirname的妹妹basename来得到 文件名

    $(basename ../../../../../passwd)
    

    产量

    passwd
    

    把它们放在一起..

    F=../../../../../etc/passwd
    echo "$(dirname $(readlink -e $F))/$(basename $F)"
    

    产量

    /etc/passwd
    

    如果您以目录为目标,那么您是安全的,basename 不会返回任何内容 最终输出中会出现双斜线。

    【讨论】:

    • dirnamereadlinkbasename 的优秀作品。这帮助我获得了符号链接的绝对路径——而不是它的目标。
    • 当你想返回符号链接的路径时不起作用(我恰好需要这样做......)。
    • 你怎么能找到一个不存在的路径的绝对路径?
    • @synthesizerpatel 很容易,我想;如果我在/home/GKFX 中并输入touch newfile,那么在我按下回车键之前,您可以知道我的意思是“创建/home/GKFX/newfile”,这是一个不存在的文件的绝对路径还没有。
    【解决方案6】:

    我认为这是最便携的:

    abspath() {                                               
        cd "$(dirname "$1")"
        printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
        cd "$OLDPWD"
    }
    

    如果路径不存在,它将失败。

    【讨论】:

    • 没有必要再cd回来。见stackoverflow.com/a/21188136/1504556。恕我直言,您的答案是此页面上的最佳答案。对于那些感兴趣的人,该链接解释了为什么这个解决方案有效。
    • 这不是很便携,dirname 是一个 GNU 核心实用程序,我相信并非所有 unixen 都通用。
    • @einpoklum dirname 是 POSIX 标准实用程序,请参见此处:pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
    • 天哪,谢谢。我已经尝试修复使用 ${1##*/} 的版本一天了,现在我用 basename "$1" 替换了那个垃圾,它似乎终于可以正确处理以 / 结尾的路径了。
    • 注意,对于以 ../.. 结尾的路径,这并不正确
    【解决方案7】:

    realpath 可能是最好的

    但是……

    最初的问题一开始就很困惑,举个例子很糟糕 与所述问题相关。

    所选答案实际上回答了给出的示例,而不是完全 标题中的问题。第一个命令就是那个答案(是 真的吗 ?我怀疑),并且没有'/'也可以。我看不到 第二个命令在做什么。

    几个问题混在一起:

    • 将相对路径名更改为绝对路径名,无论它是什么 表示,可能什么都没有。 (通常,如果您发出诸如 touch foo/bar 之类的命令,则 路径名foo/bar 必须为您存在,并且可能用于 计算,在实际创建文件之前。)

    • 可能有多个绝对路径名表示同一个文件 (或潜在文件),特别是因为符号链接(symlinks) 在路径上,但可能出于其他原因(设备可能是 以只读方式安装两次)。一个人可能想也可能不想解决 明确此类符号链接。

    • 到达非符号链接的符号链接链的末尾 文件或名称。这可能会或可能不会产生绝对路径名, 取决于它是如何完成的。一个人可能想,也可能不想 将其解析为绝对路径名。

    不带选项的命令readlink foo 仅在其 参数foo 是一个符号链接,答案就是那个值 符号链接。没有其他链接。答案可能是相对路径: 符号链接参数的值是什么。

    但是,readlink 有适用于所有人的选项(-f -e 或 -m) 文件,并给出一个绝对路径名(没有符号链接的那个) 参数实际表示的文件。

    这适用于任何不是符号链接的东西,尽管可能 希望使用绝对路径名而不解析中间 路径上的符号链接。这是通过命令realpath -s foo

    完成的

    在符号链接参数的情况下,readlink 及其选项将 再次解析参数绝对路径上的所有符号链接,但是 这还将包括可能遇到的所有符号链接 跟随参数值。如果你想要一个,你可能不想要那个 参数符号链接本身的绝对路径,而不是任何东西 它可能链接到。同样,如果foo 是符号链接,realpath -s foo 将 在不解析符号链接的情况下获得绝对路径,包括 作为参数给出。

    如果没有 -s 选项,realpath 的作用与 readlink,除了简单读取一个链接的值,还有几个 其他事情。我只是不清楚为什么readlink 有它的 选项,显然与 realpath.

    探索网络并没有多说,除了可能有 跨系统的一些变化。

    结论:realpath是最好用的命令,用的最多 灵活性,至少对于此处要求的用途而言。

    【讨论】:

    • readlinksays 的手册页:“注意 realpath(1) 是用于规范化功能的首选命令。”
    • @jarno 很有趣。我的手册页确实这么说,但日期是 2021 年 4 月。知道何时包含该行吗?
    • commit 制作于 2017 年。
    • @jarno 谢谢。我应该学会搜索那种信息。好吧,很高兴有一个在 4 年后得到官方支持的答案:-)。太糟糕了,没有徽章。
    【解决方案8】:

    我最喜欢的解决方案是@EugenKonkov 的the one,因为它并不意味着存在其他实用程序(coreutils 包)。

    但相对路径“。”失败了和“..”,所以这里有一个稍微改进的版本来处理这些特殊情况。

    如果用户没有cd进入相对路径的父目录的权限,它仍然会失败。

    #! /bin/sh
    
    # Takes a path argument and returns it as an absolute path. 
    # No-op if the path is already absolute.
    function to-abs-path {
        local target="$1"
    
        if [ "$target" == "." ]; then
            echo "$(pwd)"
        elif [ "$target" == ".." ]; then
            echo "$(dirname "$(pwd)")"
        else
            echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
        fi
    }
    

    【讨论】:

      【解决方案9】:

      Eugen 的回答不太适合我,但确实如此:

          absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"
      

      附注,您当前的工作目录不受影响。

      【讨论】:

        【解决方案10】:

        对@ernest-a 相当不错的版本的改进:

        absolute_path() {
            cd "$(dirname "$1")"
            case $(basename $1) in
                ..) echo "$(dirname $(pwd))";;
                .)  echo "$(pwd)";;
                *)  echo "$(pwd)/$(basename $1)";;
            esac
        }
        

        这可以正确处理路径的最后一个元素是.. 的情况,在这种情况下@ernest-a 的答案中的"$(pwd)/$(basename "$1")" 将作为accurate_sub_path/spurious_subdirectory/.. 出现。

        【讨论】:

          【解决方案11】:

          恕我直言,最好的解决方案是在此处发布的解决方案:https://stackoverflow.com/a/3373298/9724628

          它确实需要 python 才能工作,但它似乎涵盖了所有或大部分边缘情况,并且是非常便携的解决方案。

          1. 解析符号链接:
          python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
          
          1. 或者没有它:
          python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file
          

          【讨论】:

            【解决方案12】:

            find 的情况下,最简单的方法可能是只给出搜索的绝对路径,例如:

            find /etc
            find `pwd`/subdir_of_current_dir/ -type f
            

            【讨论】:

              【解决方案13】:

              如果您在不存在 realpathreadlink 无法打印绝对路径的 Mac OS X 上使用 bash,则您可以选择编写自己的版本打印它。 这是我的实现:

              (纯 bash)

              abspath(){
                local thePath
                if [[ ! "$1" =~ ^/ ]];then
                  thePath="$PWD/$1"
                else
                  thePath="$1"
                fi
                echo "$thePath"|(
                IFS=/
                read -a parr
                declare -a outp
                for i in "${parr[@]}";do
                  case "$i" in
                  ''|.) continue ;;
                  ..)
                    len=${#outp[@]}
                    if ((len==0));then
                      continue
                    else
                      unset outp[$((len-1))] 
                    fi
                    ;;
                  *)
                    len=${#outp[@]}
                    outp[$len]="$i"
                    ;;
                  esac
                done
                echo /"${outp[*]}"
              )
              }
              

              (使用 gawk)

              abspath_gawk() {
                  if [[ -n "$1" ]];then
                      echo $1|gawk '{
                          if(substr($0,1,1) != "/"){
                              path = ENVIRON["PWD"]"/"$0
                          } else path = $0
                          split(path, a, "/")
                          n = asorti(a, b,"@ind_num_asc")
                          for(i in a){
                              if(a[i]=="" || a[i]=="."){
                                  delete a[i]
                              }
                          }
                          n = asorti(a, b, "@ind_num_asc")
                          m = 0
                          while(m!=n){
                              m = n
                              for(i=1;i<=n;i++){
                                  if(a[b[i]]==".."){
                                      if(b[i-1] in a){
                                          delete a[b[i-1]]
                                          delete a[b[i]]
                                          n = asorti(a, b, "@ind_num_asc")
                                          break
                                      } else exit 1
                                  }
                              }
                          }
                          n = asorti(a, b, "@ind_num_asc")
                          if(n==0){
                              printf "/"
                          } else {
                              for(i=1;i<=n;i++){
                                  printf "/"a[b[i]]
                              }
                          }
                      }'
                  fi
              }
              

              (纯 bsd awk)

              #!/usr/bin/env awk -f
              function abspath(path,    i,j,n,a,b,back,out){
                if(substr(path,1,1) != "/"){
                  path = ENVIRON["PWD"]"/"path
                }
                split(path, a, "/")
                n = length(a)
                for(i=1;i<=n;i++){
                  if(a[i]==""||a[i]=="."){
                    continue
                  }
                  a[++j]=a[i]
                }
                for(i=j+1;i<=n;i++){
                  delete a[i]
                }
                j=0
                for(i=length(a);i>=1;i--){
                  if(back==0){
                    if(a[i]==".."){
                      back++
                      continue
                    } else {
                      b[++j]=a[i]
                    }
                  } else {
                    if(a[i]==".."){
                      back++
                      continue
                    } else {
                      back--
                      continue
                    }
                  }
                }
                if(length(b)==0){
                  return "/"
                } else {
                  for(i=length(b);i>=1;i--){
                    out=out"/"b[i]
                  }
                  return out
                }
              }
              
              BEGIN{
                if(ARGC>1){
                  for(k=1;k<ARGC;k++){
                    print abspath(ARGV[k])
                  }
                  exit
                }
              }
              {
                print abspath($0)
              }
              

              示例:

              $ abspath I/am/.//..//the/./god/../of///.././war
              /Users/leon/I/the/war
              

              【讨论】:

                【解决方案14】:

                类似于@ernest-a 的回答,但在不影响$OLDPWD 或定义新函数的情况下,您可以触发子shell (cd &lt;path&gt;; pwd)

                $ pwd
                /etc/apache2
                $ cd ../cups 
                $ cd -
                /etc/apache2
                $ (cd ~/..; pwd)
                /Users
                $ cd -
                /etc/cups
                

                【讨论】:

                  【解决方案15】:

                  他们所说的,除了find $PWD 或(在 bash 中)find ~+ 更方便一点。

                  【讨论】:

                    【解决方案16】:

                    如果相对路径是目录路径,那么试试我的,应该是最好的:

                    absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)
                    
                    echo $absPath
                    

                    【讨论】:

                    【解决方案17】:
                    echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'
                    

                    【讨论】:

                    • 感谢您提供此代码 sn-p,它可能会提供一些有限的短期帮助。一个正确的解释would greatly improve 它的长期价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。
                    【解决方案18】:

                    您可以对任何相对路径 $line 使用 bash 字符串替换:

                    line=$(echo ${line/#..\//`cd ..; pwd`\/})
                    line=$(echo ${line/#.\//`pwd`\/})
                    echo $line
                    

                    字符串前面的基本替换遵循公式
                    ${string/#substring/replacement}
                    这里讨论得很好:https://www.tldp.org/LDP/abs/html/string-manipulation.html

                    当我们希望 / 成为我们查找/替换的字符串的一部分时,\ 字符会否定它。

                    【讨论】:

                    • 请注意,我认为您可以只使用dirnamebasename。类似dir=`dirname $line`; file=`basename $line`; line=`cd $dir; pwd`/$file
                    【解决方案19】:

                    我无法找到一个可以在 Mac OS Catalina、Ubuntu 16 和 Centos 7 之间完美移植的解决方案,因此我决定使用 python inline 来完成它,它对我的​​ bash 脚本很有效。

                    to_abs_path() {
                      python -c "import os; print os.path.abspath('$1')"
                    }
                    
                    to_abs_path "/some_path/../secrets"
                    

                    【讨论】:

                      【解决方案20】:

                      这是一个相当短的函数,可用于仅使用 POSIX shell 和 readlink 语义来完全绝对化和规范化任何给定的输入路径:

                      canonicalize_path() {
                          (
                              FILEPATH="$1"
                              for _ in 1 2 3 4 5 6 7 8;  # Maximum symlink recursion depth
                              do
                                  cd -L "`case "${FILEPATH}" in */*) echo "${FILEPATH%/*}";; *) echo ".";; esac`/"  # cd $(dirname)
                                  if ! FILEPATH="$(readlink "${FILEPATH##*/}" || ( echo "${FILEPATH##*/}" && false ) )";
                                  then
                                      break
                                  fi
                              done
                              cd -P "." || return $?
                              echo "$(pwd)/${FILEPATH}"
                          )
                      }
                      

                      如果引用的文件不存在,则只会解析指向最终文件名的目录路径。如果指向文件路径的任何目录不存在,则将返回 cd 错误。这恰好是它试图模仿的 GNU/Linux readlink -f 命令的确切语义。

                      在 bash/zsh 中,您还可以将数字列表压缩为 {1..8} 或类似名称。选择 8 的数量是因为这是多年来 Linux 中的最大限制,然后在 4.2 版中将其更改为整个路径的 40 分辨率的总限制。如果达到分辨率限制,代码不会失败,而是返回最后考虑的路径——但是,可以添加显式 [ -L "${FILEPATH}" ] 检查来检测这种情况。

                      此代码还可以轻松地重复使用,以确保当前工作目录与文件系统中执行脚本的位置匹配(shell 脚本的常见要求),只需删除函数和子 shell 包装器:

                      FILEPATH="$0"
                      for _ in 1 2 3 4 5 6 7 8;  # Maximum symlink recursion depth
                      do
                          cd -L "`case "${FILEPATH}" in */*) echo "${FILEPATH%/*}";; *) echo ".";; esac`/"  # cd $(dirname)
                          if ! FILEPATH="$(readlink "${FILEPATH##*/}" || ( echo "${FILEPATH##*/}" && false ) )";
                          then
                              break
                          fi
                      done
                      cd -P "."
                      FILEPATH="$(pwd)/${FILEPATH}"
                      

                      【讨论】:

                        【解决方案21】:

                        我发现Eugen Konkov's 答案是最好的,因为它不需要安装任何程序。但是,对于不存在的目录,它会失败。

                        我写了一个适用于不存在的目录的函数:

                        function getRealPath()
                        {
                            local -i traversals=0
                            currentDir="$1"
                            basename=''
                            while :; do
                                [[ "$currentDir" == '.' ]] && { echo "$1"; return 1; }
                                [[ $traversals -eq 0 ]] && pwd=$(cd "$currentDir" 2>&1 && pwd) && { echo "$pwd/$basename"; return 0; }
                                currentBasename="$(basename "$currentDir")"
                                currentDir="$(dirname "$currentDir")"
                                [[ "$currentBasename" == '..' ]] && (( ++traversals )) || { [[ traversals -gt 0 ]] && (( traversals-- )) || basename="$currentBasename/$basename"; }
                            done
                        }
                        

                        通过dirname向上遍历直到cd成功,然后返回当前目录加上dirname删除的所有内容,解决了目录不存在的问题。

                        【讨论】:

                          【解决方案22】:

                          如果您想将包含相对路径的变量转换为绝对路径,这可行:

                             dir=`cd "$dir"`
                          

                          "cd" 回显而不改变工作目录,因为这里是在一个子 shell 中执行的。

                          【讨论】:

                          • 在 bash-4.3-p46 上,这不起作用:当我运行 dir=`cd ".."` &amp;&amp; echo $dir 时,shell 会打印一个空行
                          【解决方案23】:

                          这是所有其他解决方案的链式解决方案,例如,当realpath 失败时,无论是因为它没有安装还是因为它退出并显示错误代码,然后,尝试下一个解决方案,直到它找到正确的路径。

                          #!/bin/bash
                          
                          function getabsolutepath() {
                              local target;
                              local changedir;
                              local basedir;
                              local firstattempt;
                          
                              target="${1}";
                              if [ "$target" == "." ];
                              then
                                  printf "%s" "$(pwd)";
                          
                              elif [ "$target" == ".." ];
                              then
                                  printf "%s" "$(dirname "$(pwd)")";
                          
                              else
                                  changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
                                  firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
                                  firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;
                          
                                  # If everything fails... TRHOW PYTHON ON IT!!!
                                  local fullpath;
                                  local pythoninterpreter;
                                  local pythonexecutables;
                                  local pythonlocations;
                          
                                  pythoninterpreter="python";
                                  declare -a pythonlocations=("/usr/bin" "/bin");
                                  declare -a pythonexecutables=("python" "python2" "python3");
                          
                                  for path in "${pythonlocations[@]}";
                                  do
                                      for executable in "${pythonexecutables[@]}";
                                      do
                                          fullpath="${path}/${executable}";
                          
                                          if [[ -f "${fullpath}" ]];
                                          then
                                              # printf "Found ${fullpath}\\n";
                                              pythoninterpreter="${fullpath}";
                                              break;
                                          fi;
                                      done;
                          
                                      if [[ "${pythoninterpreter}" != "python" ]];
                                      then
                                          # printf "Breaking... ${pythoninterpreter}\\n"
                                          break;
                                      fi;
                                  done;
                          
                                  firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
                                  # printf "Error: Could not determine the absolute path!\\n";
                                  return 1;
                              fi
                          }
                          
                          printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"
                          

                          【讨论】:

                            【解决方案24】:

                            基于@EugenKonkovthis answer@HashChangethis answer,我的回答结合了前者的简洁性和后者的... 的处理。我相信下面的所有选项都依赖于基本的Shell Command Language POSIX standards

                            使用dirnamebasename,一个选项是:

                            absPathDirname()
                            {
                                [ -d "${1}" ] && set -- "${1}" || set -- "`dirname "${1}"`" "/`basename "${1}"`"
                                echo "`cd "${1}"; pwd`${2}";
                            }
                            

                            不使用dirnamebasename,另一个简短的选项是:

                            absPathMinusD()
                            {
                                [ -d "${1}" ] && set -- "${1}" || set -- "${1%${1##*/}}" "/${1##*/}"
                                echo "`cd "${1:-.}"; pwd`${2}";
                            }
                            

                            我会推荐上面两个选项之一,其余的只是为了好玩......

                            Grep 版本:

                            absPathGrep()
                            {
                                echo "`[ "${1##/*}" ] && echo "$1" | grep -Eo '^(.*/)?\.\.($|/)' | { read d && cd "$d"; echo "${PWD}/${1#$d}"; } || echo "$1"`"
                            }
                            

                            作为“使用 shell 的有限 RegEx 可以做什么”的一个有趣示例:

                            absPathShellReplace()
                            {
                                E="${1##*/}"; D="${E#$E${E#.}}"; DD="${D#$D${D#..}}"
                                DIR="${1%$E}${E#$DD}"; FILE="${1#$DIR}"; SEP=${FILE:+/}
                                echo "`cd "${DIR:-.}"; pwd`${SEP#$DIR}$FILE"
                            }
                            

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2014-03-09
                              • 2011-10-16
                              • 1970-01-01
                              • 2021-08-15
                              • 2011-11-25
                              • 1970-01-01
                              • 2012-01-11
                              • 2010-09-15
                              相关资源
                              最近更新 更多