【问题标题】:Bash script absolute path with OS X带有 OS X 的 Bash 脚本绝对路径
【发布时间】:2011-04-04 01:49:21
【问题描述】:

我正在尝试获取 OS X 上当前正在运行的脚本的绝对路径。

我看到很多人回复readlink -f $0。然而,由于 OS X 的 readlink 与 BSD 相同,所以它不起作用(它适用于 GNU 的版本)。

是否有开箱即用的解决方案?

【问题讨论】:

标签: macos bash path


【解决方案1】:

有一个realpath() C 函数可以完成这项工作,但我在命令行上看不到任何可用的东西。这是一个快速而肮脏的替换:

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

如果路径以/ 开头,则会逐字打印路径。如果不是,它必须是相对路径,所以它会将$PWD 放在前面。 #./ 部分从$1 的前面剥离./

【讨论】:

  • 我也注意到了 C 函数,但找不到任何对应的二进制文件或任何东西。无论如何,你的功能正好符合我的需要。谢谢!
  • 请注意,这不会取消引用符号链接。
  • realpath ../something 返回$PWD/../something
  • 这对我有用,但我将其重命名为“realpath_osx()”,因为我可能需要将此 bash 脚本发送到 linux shell 并且不希望它与“真实”真实路径发生冲突! (我敢肯定有更优雅的方式,但我是 bash n00b。)
  • command -v realpath >/dev/null 2>&1 || realpath() { ... }
【解决方案2】:

出于以下几个原因,我发现答案有点欠缺:
特别是,它们不能解析多个级别的符号链接,而且它们非常“Bash-y”。

虽然最初的问题确实明确要求“Bash 脚本”,但它也提到了 Mac OS X 的类似 BSD、非 GNU readlink

所以这里尝试了一些合理的可移植性(我已经用 bash 作为 'sh' 和破折号对其进行了检查),解析任意数量的符号链接;它还应该与路径中的空格一起使用。

此答案之前已编辑,重新添加了 local bashism。这个答案的重点是一个可移植的 POSIX 解决方案。我已经对其进行了编辑,以通过将其更改为子shell函数而不是内联函数来解决变量范围。请不要编辑。

#!/bin/sh
realpath() (
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
)
realpath "$@"

希望对某人有用。

【讨论】:

  • 我只建议对函数内部定义的变量使用local,以免污染全局命名空间。例如。 local OURPWD=...。至少适用于 bash。
  • 另外,代码不应该对私有变量使用大写。大写变量保留给系统使用。
  • 感谢脚本。如果链接和真实文件具有不同的基本名称,最好在 while 中添加 BASENAME=$(basename "$LINK") 并在第二个 LINK 设置器和 REALPATH 设置器中使用它
  • 这并不像realpath 那样处理符号链接和.. 父引用。安装自制软件coreutils,尝试ln -s /var/log /tmp/linkexample 然后realpath /tmp/linkexample/../;这打印/private/var。但是您的函数会生成 /tmp/linkexample/..,因为 .. 不是符号链接。
  • @Martin 有趣。我会调查一下。
【解决方案3】:

正如您在上面看到的,我在大约 6 个月前对此进行了拍摄。我完全 忘了它,直到我发现自己再次需要类似的东西。我曾是 完全震惊看到它是多么简陋;我一直在教 我自己花了大约一年的时间进行非常密集的编码,但我经常觉得 也许在最糟糕的时候我什么都没学到。

我会删除上面的“解决方案”,但我真的很喜欢它作为记录 在过去的几个月里,我真的学到了多少。

但我离题了。昨晚我坐下来把这一切都解决了。中的解释 cmets应该足够了。如果您想跟踪副本,我将继续 继续工作,you can follow this gist. 这可能满足您的需求。

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

【讨论】:

  • 要点链接似乎已损坏。
  • 这个答案有效:stackoverflow.com/a/46772980/1223975 .... 你应该使用它。
  • 请注意,您的实现不会解析 .. 父引用之前的符号链接;例如/foo/link_to_other_directory/.. 被解析为 /foo 而不是符号链接 /foo/link_to_other_directory 指向的任何路径的父级。 readlink -frealpath 从根开始解析每个路径组件,并将前置链接目标更新为仍在处理的其余部分。我已经为这个问题添加了一个答案,重新实现了这个逻辑。
