本文介绍了如何让POSIX sh脚本目录?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的bash脚本如下code。现在,我想在POSIX sh的使用它。因此,如何将它转换?谢谢。

  DIR =$(光盘$(目录名称$ {BASH_SOURCE [0]})>的/ dev / null的&放大器;&安培; PWD)


$ BASH_SOURCE 解决方案

在POSIX的shell( SH )对应C $ C>是 $ 1,0

买者:关键的区别是,如果你的脚本被的来源的(装入的电流的外壳采用下面的片段会的的正常工作。

在简单的情况下,这会做(业务方案的命令在相当于

  DIR = $(CDPATH = CD  - $(目录名称 - $ 0)&放大器;&安培; PWD)

如果你也想为解决的产生的目录路径,其最终目标的情况下,目录和/或其组成部分的符号链接添加 -P PWD 命令:

  DIR = $(CDPATH = CD  - $(目录名称 - $ 0)&放大器;&安培; PWD -P)

买者:这是不会一样找到脚本本身的起源的真实目录:结果
比方说,你的脚本被链接到的/ usr / local / bin目录/富 $ PATH ,但其真正的路径是 / foodir /斌/富。结果
以上仍然报告的/ usr / local / bin目录,因为符号链接分辨率( -P )被应用到目录的/ usr / local / bin目录,而不是脚本本身。

要找到脚本自身起源的真正目录,你必须检查的脚本的路径,看看它是否是一个符号链接,如果是这样,按照(符号链接的最终目标文件)链,然后从的目标的文件的规范路径解压的目录路径。

GNU的的readlink -f (更好:的readlink -e ),可以为你做的,但的readlink 不是POSIX实用程序。结果

在事实上,有没有的POSIX解决程序的文件的符号链接即可。
有办法解决的,但他们麻烦,不完全健壮的:

下, POSIX兼容的shell函数的实施什么GNU的的readlink -e 确实,是一个合理可靠的解决方案的只有失败两种罕见的边缘情况


  • 与路径内嵌的换行的(很少见)

  • 包含文字字符串文件名 - > (也少见)

通过这个功能,命名为 rreadlink 定义,以下决定起源的脚本的真实目录路径

  DIR = $(目录名称 - $(rreadlink$ 0))

rreadlink() 源$ C ​​$ C - 地方的的调用它在脚本:

rreadlink()(#执行功能的一个子shell * *本地化变量和cd`的`效果。  TARGET = $ 1 = FNAME = TARGETDIR = CDPATH  #尽量让执行环境为predictable尽可能:
  #所有命令下面通过`command`调用的,所以我们必须确保`command`
  #本身不会重新定义为别名或shell函数。
  #(请注意,命令跨越炮弹太不协调,所以我们不使用它。)
  #`command`是bash中,破折号和ksh,zsh的一个*内置*,有些平台甚至没有
  #它的外部工具的版本(例如,Ubuntu的)。
  #`command`绕过别名和shell函数,也发现建宏
  #在bash,破折号和ksh。在zsh中,选择POSIX_BUILTINS必须打开该
  # 即将发生。
  {\\ unalias命令; \\取消设置-f命令; }>的/ dev / null的2 - ;&放大器; 1
  [-n$ ZSH_VERSION]&放大器;&安培;选项​​[POSIX_BUILTINS] =#上的zsh做*找到内建*用`command`了。  而:;做#,直至最终目标是找到解决潜在的符号链接。
      [-L$目标] || [-e$目标] || {printf的命令,'%s的\\ n'ERROR:'$目标'不存在。 >和2;返回1; }
      命令cd$(命令目录名称 - $目标)#切换到目标目录;必要的目标路径的正确的分辨率。
      FNAME = $(命令基名 - $目标)#提取文件名。
      [$ FNAME='/']放大器;&安培; FNAME =''#!奇怪的是,`基本名/`返回'/'
      如果[-L$ FNAME];然后
        #提取[下一页]目标路径,可以被定义
        #*相对*的符号链接自己的目录。
        #注:我们解析`LS -l`输出找到链接目标
        #这是唯一符合POSIX标准,尽管有些脆弱,方式。
        目标= $(ls -l命令$ FNAME)
        目标= $ {#目标* - > }
        继续解析#【下一步】链接目标。
      科幻
      打破#最终目标达到了。
  DONE
  TARGETDIR = $(pwd命令-P)#得到规范的目录。路径
  #输出最终目标的标准路径。
  #请注意,我们手动解决/结束路径。和/ ..以确保我们有一个标准化的路径。
  如果[$ FNAME=。 ];然后
    命令的printf'%s的\\ n'$ {TARGETDIR%/}
  ELIF [$ FNAME=..];然后
    #警告:像在/ var / ..将解析/私营(假设的/ var @ - > /私营/ VAR),即'..'应用
    #规范化了。
    命令的printf'%s的\\ n'$(命令目录名称 - $ {} TARGETDIR)
  其他
    命令的printf'%s的\\ n'$ {TARGETDIR%/} / $ FNAME
  科幻

要成为稳健和predictable,函数使用命令,以确保只有shell内建命令或外部工具分别被称为(忽略重载别名和函数的形式)。结果
它已经在最新版本以下的炮弹进行测试:庆典破折号 KSH 的zsh


对于如何处理的来源的调用

TL;博士

使用POSIX功能只:


  • 不能够的决定脚本路径来源的调用(除 zsh的,其中,但是,通常并不作为 SH )。

  • 您的可以的检测是否的脚本正在来源只有当你的脚本正在采购的直接的由壳(如外壳轮廓/初始化文件;可能通过的的sourcings的),通过比较 $ 1,0 到外壳可执行文件的名称/路径(除了在的zsh ,其中,如上所述 $ 1,0 确实是当前脚本的路径)。相比之下(除的zsh ),脚本正在从采购的另一个脚本的是本身的直接引用的,包含的脚本的路径 $ 1,0

  • 为了解决这些问题,庆典 KSH 的zsh 有无的非标准的特点的允许确定实际脚本路径甚至在了源代码的情况,也检测脚本是否正在采购或不;例如,在庆典 $ BASH_SOURCE 总是的包含了运行脚本的路径,无论是正在采购或没有, [$ 0!=$ BASH_SOURCE]] 可用于测试脚本是否正在采购。

要说明为什么不能做,让我们从分析命令

 #不建议 - 见下文的讨论。
    DIR = $(CD-P - $(目录名称 - $(命令-v - $ 0))和安培;&安培; PWD -P)


  • (二旁白:

    • 使用 -P 两次是多余的 - 这是足以与 PWD 使用

    • 命令缺少 CD 的潜在标准输出输出的沉默,如果 $ CDPATH 情况进行设置。 )


  • 命令-v - $ 0

    • 命令-v - $ 0旨在涵盖一个额外的场景:如果脚本正在的来源的从的互动的外壳, $ 1,0 通常包含的只是文件名的外壳可执行文件( SH ),在这种情况下,目录名只会返回(因为这是目录名给出的参数,当没有的路径的分量)总是一样。
      命令-v - $ 0然后返回通过 $ PATH 查找(<$ C $ shell的绝对路径C> / bin / sh的
      )。但是请注意,该登录的在某些平台上(如OSX)弹有其名prefixed与 - $ 1,0 -SH ),在这种情况下,命令-v - $ 0不作为工作打算(返回的空字符串的)。

    • 相反, 命令-v - $ 0可以在两种的的-sourced方案中,胡作非为外壳可执行文件, SH 直接的调用,用脚本作为参数:

      • 如果脚本本身的的可执行文件:命令-v - $ 0可能返回的空字符串,这取决于具体的外壳作为 SH 给定系统上:庆典 KSH 的zsh 返回一个空字符串;仅破折号回声 $ 1,0 结果

      • 如果脚本的的可执行文件,而不是在 $ PATH ,并调用使用它的只是文件名的(例如, SH myScript的),命令-v - $ 0也将返回的空字符串的,除了在破折号


    • 鉴于该脚本目录的不能的脚本时,被的来源确定的 - 因为 $ 1,0 则没有按' ŧ包含该信息(除的zsh ,它通常不会充当 SH ) - 有没有很好的解决这个问题。

      • 返还的外壳可执行文件的的在这种情况下目录路径是有限的使用 - 它毕竟,不是的脚本的目录 - 除非是后来使用该路径一个的测试的决定的的剧本正在采购。

        • 系统更可靠的方法是简单地测试 $ 1,0 直接: [$ 0=SH] || [$ 0=-SH] || [$ 0=/ bin / sh的]


      • 然而,即使如果该脚本是从来源不工作的其他的脚本(这是本身的直接的调用),因为 $ 0个然后只需包含的采购的脚本的路径。


    • 由于命令-v的作用有限 - $ 0在了源代码的场景和,它打破两个的-sourced场景的事实我的票是不使用它,它留给我们:

      • 所有的的-sourced场景的覆盖。

      • 来源的调用,您的无法确定脚本的路径的,充其量,在有限的情况下,你可以的检测是否采购正在发生:

        • 在当前采购的直接的由shell(如从壳型材/初始化文件), $ DIR 结束或者包含,如果解释器可执行程序被调用作为一个单纯的文件名(适用目录名来一个单纯的文件名的总是的返回),或在shell可执行文件的目录路径,否则。 无法从当前目录中的非调用来源可靠地区分。

        • 当来自的另一个脚本的(这是本身不是也提供), $ 1,0 包含源的的脚本的路径,与被采购的文字没有告诉这是否是该案件的方式。





背景资料

POSIX 定义 $ 1,0 的行为相对于外壳的脚本

从本质上讲, $ 1,0 应反映在脚本文件的路径的为指定的的,这意味着:


  • 不依赖于 $ 1,0 包含的绝对的路径

  • $ 1,0 包含一个绝对的路径仅在


    • 您的明确的指定的绝对的路径;例如。:

      • 〜/斌/ myScript的(假设脚本本身是可执行的)

      • SH〜/斌/ myScript的


    • 您通过的只是文件名的,这就要求它既可执行的的在 $ PATH ;在幕后,系统转换背后 myScript的成绝对路径,然后执行它;例如。:

      • myScript的#执行时的/ home / JDOE /斌/ myScript的,比如



  • 在其他情况下, $ 1,0 将反映脚本的路径的为指定的


    • 当明确调用 SH 使用脚本,这可能是一个的只是文件名的(例如, SH myScript的)或的相对路径的(例如, SH ./myScript

    • 当调用可执行脚本的直接的,这可能是一个的相对的路径(例如, ./ myScript的 - 注意,仅文件名的只会找到脚本的 $ PATH 的)。


在实践中, 庆典破折号 KSH 的zsh 都表现出这种行为。

相反, POSIX并不时的采购的脚本(使用特殊的内置强制 $ 1,0 的价值-in公用(点)),这样的你不能依靠它,然后在实践中,行为跨炮弹有所不同


  • 因此,你不能盲目使用 $ 1,0 当你的脚本被采购,并期望行为规范。

    • 在实践中,庆典破折号 KSH $ 1,0 触及采购脚本时,这意味着 $ 1,0 包含的的的主叫方的 $ 1,0 值,或者更准确地说,最近调用链中调用者的 $ 1,0 价值尚未来源本身;因此, $ 1,0 可能指向要么shell的可执行文件或到源的当前的其他的(直接援引)脚本的路径。

    • 相反,的zsh ,作为唯一的反对者,其实确实的在<$报告的电流的脚本的路径C $ C> $ 1,0 。相反, $ 1,0 将提供没有说明脚本是否正在采购或没有。

    • 简而言之:使用POSIX才有的功能,你既不能告诉可靠手头的剧本是否正在来源,也没有什么手头的路径剧本,也没有什么关系 $ 1,0 当前脚本的路径。


  • 如果您确实需要处理这种情况,您必须确定在现有的具体外壳和访问它的具体的非标准的特性

    • 庆典 KSH 的zsh 所有报价他们的的获取运行脚本的路径方式,甚至当它被采购。


有关完整起见:在 $ 1,0 的值的其他的上下文


  • 内壳的功能,POSIX强制要求 $ 1,0 留的不变的;因此,无论价值具有的之外的功能,这将有的为好。

    • 在实践中,庆典破折号 KSH 你的行为的方式。

    • 同样,的zsh 是唯一的反对者,并报告的函数的名称。


  • 在接受通过 -c 选项命令字符串在启动一个空壳,它的的第一个操作数的(非选项参数),设置 $ 1,0 ;例如。:

    • 上海-c'回声\\ $ 0:$ 0 \\ $ 1:$ 1'foo的一个#, - &GT; '$ 0:foo的$ 1:一个人

    • 庆典破折号 KSH 的zsh 所有行为的方式。


  • ,否则后,在shell的的执行的脚本文件 $ 1,0 是第一个参数的值shell的的父进程的传递 - 通常情况下,这就是的壳牌的名称或路径(如 SH / bin / sh的);这包括:

    • 交互的壳

      • 买者:某些平台上,尤其是OSX,总是创建的登录的创建交互式的炮弹时,和贝壳的 prePEND - $ 1,0 ,以便发出信号,告知这是一个_login壳前壳EM>到shell名;因此,在默认情况下, $ 1,0 报告 -bash ,而不是庆典,在OSX交互式shell。


    • 一个shell读取的从命令的标准输入

      • 这也适用于通过标准输入到管道的脚本文件,在shell(例如, SH&LT; myScript的


    • 庆典破折号 KSH 的zsh 所有行为的方式。


I have the following code in my bash script. Now I wanna use it in POSIX sh. So how to convert it? thanks.

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
解决方案

The POSIX-shell (sh) counterpart of $BASH_SOURCE is $0.

Caveat: The crucial difference is that if your script is being sourced (loaded into the current shell with .), the snippets below will not work properly.

In the simplest case, this will do (the equivalent of the OP's command):

dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)

If you also want to resolve the resulting directory path to its ultimate target in case the directory and/or its components are symlinks, add -P to the pwd command:

dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)

Caveat: This is NOT the same as finding the script's own true directory of origin:
Let's say your script foo is symlinked to /usr/local/bin/foo in the $PATH, but its true path is /foodir/bin/foo.
The above will still report /usr/local/bin, because the symlink resolution (-P) is applied to the directory, /usr/local/bin, rather than to the script itself.

To find the script's own true directory of origin, you'd have to inspect the script's path to see if it's a symlink and, if so, follow the (chain of) symlinks to the ultimate target file, and then extract the directory path from the target file's canonical path.

GNU's readlink -f (better: readlink -e) could do that for you, but readlink is not a POSIX utility.

In fact, there is no POSIX utility for resolving file symlinks.There are ways to work around that, but they're cumbersome and not fully robust:

The following, POSIX-compliant shell function implements what GNU's readlink -e does and is a reasonably robust solution that only fails in two rare edge cases:

  • paths with embedded newlines (very rare)
  • filenames containing literal string -> (also rare)

With this function, named rreadlink, defined, the following determines the script's true directory path of origin:

dir=$(dirname -- "$(rreadlink "$0")")

rreadlink() source code - place before calls to it in scripts:

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
)

To be robust and predictable, the function uses command to ensure that only shell builtins or external utilities are called (ignores overloads in the forms of aliases and functions).
It's been tested in recent versions of the following shells: bash, dash, ksh, zsh.


As for how to handle sourced invocations:

tl;dr:

Using POSIX features only:

  • You cannot determine the script's path in a sourced invocation (except in zsh, which, however, doesn't usually act as sh).
  • You can detect whether or not your script is being sourced ONLY if your script is being sourced directly by the shell (such as in a shell profile/initialization file; possibly via a chain of sourcings), by comparing $0 to the shell executable name/path (except in zsh, where, as noted $0 is truly the current script's path). By contrast (except in zsh), a script being sourced from another script that itself was directly invoked, contains that script's path in $0.
  • To solve these problems, bash, ksh, and zsh have nonstandard features that do allow determining the actual script path even in sourced scenarios and also detecting whether a script is being sourced or not; for instance, in bash, $BASH_SOURCE always contains the running script's path, whether it's being sourced or not, and [[ $0 != "$BASH_SOURCE" ]] can be used to test whether the script is being sourced.

To show why this cannot be done, let's analyze the command from Walter A's answer:

    # NOT recommended - see discussion below.
    DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )

  • (Two asides:
    • Using -P twice is redundant - it's sufficient to use it with pwd.
    • The command is missing silencing of cd's potential stdout output, if $CDPATH happens to be set.)
  • command -v -- "$0"
    • command -v -- "$0" is designed to cover one additional scenario: if the script is being sourced from an interactive shell, $0 typically contains the mere filename of the shell executable (sh), in which case dirname would simply return . (because that's what dirname invariably does when given a argument without a path component).command -v -- "$0" then returns that shell's absolute path through a $PATH lookup (/bin/sh). Note, however, that login shells on some platforms (e.g., OSX) have their filename prefixed with - in $0 (-sh), in which case command -v -- "$0" doesn't work as intended (returns an empty string).
    • Conversely, command -v -- "$0" can misbehave in two non-sourced scenarios in which the shell executable, sh, is directly invoked, with the script as an argument:
      • if the script itself is not executable: command -v -- "$0" may return an empty string, depending on what specific shell acts as sh on a given system: bash, ksh, and zsh return an empty string; only dash echoes $0
      • if the script is executable, but not in the $PATH, and the invocation uses its mere filename (e.g., sh myScript), command -v -- "$0" will also return the empty string, except in dash.
    • Given that the script's directory cannot be determined when the script is being sourced - because $0 then doesn't contain that information (except in zsh, which doesn't usually act as sh) - there's no good solution to this problem.
      • Returning the shell executable's directory path in that situation is of limited use - it is, after all, not the script's directory - except perhaps to later use that path in a test to determine whether or not the script is being sourced.
        • A more reliable approach would be to simply test $0 directly: [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
      • However, even that doesn't work if the script is being sourced from another script (that was itself directly invoked), because $0 then simply contains the sourcing script's path.
    • Given the limited usefulness of command -v -- "$0" in sourced scenarios and the fact that it breaks two non-sourced scenarios, my vote is for NOT using it, which leaves us with:
      • All non-sourced scenarios are covered.
      • In sourced invocations, you cannot determine the script's path, and at best, in limited circumstances, you can detect whether or not sourcing is occurring:
        • When sourced directly by the shell (such as from a shell profile/initialization file), $dir ends up either containing ., if the shell executable was invoked as a mere filename (applying dirname to a mere filename always returns .), or the shell executable's directory path otherwise. . cannot be reliably distinguished from a non-sourced invocation from the current directory.
        • When sourced from another script (that was itself not also sourced), $0 contains that script's path, and the script being sourced has no way of telling whether that's the case.

Background information:

POSIX defines the behavior of $0 with respect to shell scripts here.

Essentially, $0 should reflect the path of the script file as specified, which implies:

  • Do NOT rely on $0 containing an absolute path.
  • $0 contains an absolute path only if:

    • you explicitly specify an absolute path; e.g.:
      • ~/bin/myScript (assuming the script itself is executable)
      • sh ~/bin/myScript
    • you invoke an executable script by mere filename, which requires that it both be executable and in the $PATH; behind the scenes, the system transforms myScript into an absolute path and then executes it; e.g.:
      • myScript # executes /home/jdoe/bin/myScript, for instance
  • In all other cases, $0 will reflect the script path as specified:

    • When explicitly invoking sh with a script, this can be a mere filename (e.g., sh myScript) or a relative path (e.g., sh ./myScript)
    • When invoking an executable script directly, this can be a relative path (e.g., ./myScript - note that a mere filename would only find scripts in the $PATH).

In practice, bash, dash, ksh, and zsh all exhibit this behavior.

By contrast, POSIX does NOT mandate the value of $0 when sourcing a script (using the special built-in utility . ("dot")), so you cannot rely on it, and, in practice, behavior differs across shells.

  • Thus, you cannot blindly use $0 when your script is being sourced and expect standardized behavior.
    • In practice, bash, dash, and ksh leave $0 untouched when sourcing scripts, meaning that $0 contains the caller's $0 value, or, more accurately, the $0 value of the most recent caller in the call chain that hasn't been sourced itself; thus, $0 may point either to the shell's executable or to the path of another (directly invoked) script that sourced the current one.
    • By contrast, zsh, as the lone dissenter, actually does report the current script's path in $0. Conversely, $0 will provide no indication as to whether the script is being sourced or not.
    • In short: using POSIX features only, you can neither tell reliably whether the script at hand is being sourced, nor what the script at hand's path is, nor what the relationship of $0 to the current script's path is.
  • If you do need to handle this situation, you must identify the specific shell at hand and access its specific non-standard features:
    • bash, ksh, and zsh all offer their own ways of obtaining the running script's path, even when it's being sourced.

For the sake of completeness: the value of $0 in other contexts:

  • Inside a shell function, POSIX mandates that $0 remain unchanged; therefore, whatever value it has outside the function, it'll have inside as well.
    • In practice, bash, dash, and ksh do behave that way.
    • Again, zsh is the lone dissenter and reports the function's name.
  • In a shell that accepted a command string via the -c option on startup, it's the first operand (non-option argument) that sets $0; e.g.:
    • sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
    • bash, dash, ksh, and zsh all behave that way.
  • Otherwise, in a shell not executing a script file, $0 is the value of the first argument that the shell's parent process passed - typically, that's the shell's name or path (e.g. sh, or /bin/sh); this includes:
    • an interactive shell
      • Caveat: some platforms, notably OSX, always create login shells when creating interactive shells, and prepend - to the shell name before placing it in $0, so as to signal to the shell that it is a _login shell; thus, by default, $0 reports -bash, not bash, in interactive shells on OSX.
    • a shell that reads commands from stdin
      • this also applies to piping a script file to the shell via stdin (e.g., sh < myScript)
    • bash, dash, ksh, and zsh all behave that way.

这篇关于如何让POSIX sh脚本目录?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-30 16:09