如何在POSIX sh 中获取脚本目录?
How to get script directory in POSIX sh?
我的 bash 脚本中有以下代码。现在我想在 POSIX sh 中使用它。如何转换?
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
@City 回应说
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "[=10=]")")" && pwd -P )
有效。我也用过
我在 .
找到了命令
$BASH_SOURCE
的 POSIX-shell (sh
) 对应项是 [=17=]
。 背景信息见底部
注意事项:关键区别在于如果您的脚本是来源(加载到current shell with .
),下面的 片段将 not 正常工作。 下面进一步解释
请注意,我已将下面代码段中的 DIR
更改为 dir
,因为它是 better not to use all-uppercase variable names 以避免与环境变量和特殊 shell变量。
CDPATH=
前缀取代了原始命令中的 > /dev/null
:$CDPATH
设置为空字符串,以确保 cd
从不 echoes随便什么。
在最简单的情况下,这样做(相当于 OP 的命令):
dir=$(CDPATH= cd -- "$(dirname -- "[=10=]")" && pwd)
如果您还想将结果目录路径解析为其最终目标,以防目录and/or其组件是symlinks,在pwd
命令中添加-P
:
dir=$(CDPATH= cd -- "$(dirname -- "[=11=]")" && pwd -P)
警告:这不是与查找脚本自己的真实目录相同:
假设您的脚本 foo
在 $PATH
中符号链接到 /usr/local/bin/foo
,但其真实路径是 /foodir/bin/foo
.
上面仍然会报告 /usr/local/bin
,因为符号链接解析 (-P
) 应用于 目录 、/usr/local/bin
,而不是脚本本身.
要找到脚本自己的真实目录 ,您必须检查 脚本的 到 的路径查看它是否是 symlink,如果是,则遵循指向最终目标文件的(链)符号链接,然后从 target 文件的规范路径。
GNU 的 readlink -f
(更好:readlink -e
)可以为您做到这一点,但 readlink
不是 POSIX 实用程序。
虽然 BSD 平台(包括 macOS)也有 readlink
实用程序,但在 macOS 上它不支持 -f
的功能。也就是说,为了显示 任务变得多么简单 如果 readlink -f
可用 :
dir=$(dirname "$(readlink -f -- "[=40=]")")
。
事实上,有 no POSIX 工具来解析 file 符号链接.
有一些方法可以解决这个问题,但它们很麻烦而且不够稳健:
下面的POSIX-compliantshell函数实现了GNU的readlink -e
所做的并且是相当稳健的解决方案,仅在两种罕见的边缘情况下失败:
- 带有嵌入 换行符 的路径(非常罕见)
- 包含文字字符串的文件名
->
(也很少见)
有了这个函数,命名为rreadlink
,定义,下面确定脚本的真实目录源路径:
dir=$(dirname -- "$(rreadlink "[=12=]")")
注意:如果您愿意假设存在一个 (non-POSIX) readlink
实用程序——它将涵盖 macOS、FreeBSD 和 Linux - 在相关问题的 this answer 中可以找到类似但更简单的解决方案。
rreadlink()
源代码 - 在脚本中调用之前放置 :
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target= 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
)
为了健壮和可预测,该函数使用 command
来确保仅调用 shell 内置函数或外部实用程序(忽略别名和函数形式的重载)。
它已在以下 shell 的最新版本中进行了 测试:bash
、dash
、ksh
、zsh
。
如何处理 sourced 调用:
tl;dr:
仅使用POSIX功能:
您无法确定sourced[中的脚本路径 =494=] 调用(zsh
除外,但通常不作为 sh
)。
您可以检测您的脚本是否仅当您的脚本被直接获取时[=] =494=] 通过 shell(例如在 shell profile/initialization 文件中;可能通过 链 来源) ,通过将 [=17=]
与 shell 可执行文件 name/path 进行比较(zsh
除外,其中 [=17=]
确实是当前脚本的路径)。相比之下(zsh
除外),源自 另一个脚本 的脚本本身 直接调用 ,包含 脚本在 [=17=]
.
中的路径
为了解决这些问题,bash
、ksh
、zsh
有非标准 features do 允许确定实际的脚本路径,即使在源场景中也可以检测脚本是否正在源;例如,在 bash
、$BASH_SOURCE
中总是 包含 运行 脚本的路径,无论它是否被源化,并且 [[ [=63=] != "$BASH_SOURCE" ]]
可以用于测试脚本是否被获取。
- This answer 在确定给定脚本是否为来源的上下文中间接展示了这些技术。
为了说明为什么不能这样做,让我们分析一下来自的命令:
# NOT recommended - see discussion below.
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "[=14=]")")" && pwd -P )
- (两条旁白:
- 使用
-P
两次是多余的 - 与 pwd
一起使用就足够了。
- 如果恰好设置了
$CDPATH
,该命令缺少对 cd
潜在标准输出输出的静音。)
command -v -- "[=68=]"
command -v -- "[=68=]"
旨在涵盖一个额外的场景:如果脚本是 sourced 来自 interactive shell,[=17=]
通常包含 shell 可执行文件 (sh
) 的 文件名 ,在这种情况下 dirname
会简单地 return .
(因为当给定一个没有 path 组件的参数时,dirname
总是这样做)。
command -v -- "[=68=]"
然后 return 是 shell 通过 $PATH
查找 (/bin/sh
) 的绝对路径。但是请注意,某些平台上的 login shells(例如 OSX)的文件名在 [=17=]
中以 -
为前缀(-sh
),在这种情况下 command -v -- "[=68=]"
无法正常工作(return 是一个 空字符串 )。
- 相反,
command -v -- "[=68=]"
可能在两个 非 来源的场景 中行为不当,其中 shell 可执行文件, sh
,是直接调用的,脚本作为参数:
- 如果脚本本身不是可执行文件:
command -v -- "[=68=]"
可能return一个空字符串,这取决于什么特定的 shell 在给定系统上充当 sh
:bash
、ksh
和 zsh
return 一个空字符串;只有 dash
回显 [=17=]
POSIX spec. for command
没有明确说明 command -v
在应用于 文件系统路径 时是否应该仅 return 可执行文件 - 这就是 bash
、ksh
和 zsh
所做的 - 但你可以争辩说 [=46= 的目的暗示了它];奇怪的是,dash
通常是最顺从的 POSIX 公民,却偏离了这里的标准。相比之下,ksh
是这里唯一的模范公民,因为它是唯一一个只报告可执行文件的人 并且 报告它们时 absolute(尽管未规范化)路径,正如规范所要求的那样。
- 如果脚本 是 可执行文件,但不在
$PATH
中,并且调用使用其 纯文件名 (例如, sh myScript
), command -v -- "[=68=]"
也将 return 空字符串 , 除了 dash
.
- 鉴于脚本的目录 无法 在脚本 sourced 时确定 - 因为
[=17=]
然后不会包含该信息(zsh
除外,它通常不作为 sh
)- 这个问题没有好的解决方案。
- 返回 shell 可执行文件的 目录路径在那种情况下用途有限 - 毕竟,它不是 脚本的 目录 - 除了稍后可能在 测试 中使用该路径来确定 是否 脚本被获取。
- 更可靠的方法是直接测试
[=17=]
:[ "[=107=]" = "sh" ] || [ "[=107=]" = "-sh" ] || [ "[=107=]" = "/bin/sh" ]
- 但是,如果脚本来自 另一个 脚本(它本身 直接 调用),即使这样也不起作用,因为
[=17=]
然后只包含 sourcing 脚本的路径。
- 鉴于
command -v -- "[=68=]"
在源场景中的用处有限以及它打破了两个 非 源场景的事实, 我的投票是不使用它,这给我们留下了:
- 涵盖所有非来源的场景。
- 在 sourced 调用中,您 无法确定脚本的路径 ,充其量,在有限的情况下,您可以检测是否 来源发生:
- 当 shell 直接 来源时(例如来自 shell profile/initialization 文件),
$dir
结束要么包含 .
,如果 shell 可执行文件仅作为文件名调用(将 dirname
应用于纯粹的文件名 always returns .
),否则 shell 可执行文件的目录路径。 .
无法可靠地区分来自当前目录的 non-sourced 调用。
- 当来源于另一个脚本(它本身也不是来源)时,
[=17=]
包含那个脚本的路径,并且获取的脚本无法判断情况是否如此。
背景资料:
POSIX 定义了 [=17=]
相对于 shell scripts[= 的 行为494=]here.
本质上,[=17=]
应该按照指定反映脚本文件的路径,这意味着:
不要依赖包含绝对路径.
的[=17=]
[=17=]
包含 绝对 路径 仅当:
你显式指定一个绝对路径;例如:
~/bin/myScript
(假设脚本本身是可执行的)
sh ~/bin/myScript
您通过 仅文件名 调用可执行脚本,这要求它在 和 中都是可执行的 $PATH
;在后台,系统将 myScript
转换为绝对路径,然后执行;例如:
myScript # executes /home/jdoe/bin/myScript, for instance
在所有其他情况下,[=17=]
将按指定反映脚本 路径 :
- 当使用脚本显式调用
sh
时,这可以是 纯文件名 (例如 sh myScript
)或 相关路径(例如,sh ./myScript
)
- 当直接调用可执行脚本时,这可以是相对路径(例如,
./myScript
- 请注意仅文件名 只能在 $PATH
). 中找到脚本
实际上,bash
、dash
、ksh
和 zsh
都表现出这种行为。
相比之下,POSIX 在 采购 脚本 时不强制要求 [=17=]
的值(使用特殊的 built-in 实用程序 .
("dot")),所以 你不能依赖它 ,并且在实践中, 行为shells.
不同
- 因此,您不能在获取脚本资源时盲目地使用
[=17=]
并期待标准化行为。
- 实际上,
bash
、dash
和 ksh
在采购脚本时保留 [=17=]
不变 ,这意味着 [ =17=]
包含 caller 的 [=17=]
值,或者更准确地说,是调用链中最近调用者的 [=17=]
值自己采购;因此,[=17=]
可能指向 shell 的可执行文件或 另一个 (直接调用)脚本的路径,该脚本是当前脚本的来源。
- 相比之下,
zsh
作为唯一的异议者,实际上确实报告了当前脚本在[=17中的路径=].相反,[=17=]
将不提供有关脚本是否正在来源的指示。
- 简而言之:仅使用 POSIX 功能,您既不能可靠地判断手头的脚本是否是来源,也不能判断手头的脚本路径是什么,也不能判断它们之间的关系
[=17=]
当前脚本的路径是.
- 如果您确实需要处理这种情况,您必须确定手头的具体 shell 并访问其 具体 non-standard功能:
bash
、ksh
和 zsh
都提供了自己的 方法来获取 运行 脚本的路径,甚至采购时。
为了完整起见:在其他上下文中[=17=]
的值:
- 在shell函数中,POSIX强制
[=17=]
保持不变;因此,无论它在外部函数中具有什么值,它也会在内部中具有任何值。
- 在实践中,
bash
、dash
和 ksh
确实如此。
- 同样,
zsh
是唯一的反对者并报告了 函数的 名称。
- 在启动时通过
-c
选项接受命令字符串的shell中,它是第一个操作数(non-option 参数)设置 [=17=]
;例如。:
sh -c 'echo $0: [=161=] $1: ' foo one # -> '[=161=]: foo : one'
bash
、dash
、ksh
和 zsh
都是这样。
- 否则,在shell不执行脚本文件,
[=17=]
是 shell 的 父进程 传递的第一个参数的值 - 通常,这是 shell的名称或路径(例如sh
,或/bin/sh
);这包括:
- 互动shell
- 注意事项:某些平台,尤其是 OSX,在创建交互式 login shell 时总是创建 [= =755=]s,并且 在 shell 名称之前添加
-
,然后将其放入 [=17=]
,以便向 [=755] 发出信号=] 这是一个 _login shell;因此,默认情况下,[=17=]
在 OSX. 上的交互式 shell 中报告 -bash
,而不是 bash
- a shell 从 stdin 读取 命令
- 这也适用于通过标准输入(例如,
sh < myScript
)将脚本文件通过管道传输到 shell
bash
、dash
、ksh
和 zsh
都是这样。
if OLDPWD=/dev/fd/0 \
cd - && ls -lLidFH ?
then cd . <8
fi </proc/self/fd 8<. 9<[=10=]
那里。这应该使您能够通过一些神奇的链接将 directpry 更改为文件描述符。
设置 $OLDPWD
pre-cd
在一次更改目录 期间导出值(注意:cd
会对 hash
表,但我知道唯一 sh
实际上男性可以很好地使用这些表的是 kevin ahlmquists - 自从 herbert xu - dash
,也许还有一些 bsd
东西,但我知道什么?) 但不会因更改而结转任何 cd
导出。
因此,$OLDPWD
实际上并没有改变,即使它有任何价值,它仍然保持原样。 $PWD
由于第一个 cd
而改变,值变成 /dev/fd/0
指向 /proc/self/fd
,我们进程的文件描述符列表应该在 .
,包括 [=25=]
在 ./2
.
上的任何内容
所以我们做了一个 ls ... ?
并查看我们可以获得的所有精彩信息,然后我们去哪里。
耶!
我的 bash 脚本中有以下代码。现在我想在 POSIX sh 中使用它。如何转换?
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
@City 回应说
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "[=10=]")")" && pwd -P )
有效。我也用过
我在 .
$BASH_SOURCE
的 POSIX-shell (sh
) 对应项是 [=17=]
。 背景信息见底部
注意事项:关键区别在于如果您的脚本是来源(加载到current shell with .
),下面的 片段将 not 正常工作。 下面进一步解释
请注意,我已将下面代码段中的 DIR
更改为 dir
,因为它是 better not to use all-uppercase variable names 以避免与环境变量和特殊 shell变量。
CDPATH=
前缀取代了原始命令中的 > /dev/null
:$CDPATH
设置为空字符串,以确保 cd
从不 echoes随便什么。
在最简单的情况下,这样做(相当于 OP 的命令):
dir=$(CDPATH= cd -- "$(dirname -- "[=10=]")" && pwd)
如果您还想将结果目录路径解析为其最终目标,以防目录and/or其组件是symlinks,在pwd
命令中添加-P
:
dir=$(CDPATH= cd -- "$(dirname -- "[=11=]")" && pwd -P)
警告:这不是与查找脚本自己的真实目录相同:
假设您的脚本 foo
在 $PATH
中符号链接到 /usr/local/bin/foo
,但其真实路径是 /foodir/bin/foo
.
上面仍然会报告 /usr/local/bin
,因为符号链接解析 (-P
) 应用于 目录 、/usr/local/bin
,而不是脚本本身.
要找到脚本自己的真实目录 ,您必须检查 脚本的 到 的路径查看它是否是 symlink,如果是,则遵循指向最终目标文件的(链)符号链接,然后从 target 文件的规范路径。
GNU 的 readlink -f
(更好:readlink -e
)可以为您做到这一点,但 readlink
不是 POSIX 实用程序。
虽然 BSD 平台(包括 macOS)也有 readlink
实用程序,但在 macOS 上它不支持 -f
的功能。也就是说,为了显示 任务变得多么简单 如果 readlink -f
可用 :dir=$(dirname "$(readlink -f -- "[=40=]")")
。
事实上,有 no POSIX 工具来解析 file 符号链接. 有一些方法可以解决这个问题,但它们很麻烦而且不够稳健:
下面的POSIX-compliantshell函数实现了GNU的readlink -e
所做的并且是相当稳健的解决方案,仅在两种罕见的边缘情况下失败:
- 带有嵌入 换行符 的路径(非常罕见)
- 包含文字字符串的文件名
->
(也很少见)
有了这个函数,命名为rreadlink
,定义,下面确定脚本的真实目录源路径:
dir=$(dirname -- "$(rreadlink "[=12=]")")
注意:如果您愿意假设存在一个 (non-POSIX) readlink
实用程序——它将涵盖 macOS、FreeBSD 和 Linux - 在相关问题的 this answer 中可以找到类似但更简单的解决方案。
rreadlink()
源代码 - 在脚本中调用之前放置 :
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target= 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
)
为了健壮和可预测,该函数使用 command
来确保仅调用 shell 内置函数或外部实用程序(忽略别名和函数形式的重载)。
它已在以下 shell 的最新版本中进行了 测试:bash
、dash
、ksh
、zsh
。
如何处理 sourced 调用:
tl;dr:
仅使用POSIX功能:
您无法确定sourced[中的脚本路径 =494=] 调用(
zsh
除外,但通常不作为sh
)。您可以检测您的脚本是否仅当您的脚本被直接获取时[=] =494=] 通过 shell(例如在 shell profile/initialization 文件中;可能通过 链 来源) ,通过将
中的路径[=17=]
与 shell 可执行文件 name/path 进行比较(zsh
除外,其中[=17=]
确实是当前脚本的路径)。相比之下(zsh
除外),源自 另一个脚本 的脚本本身 直接调用 ,包含 脚本在[=17=]
.为了解决这些问题,
bash
、ksh
、zsh
有非标准 features do 允许确定实际的脚本路径,即使在源场景中也可以检测脚本是否正在源;例如,在bash
、$BASH_SOURCE
中总是 包含 运行 脚本的路径,无论它是否被源化,并且[[ [=63=] != "$BASH_SOURCE" ]]
可以用于测试脚本是否被获取。- This answer 在确定给定脚本是否为来源的上下文中间接展示了这些技术。
为了说明为什么不能这样做,让我们分析一下来自
# NOT recommended - see discussion below.
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "[=14=]")")" && pwd -P )
- (两条旁白:
- 使用
-P
两次是多余的 - 与pwd
一起使用就足够了。 - 如果恰好设置了
$CDPATH
,该命令缺少对cd
潜在标准输出输出的静音。)
- 使用
command -v -- "[=68=]"
command -v -- "[=68=]"
旨在涵盖一个额外的场景:如果脚本是 sourced 来自 interactive shell,[=17=]
通常包含 shell 可执行文件 (sh
) 的 文件名 ,在这种情况下dirname
会简单地 return.
(因为当给定一个没有 path 组件的参数时,dirname
总是这样做)。command -v -- "[=68=]"
然后 return 是 shell 通过$PATH
查找 (/bin/sh
) 的绝对路径。但是请注意,某些平台上的 login shells(例如 OSX)的文件名在[=17=]
中以-
为前缀(-sh
),在这种情况下command -v -- "[=68=]"
无法正常工作(return 是一个 空字符串 )。- 相反,
command -v -- "[=68=]"
可能在两个 非 来源的场景 中行为不当,其中 shell 可执行文件,sh
,是直接调用的,脚本作为参数:- 如果脚本本身不是可执行文件:
command -v -- "[=68=]"
可能return一个空字符串,这取决于什么特定的 shell 在给定系统上充当sh
:bash
、ksh
和zsh
return 一个空字符串;只有dash
回显[=17=]
POSIX spec. forcommand
没有明确说明command -v
在应用于 文件系统路径 时是否应该仅 return 可执行文件 - 这就是bash
、ksh
和zsh
所做的 - 但你可以争辩说 [=46= 的目的暗示了它];奇怪的是,dash
通常是最顺从的 POSIX 公民,却偏离了这里的标准。相比之下,ksh
是这里唯一的模范公民,因为它是唯一一个只报告可执行文件的人 并且 报告它们时 absolute(尽管未规范化)路径,正如规范所要求的那样。 - 如果脚本 是 可执行文件,但不在
$PATH
中,并且调用使用其 纯文件名 (例如,sh myScript
),command -v -- "[=68=]"
也将 return 空字符串 , 除了dash
.
- 如果脚本本身不是可执行文件:
- 鉴于脚本的目录 无法 在脚本 sourced 时确定 - 因为
[=17=]
然后不会包含该信息(zsh
除外,它通常不作为sh
)- 这个问题没有好的解决方案。- 返回 shell 可执行文件的 目录路径在那种情况下用途有限 - 毕竟,它不是 脚本的 目录 - 除了稍后可能在 测试 中使用该路径来确定 是否 脚本被获取。
- 更可靠的方法是直接测试
[=17=]
:[ "[=107=]" = "sh" ] || [ "[=107=]" = "-sh" ] || [ "[=107=]" = "/bin/sh" ]
- 更可靠的方法是直接测试
- 但是,如果脚本来自 另一个 脚本(它本身 直接 调用),即使这样也不起作用,因为
[=17=]
然后只包含 sourcing 脚本的路径。
- 返回 shell 可执行文件的 目录路径在那种情况下用途有限 - 毕竟,它不是 脚本的 目录 - 除了稍后可能在 测试 中使用该路径来确定 是否 脚本被获取。
- 鉴于
command -v -- "[=68=]"
在源场景中的用处有限以及它打破了两个 非 源场景的事实, 我的投票是不使用它,这给我们留下了:- 涵盖所有非来源的场景。
- 在 sourced 调用中,您 无法确定脚本的路径 ,充其量,在有限的情况下,您可以检测是否 来源发生:
- 当 shell 直接 来源时(例如来自 shell profile/initialization 文件),
$dir
结束要么包含.
,如果 shell 可执行文件仅作为文件名调用(将dirname
应用于纯粹的文件名 always returns.
),否则 shell 可执行文件的目录路径。.
无法可靠地区分来自当前目录的 non-sourced 调用。 - 当来源于另一个脚本(它本身也不是来源)时,
[=17=]
包含那个脚本的路径,并且获取的脚本无法判断情况是否如此。
- 当 shell 直接 来源时(例如来自 shell profile/initialization 文件),
背景资料:
POSIX 定义了 [=17=]
相对于 shell scripts[= 的 行为494=]here.
本质上,[=17=]
应该按照指定反映脚本文件的路径,这意味着:
不要依赖包含绝对路径.
的[=17=]
[=17=]
包含 绝对 路径 仅当:你显式指定一个绝对路径;例如:
~/bin/myScript
(假设脚本本身是可执行的)sh ~/bin/myScript
您通过 仅文件名 调用可执行脚本,这要求它在 和 中都是可执行的
$PATH
;在后台,系统将myScript
转换为绝对路径,然后执行;例如:myScript # executes /home/jdoe/bin/myScript, for instance
在所有其他情况下,
[=17=]
将按指定反映脚本 路径 :- 当使用脚本显式调用
sh
时,这可以是 纯文件名 (例如sh myScript
)或 相关路径(例如,sh ./myScript
) - 当直接调用可执行脚本时,这可以是相对路径(例如,
./myScript
- 请注意仅文件名 只能在$PATH
). 中找到脚本
- 当使用脚本显式调用
实际上,bash
、dash
、ksh
和 zsh
都表现出这种行为。
相比之下,POSIX 在 采购 脚本 时不强制要求 [=17=]
的值(使用特殊的 built-in 实用程序 .
("dot")),所以 你不能依赖它 ,并且在实践中, 行为shells.
- 因此,您不能在获取脚本资源时盲目地使用
[=17=]
并期待标准化行为。- 实际上,
bash
、dash
和ksh
在采购脚本时保留[=17=]
不变 ,这意味着[ =17=]
包含 caller 的[=17=]
值,或者更准确地说,是调用链中最近调用者的[=17=]
值自己采购;因此,[=17=]
可能指向 shell 的可执行文件或 另一个 (直接调用)脚本的路径,该脚本是当前脚本的来源。 - 相比之下,
zsh
作为唯一的异议者,实际上确实报告了当前脚本在[=17中的路径=].相反,[=17=]
将不提供有关脚本是否正在来源的指示。 - 简而言之:仅使用 POSIX 功能,您既不能可靠地判断手头的脚本是否是来源,也不能判断手头的脚本路径是什么,也不能判断它们之间的关系
[=17=]
当前脚本的路径是.
- 实际上,
- 如果您确实需要处理这种情况,您必须确定手头的具体 shell 并访问其 具体 non-standard功能:
bash
、ksh
和zsh
都提供了自己的 方法来获取 运行 脚本的路径,甚至采购时。
为了完整起见:在其他上下文中[=17=]
的值:
- 在shell函数中,POSIX强制
[=17=]
保持不变;因此,无论它在外部函数中具有什么值,它也会在内部中具有任何值。- 在实践中,
bash
、dash
和ksh
确实如此。 - 同样,
zsh
是唯一的反对者并报告了 函数的 名称。
- 在实践中,
- 在启动时通过
-c
选项接受命令字符串的shell中,它是第一个操作数(non-option 参数)设置[=17=]
;例如。:sh -c 'echo $0: [=161=] $1: ' foo one # -> '[=161=]: foo : one'
bash
、dash
、ksh
和zsh
都是这样。
- 否则,在shell不执行脚本文件,
[=17=]
是 shell 的 父进程 传递的第一个参数的值 - 通常,这是 shell的名称或路径(例如sh
,或/bin/sh
);这包括:- 互动shell
- 注意事项:某些平台,尤其是 OSX,在创建交互式 login shell 时总是创建 [= =755=]s,并且 在 shell 名称之前添加
-
,然后将其放入[=17=]
,以便向 [=755] 发出信号=] 这是一个 _login shell;因此,默认情况下,[=17=]
在 OSX. 上的交互式 shell 中报告
-bash
,而不是bash
- 注意事项:某些平台,尤其是 OSX,在创建交互式 login shell 时总是创建 [= =755=]s,并且 在 shell 名称之前添加
- a shell 从 stdin 读取 命令
- 这也适用于通过标准输入(例如,
sh < myScript
)将脚本文件通过管道传输到 shell
- 这也适用于通过标准输入(例如,
bash
、dash
、ksh
和zsh
都是这样。
- 互动shell
if OLDPWD=/dev/fd/0 \
cd - && ls -lLidFH ?
then cd . <8
fi </proc/self/fd 8<. 9<[=10=]
那里。这应该使您能够通过一些神奇的链接将 directpry 更改为文件描述符。
设置 $OLDPWD
pre-cd
在一次更改目录 期间导出值(注意:cd
会对 hash
表,但我知道唯一 sh
实际上男性可以很好地使用这些表的是 kevin ahlmquists - 自从 herbert xu - dash
,也许还有一些 bsd
东西,但我知道什么?) 但不会因更改而结转任何 cd
导出。
因此,$OLDPWD
实际上并没有改变,即使它有任何价值,它仍然保持原样。 $PWD
由于第一个 cd
而改变,值变成 /dev/fd/0
指向 /proc/self/fd
,我们进程的文件描述符列表应该在 .
,包括 [=25=]
在 ./2
.
所以我们做了一个 ls ... ?
并查看我们可以获得的所有精彩信息,然后我们去哪里。
耶!