【解决方案4】:

这三个简单的步骤将解决这个问题和许多其他 OS X 问题:

  1. 安装Homebrew
  2. brew install coreutils
  3. grealpath .

(3) 可以改为 realpath,见 (2) 输出

【讨论】:

  • 这解决了问题,但需要安装您可能不想要或不需要的东西,或者可能不在目标系统上的东西,即 OS X
  • @JasonS GNU Coreutils 太好用了。它包括许多很棒的实用程序。事实上,Linux 内核没有它就毫无用处,这真是太好了,为什么有些人称它为 GNU/Linux。 Coreutils 很棒。很好的答案这应该是选定的答案。
  • @omouse 这个问题特别提到了“开箱即用的解决方案”(对于 OS X)。不管 coreutils 有多棒,它在 OS X 上都不是“开箱即用”的,所以答案不是一个好答案。这不是 coreutils 有多好,或者它是否比 OS X 上更好的问题。有“开箱即用”的解决方案 $( cd "$(dirname "$0")" ; pwd -P ) 对我来说很好。
  • OS X 的“特性”之一是目录和文件名不区分大小写。因此,如果您有一个名为XXX 的目录和一个名为cd xxx 的目录,那么pwd 将返回.../xxx。除了这个答案之外,当您真正想要的是XXX 时,上面的所有 解决方案都会返回xxx。谢谢!
  • 我不知道无休止地复制、粘贴和重新发明代码比现有的包管理器解决方案更好。如果您需要realpath,那么当您几乎肯定需要来自coreutils 的其他项目时会发生什么?也用 bash 重写这些函数? :P
【解决方案5】:

我一直在寻找在系统配置脚本中使用的解决方案,即在安装 Homebrew 之前运行。由于缺乏合适的解决方案,我只能将任务转移到跨平台语言,例如 Perl:

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

更多时候我们真正想要的是包含目录:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")

【讨论】:

  • 太棒了! Perl 非常适合小开销!您可能可以将第一个版本简化为FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')")。有什么反对的理由吗?
  • @FPereira 从未转义的用户提供的字符串生成程序代码绝不是一个好主意。 '' 不是防弹的。例如,如果$0 包含单引号,它就会中断。一个非常简单的示例:在/tmp/'/test.sh 中尝试您的版本,并通过其完整路径调用/tmp/'/test.sh
  • 或者更简单,/tmp/'.sh
【解决方案6】:

使用 Python 获取:

#!/usr/bin/env python
import os
import sys

print(os.path.realpath(sys.argv[1]))

