如何在POSIX中获取脚本目录?

cit*_*ity 47 bash posix sh

我的bash脚本中有以下代码.现在我想在POSIX中使用它.那么如何转换呢?谢谢.

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 87

POSIX-shell(sh)对应的$BASH_SOURCE$0.请参阅底部的背景信息

警告:关键的区别在于,如果您的脚本被采购(加载到当前的 shell中.),下面 的代码段将无法正常工作.进一步解释如下

请注意,我已经改变DIRdir在下面的代码片段,因为它是最好不要使用全部大写的变量名,以避免与环境变量和特殊shell变量冲突.
CDPATH=前缀需要的地方> /dev/null在原来的命令:$CDPATH被设置为空字符串,以确保cd永不呼应东西.

在最简单的情况下,这将做(相当于OP的命令):

dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
Run Code Online (Sandbox Code Playgroud)

如果您还想结果目录路径解析为其最终目标,以防目录和/或其组件是符号链接,请添加-Ppwd命令:

dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
Run Code Online (Sandbox Code Playgroud)

警告:这是一样的发现脚本自身起源的真正目录:
比方说,你的脚本foo被链接到/usr/local/bin/foo$PATH,但其真正的路径/foodir/bin/foo.
上述仍将报告/usr/local/bin,因为符号链接的分辨率(-P)被应用到目录中,/usr/local/bin而不是在脚本本身.

要查找脚本自己的真实原始目录,您必须检查脚本的路径以查看它是否为符号链接,如果是,请遵循(链)符号链接到最终目标文件,然后从中提取目录路径该 目标文件的规范路径.

GNU readlink -f(更好:) readlink -e可以为您做到这一点,但readlink不是POSIX实用程序.
虽然包括OSX在内的BSD平台也有readlink实用程序,但在OSX上它不支持其-f功能.这就是说,以显示任务如何变得简单,如果 readlink -f是可用的:dir=$(dirname "$(readlink -f -- "$0")").

实际上,没有用于解析文件符号链接的POSIX实用程序.有办法解决这个问题,但它们很麻烦且不够完善:

下面的,符合POSIX标准的shell函数实现什么GNU的readlink -e确实是一个相当强大的解决方案唯一的失败有两种罕见的边缘情况:

  • 嵌入换行符的路径(非常罕见)
  • 包含文字字符串的文件名->(也很少见)

使用此函数named rreadlink,defined,以下内容确定脚本的真实目录路径:

dir=$(dirname -- "$(rreadlink "$0")")
Run Code Online (Sandbox Code Playgroud)

readlink 源代码 - 在脚本中调用之前放置:

rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that `command`
  # itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not even have
  # an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for that
  # to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)
Run Code Online (Sandbox Code Playgroud)

为了具有健壮性和可预测性,该函数用于rreadlink()确保仅调用shell内置函数或外部实用程序(忽略别名和函数形式的重载).
它已经:在最近版本的下列炮弹进行测试command,bash,dash,ksh.


如何处理调用:

tl;博士:

使用POSIX功能:

  • 无法调用中确定脚本的路径(除了zsh,但通常不起作用zsh).
  • 如果您的脚本是由shell 直接提供的(例如在shell配置文件/初始化文件中;可能通过一连串的源代码),通过与shell可执行文件名称/路径进行比较,您可以检测脚本是否仅来源(除非在哪里,正如所指出的那样,是真正的当前脚本的路径).相比之下(除了in ),脚本来自另一个本身被直接调用的脚本,包含脚本的路径.sh$0zsh$0zsh
  • 为了解决这些问题,$0,bash,和ksh具有非标准特征在于允许确定实际脚本路径即使在源场景和还检测脚本是否正在来源与否; 例如,in zsh,bash 始终包含正在运行的脚本的路径,无论它是否来源,并且$BASH_SOURCE可用于测试脚本是否来源.

为了说明为什么不能这样做,让我们分析Walter A答案中的命令:

    # NOT recommended - see discussion below.
    DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