【讨论】:

    【解决方案7】:

    Python 解决方案对命令行更友好的变体:

    python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' ./my/path
    

    【讨论】:

    • 以防万一有人疯狂到为单个命令启动 python 解释器......
    • 我已经够疯了,但使用了python -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
    • @Bachsau 您是否检查过有多少“单个命令”已经只是位于/usr/bin 中的普通 Linux 系统的 python 脚本?您可能会惊讶于您已经在使用它。
    • @PhilipCouling 我所说的“单个命令”并不是指我在命令行上键入的单个命令,而是由单个命令组成的脚本内容。
    • @Bachsau 我知道,但请仔细考虑这两者之间的确切区别(不是很多)。 Python 作为解释器足够高效,可用于小型实用程序。以上述方式使用它与调用 sed 或 awk... 或 sh -c ... 几乎没有什么不同,它们都是解释器。
    【解决方案8】:

    因为有一个realpath,正如其他人指出的那样:

    // realpath.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main (int argc, char* argv[])
    {
      if (argc > 1) {
        for (int argIter = 1; argIter < argc; ++argIter) {
          char *resolved_path_buffer = NULL;
          char *result = realpath(argv[argIter], resolved_path_buffer);
    
          puts(result);
    
          if (result != NULL) {
            free(result);
          }
        }
      }
    
      return 0;
    }
    

    生成文件:

    #Makefile
    OBJ = realpath.o
    
    %.o: %.c
          $(CC) -c -o $@ $< $(CFLAGS)
    
    realpath: $(OBJ)
          gcc -o $@ $^ $(CFLAGS)
    

    然后使用make 编译并放入软链接:
    ln -s $(pwd)/realpath /usr/local/bin/realpath

    【讨论】:

    • 我们可以做gcc realpath.c -o /usr/local/bin/realpath吗?
    • @AlexanderMills 您不应该以 root 身份运行编译器;然后如果你不这样做,你将没有权限写信给/usr/local/bin
    【解决方案9】:

    Mac OS X 的真实路径

    realpath() {
        path=`eval echo "$1"`
        folder=$(dirname "$path")
        echo $(cd "$folder"; pwd)/$(basename "$path"); 
    }
    

    相关路径示例:

    realpath "../scripts/test.sh"
    

    主文件夹示例

    realpath "~/Test/../Test/scripts/test.sh"
    

    【讨论】:

    • 不错的简单解决方案,我刚刚发现一个警告,当使用.. 调用它时,它不会产生正确的答案,所以我添加了一个检查给定路径是否是一个目录:if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
    • 对我不起作用,而 "$(dirname $(dirname $(realpath $0)))" 确实起作用,所以需要其他东西...
    • useless use of echo 也不应该被执行。
    • 这实际上并不能像 realpath 那样解析符号链接。它解析..父引用之前解析符号链接,而不是之后;尝试安装自制软件coreutils,使用ln -s /var/log /tmp/linkexample 创建链接,然后运行realpath /tmp/linkexample/../;这打印/private/var。但是您的函数会生成 /tmp/linkexample/..,因为 pwd 在 cd 之后仍然显示 /tmp/linkexample
    【解决方案10】:

    根据与评论者的交流,我同意这是非常困难的,并且没有简单的方法来实现与 Ubuntu 完全相同的真实路径。

    但以下版本,可以处理极端情况最佳答案不能满足我在 macbook 上的日常需求。将此代码放入您的 ~/.bashrc 并记住:

    • arg 只能是 1 个文件或目录,不能是通配符
    • 目录或文件名中没有空格
    • 至少文件或目录的父目录存在
    • 请随意使用。 ../东西,这些是安全的

        # 1. if is a dir, try cd and pwd
        # 2. if is a file, try cd its parent and concat dir+file
        realpath() {
         [ "$1" = "" ] && return 1
    
         dir=`dirname "$1"`
         file=`basename "$1"`
    
         last=`pwd`
    
         [ -d "$dir" ] && cd $dir || return 1
         if [ -d "$file" ];
         then
           # case 1
           cd $file && pwd || return 1
         else
           # case 2
           echo `pwd`/$file | sed 's/\/\//\//g'
         fi
    
         cd $last
        }
    

    【讨论】:

    • 你想避免useless use of echo。只需pwdecho $(pwd) 执行相同的操作,而无需生成第二个shell 副本。此外,不引用echo 的参数是一个错误(您将丢失任何前导或尾随空格、任何相邻的内部空格字符,并扩展通配符等)。进一步查看stackoverflow.com/questions/10067266/…
    • 另外,不存在路径的行为是错误的;但我想这就是“但记住”这句话可能想说的。虽然 Ubuntu 上的行为当然不是在您请求不存在目录的 realpath 时打印当前目录。
    • 为了一致性,可能更喜欢dir=$(dirname "$1"); file=$(basename "$1") 而不是过时的反引号语法。还要注意参数的正确引用。
    • 您更新的答案似乎无法修复许多错误,并添加了新错误。
    • 请给我一些具体的失败案例,因为我在ubuntu 18.04桌面上做的所有测试都可以,谢谢。
    【解决方案11】:

    在 macOS 上,我发现的唯一可靠处理符号链接的解决方案是使用 realpath。因为这需要brew install coreutils,所以我只是自动化了这一步。我的实现如下所示:

    #!/usr/bin/env bash
    
    set -e
    
    if ! which realpath >&/dev/null; then
      if ! which brew >&/dev/null; then
        msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
        echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
        exit 1
      fi
      echo "Installing coreutils/realpath"
      brew install coreutils >&/dev/null
    fi
    
    thisDir=$( dirname "`realpath "$0"`" )
    echo "This script is run from \"$thisDir\""
    


    如果他们没有安装brew,则会出现此错误,但您也可以直接安装它。我只是对自动化一些从网上卷曲任意 ruby​​ 代码的东西感到不舒服。

    请注意,这是 Oleg Mikheev 的 answer 的自动变体。


    一项重要的测试

    对这些解决方案的一个很好的测试是:

    1. 将代码放在某个脚本文件中
    2. 在另一个目录中,符号链接 (ln -s) 到该文件
    3. 从该符号链接运行脚本

    解决方案是否取消引用符号链接,并为您提供原始目录?如果是这样,它的工作原理。

    【讨论】:

      【解决方案12】:

      这似乎适用于 OSX,不需要任何二进制文件,并且被拉取 from here

      function normpath() {
        # Remove all /./ sequences.
        local path=${1//\/.\//\/}
      
        # Remove dir/.. sequences.
        while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
          path=${path/${BASH_REMATCH[0]}/}
        done
        echo $path
      }
      

      【讨论】:

        【解决方案13】:

        我喜欢这个:

        #!/usr/bin/env bash
        function realpath() {
            local _X="$PWD"
            local _LNK=$1
            cd "$(dirname "$_LNK")"
            if [ -h "$_LNK" ]; then
                _LNK="$(readlink "$_LNK")"
                cd "$(dirname "$_LNK")"
            fi
            echo "$PWD/$(basename "$_LNK")"
            cd "$_X"
        }
        

        【讨论】:

          【解决方案14】:
          abs_path () {    
             echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
          }
          

          dirname 将给出/path/to/file 的目录名称,即/path/to

          cd /path/to; pwd 确保路径是绝对的。

          basename 将只给出/path/to/file 中的文件名,即file

          【讨论】:

          • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
          【解决方案15】:

          我需要在 OS X 上替换 realpath,它可以在带有符号链接和父引用的路径上正确运行就像 readlink -f 一样。这包括在解析父引用之前解析路径中的符号链接;例如如果您已经安装了自制软件coreutils 瓶,则运行:

          $ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
          $ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
          /private/var/log
          

          请注意,readlink -f 已解析 /tmp/linkeddir 解析 .. 父目录引用之前。当然,Mac 上也没有readlink -f

          因此,作为 realpath 的 bash 实现的一部分,我在 Bash 3.2 中重新实现了 GNUlib canonicalize_filename_mode(path, CAN_ALL_BUT_LAST) function call does;这也是 GNU readlink -f 进行的函数调用:

          # shellcheck shell=bash
          set -euo pipefail
          
          _contains() {
              # return true if first argument is present in the other arguments
              local elem value
          
              value="$1"
              shift
          
              for elem in "$@"; do 
                  if [[ $elem == "$value" ]]; then
                      return 0
                  fi
              done
              return 1
          }
          
          _canonicalize_filename_mode() {
              # resolve any symlink targets, GNU readlink -f style
              # where every path component except the last should exist and is
              # resolved if it is a symlink. This is essentially a re-implementation
              # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
              # takes the path to canonicalize as first argument
          
              local path result component seen
              seen=()
              path="$1"
              result="/"
              if [[ $path != /* ]]; then  # add in current working dir if relative
                  result="$PWD"
              fi
              while [[ -n $path ]]; do
                  component="${path%%/*}"
                  case "$component" in
                      '') # empty because it started with /
                          path="${path:1}" ;;
                      .)  # ./ current directory, do nothing
                          path="${path:1}" ;;
                      ..) # ../ parent directory
                          if [[ $result != "/" ]]; then  # not at the root?
                              result="${result%/*}"      # then remove one element from the path
                          fi
                          path="${path:2}" ;;
                      *)
                          # add this component to the result, remove from path
                          if [[ $result != */ ]]; then
                              result="$result/"
                          fi
                          result="$result$component"
                          path="${path:${#component}}"
                          # element must exist, unless this is the final component
                          if [[ $path =~ [^/] && ! -e $result ]]; then
                              echo "$1: No such file or directory" >&2
                              return 1
                          fi
                          # if the result is a link, prefix it to the path, to continue resolving
                          if [[ -L $result ]]; then
                              if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                                  # we've seen this link before, abort
                                  echo "$1: Too many levels of symbolic links" >&2
                                  return 1
                              fi
                              seen+=("$result")
                              path="$(readlink "$result")$path"
                              if [[ $path = /* ]]; then
                                  # if the link is absolute, restart the result from /
                                  result="/"
                              elif [[ $result != "/" ]]; then
                                  # otherwise remove the basename of the link from the result
                                  result="${result%/*}"
                              fi
                          elif [[ $path =~ [^/] && ! -d $result ]]; then
                              # otherwise all but the last element must be a dir
                              echo "$1: Not a directory" >&2
                              return 1
                          fi
                          ;;
                  esac
              done
              echo "$result"
          }
          

          它包括循环符号链接检测,如果相同(中间)路径出现两次则退出。

          如果你只需要readlink -f,那么你可以使用上面的:

          readlink() {
              if [[ $1 != -f ]]; then  # poor-man's option parsing
                  # delegate to the standard readlink command
                  command readlink "$@"
                  return
              fi
          
              local path result seenerr
              shift
              seenerr=
              for path in "$@"; do
                  # by default readlink suppresses error messages
                  if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
                      seenerr=1
                      continue
                  fi
                  echo "$result"
              done
              if [[ $seenerr ]]; then
                  return 1;
              fi
          }
          

          对于realpath,我还需要--relative-to--relative-base 支持,它们在规范化后为您提供相对路径:

          _realpath() {
              # GNU realpath replacement for bash 3.2 (OS X)
              # accepts --relative-to= and --relative-base options
              # and produces canonical (relative or absolute) paths for each
              # argument on stdout, errors on stderr, and returns 0 on success
              # and 1 if at least 1 path triggered an error.
          
              local relative_to relative_base seenerr path
          
              relative_to=
              relative_base=
              seenerr=
          
              while [[ $# -gt 0 ]]; do
                  case $1 in
                      "--relative-to="*)
                          relative_to=$(_canonicalize_filename_mode "${1#*=}")
                          shift 1;;
                      "--relative-base="*)
                          relative_base=$(_canonicalize_filename_mode "${1#*=}")
                          shift 1;;
                      *)
                          break;;
                  esac
              done
          
              if [[
                  -n $relative_to
                  && -n $relative_base
                  && ${relative_to#${relative_base}/} == "$relative_to"
              ]]; then
                  # relative_to is not a subdir of relative_base -> ignore both
                  relative_to=
                  relative_base=
              elif [[ -z $relative_to && -n $relative_base ]]; then
                  # if relative_to has not been set but relative_base has, then
                  # set relative_to from relative_base, simplifies logic later on
                  relative_to="$relative_base"
              fi
          
              for path in "$@"; do
                  if ! real=$(_canonicalize_filename_mode "$path"); then
                      seenerr=1
                      continue
                  fi
          
                  # make path relative if so required
                  if [[
                      -n $relative_to
                      && ( # path must not be outside relative_base to be made relative
                          -z $relative_base || ${real#${relative_base}/} != "$real"
                      )
                  ]]; then
                      local common_part parentrefs
          
                      common_part="$relative_to"
                      parentrefs=
                      while [[ ${real#${common_part}/} == "$real" ]]; do
                          common_part="$(dirname "$common_part")"
                          parentrefs="..${parentrefs:+/$parentrefs}"
                      done
          
                      if [[ $common_part != "/" ]]; then
                          real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
                      fi
                  fi
          
                  echo "$real"
              done
              if [[ $seenerr ]]; then
                  return 1
              fi
          }
          
          if ! command -v realpath > /dev/null 2>&1; then
              # realpath is not available on OSX unless you install the `coreutils` brew
              realpath() { _realpath "$@"; }
          fi
          

          我在Code Review request for this code 中包含了单元测试。

          【讨论】:

            【解决方案16】:

            我检查了每个答案,但错过了 Jason S 2016 年 7 月 14 日 3:12 的最佳答案(恕我直言),离开了评论字段。

            所以就在这里,以防像我这样的人倾向于检查答案并且没有时间检查每一个 cmets:

            $( cd "$(dirname "$0")" ; pwd -P )
            

            帮助:

            NAME
                 pwd -- return working directory name
            
            SYNOPSIS
                 pwd [-L | -P]
            
            DESCRIPTION
                 The pwd utility writes the absolute pathname of the current working
                 directory to the standard output.
            
                 Some shells may provide a builtin pwd command which is similar or identi-
                 cal to this utility.  Consult the builtin(1) manual page.
            
                 The options are as follows:
            
                 -L      Display the logical current working directory.
            
                 -P      Display the physical current working directory (all symbolic
                         links resolved).
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-01-09
              • 2016-04-11
              • 2018-02-25
              • 2011-08-16
              • 2014-07-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多