Run Code Online (Sandbox Code Playgroud)
  • (两个旁边:
    • 使用[[ $0 != "$BASH_SOURCE" ]]两次是多余的 - 使用它就足够了-P.
    • pwd如果cd恰好设置了该命令,则缺少对潜在stdout输出的静默.)
  • $CDPATH
    • command -v -- "$0"旨在覆盖一个额外的场景:如果脚本正在采购交互式外壳,command -v -- "$0"通常包含单纯的文件名外壳可执行文件(的$0),在这种情况下sh会简单地返回dirname(因为这是.给定一个参数时,没有总是做路径零件). dirname然后通过command -v -- "$0"lookup($PATH)返回该shell的绝对路径.但请注意,某些平台(例如OSX)上的登录 shell的文件名前缀为/bin/shin -($0),在这种情况下-sh不能按预期工作(返回空字符串).
    • 相反,在脚本作为参数的情况下,可以直接调用shell可执行文件的command -v -- "$0"两个非源方案行为异常: command -v -- "$0"
      • 如果脚本本身不是可执行文件:sh可能会返回一个空字符串,这取决于具体的外壳作为command -v -- "$0"一个给定的系统上:sh,bash,并ksh返回一个空字符串; 只有zsh呼应dash
        POSIX规范.对于$0没有明确说是否command,何时应用到文件系统路径,应该只返回可执行文件 -这是什么command -v,bash以及ksh做-但你可以认为它是由真正目的暗示zsh; 奇怪的command是,这通常是最合规的POSIX公民,在这里偏离了标准.相比之下,dash这里是唯一的模型公民,因为它是唯一一个仅报告可执行文件按照规范要求以绝对(尽管未规范化)路径报告它们的公民.
      • 如果脚本可执行的,但不在ksh,并且调用使用其纯粹的文件名(例如$PATH),sh myScript则还将返回空字符串,除了command -v -- "$0".
    • 鉴于在脚本来源无法确定脚本的目录- 因为那时不包含该信息(除了,通常不起作用) - 这个问题没有很好的解决方案. dash$0zsh
      • 返回该行解释器可执行的在这种情况下的目录路径是有限的用途-它毕竟,没有脚本的目录-除非是后来用在这条道路测试,以确定是否脚本正在采购.
        • 更可靠的方法是直接测试sh:$0
      • 但是,如果脚本来自另一个脚本(本身直接调用),即使这样也行不通,因为[ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]那时只包含了脚本的路径.
    • 鉴于$0源场景中的有用性有限以及它打破了两个资源场景的事实,我的投票是不使用它,这让我们:
      • 所有 -sourced场景覆盖.
      • 调用中,您无法确定脚本的路径,并且在有限的情况下,您可以检测是否正在进行采购:
        • 直接由shell获取时(例如从shell配置文件/初始化文件中获取),如果shell可执行文件仅作为文件名调用(仅应用于文件名总是返回),或者shell可执行文件的目录路径,则command -v -- "$0"最终包含.无法可靠地区分当前目录中的非源调用.$dir.dirname.
        • 当源自另一个脚本(本身也不是源代码)时,.包含脚本的路径,并且源代码的脚本无法确定是否是这种情况.

背景资料:

POSIX定义的行为$0相对于外壳脚本 在这里.

本质上,$0应该反映指定的脚本文件的路径,这意味着:

  • 不要依赖$0包含绝对路径.
  • $0仅在以下情况下包含绝对路径:

    • 明确指定一个绝对路径; 例如:
      • $0 (假设脚本本身是可执行的)
      • ~/bin/myScript
    • 需要文件名来调用一个可执行文件,这要求它既可以执行又可以sh ~/bin/myScript; 在幕后,系统转换$PATH为绝对路径,然后执行它; 例如:
      • myScript
  • 在所有其他情况下,myScript # executes /home/jdoe/bin/myScript, for instance将反映指定的脚本路径:

    • $0使用脚本显式调用时,这可能只是文件名(例如sh)或相对路径(例如sh myScript)
    • 直接调用可执行脚本时,这可以是相对路径(例如,sh ./myScript- 请注意,仅仅文件名只会在其中./myScript找到脚本).

在实践中,$PATH,bash,dash,并且ksh都表现出这种行为.

相比之下,POSIX不强制的值zsh采购的脚本(使用特殊的内置实用工具$0("点")),所以你不能依赖它,并在实践中,行为跨越炮弹有所不同.

  • 因此,您不能盲目地使用.脚本来源并期望标准化行为.
    • 在实践中,$0,bash,和dash离开ksh 不变采购脚本时,那意思$0包含主叫方的 $0价值,或者更准确的说,$0最近在尚未来源本身的调用链调用者的价值; 因此,$0可以指向shell的可执行文件或指向源代码的另一个(直接调用的)脚本的路径.
    • 相比之下,$0作为唯一的反对者,实际上确实报告了当前脚本的路径zsh.相反,$0将不提供脚本是否来源的指示.
    • 简而言之:仅使用POSIX功能,您既不能可靠地判断手头的脚本是否来源,也不能说明手头的脚本是什么,也不知道$0当前脚本路径的关系是什么.
  • 如果确实需要处理这种情况,则必须确定手头的特定shell并访问其特定的非标准功能:
    • $0,bashksh所有提供自己获取运行脚本路径的方法,即使它来源.

为了完整起见:其他情境中价值zsh:

  • 在shell 函数中,POSIX要求$0保持不变 ; 因此,无论它在函数之外有什么价值,它都有内在的.
    • 在实践中,$0,bash,和dash你的行为的方式.
    • 再次,ksh是唯一的反对者并报告函数的名称.
  • 通过zsh启动选项接受命令字符串的shell中,它是设置的第一个操作数(非选项参数)-c; 例如:
    • $0
    • sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one',bash,dash,和ksh所有的行为是那样的.
  • 否则,在外壳执行脚本文件,zsh是第一个参数的,所述壳的所述值父进程传递 -典型地,这就是外壳的名称或路径(例如$0,或sh); 这包括:
    • 一个交互式的
      • 警告:某些平台上,值得注意的是OSX,始终创建登录创建交互式炮弹炮弹时,和预先准备/bin/sh的外壳名字将它之前-,以便发出信号,告知这是一个_login壳壳; 因此,默认情况下,$0报告$0不在-bashOSX上的交互式shell中.
    • stdin读取命令的shell
      • 这也适用于通过stdin将脚本文件传递给shell(例如bash)
    • sh < myScript,bash,dash,和ksh所有的行为是那样的.