在当前脚本所在的目录中运行一个脚本。

90

我有两个Bash脚本在同一个文件夹中(由下载整个代码库的用户保存在某个地方):

  • script.sh 由用户运行
  • helper.shscript.sh所需并运行

这两个脚本应该在同一个目录下。我需要第一个脚本调用第二个脚本,但是有两个问题:

  1. 知道当前工作目录对我来说没有用,因为我不知道用户如何执行第一个脚本(可以使用 /usr/bin/script.sh,使用./script.sh,或者可以使用 ../Downloads/repo/scr/script.sh
  2. 在调用helper.sh之前,脚本script.sh将更改到另一个目录。

我肯定可以编写Bash代码来存储当前目录变量,但是对于我想象中的一个非常普遍和简单的任务来说,那段代码似乎过于复杂。

有没有一种标准的方法可以从script.sh内部可靠地调用helper.sh?并且能在任何支持Bash的操作系统上运行?

5个回答

84

由于$0保存正在运行的脚本的完整路径,因此您可以使用dirname来获取脚本的路径:

#!/bin/bash

script_name=$0
script_full_path=$(dirname "$0")

echo "script_name: $script_name"
echo "full path: $script_full_path"

所以,如果你将它存储在/tmp/a.sh中,那么你将会看到如下输出:

$ /tmp/a.sh
script_name: /tmp/a.sh
full path: /tmp

所以:

  1. 对我而言,知道当前工作目录是无用的,因为我不知道用户如何执行第一个脚本(可能是使用 /usr/bin/script.sh./script.sh,或者 ../Downloads/repo/scr/script.sh)。

使用 dirname "$0" 可以让你追踪原始路径。

  1. 在调用 helper.sh 之前,脚本 script.sh 将更改到不同的目录。

同样,由于你有 $0 中的路径,你可以 cd 回去。


10
简而言之,在脚本源代码中使用 $0 可能无法按预期工作。对于bash,有一个名为 BASH_SOURCE 的替代方法。 - Alex Che
$0将无法在原始脚本中执行的子脚本文件中工作。 - Nam G VU
@AlexChe 我之前使用${BASH_SOURCE%/*}时,手动执行时可以正常工作,但是当通过cron启动时失败了。我将其更改为$(dirname "$0"),现在一切都正常工作了。 - AndreKR
@AndreKR,你的系统上cron是否可能使用其他shell而不是Bash?请参考这个问题作为一个例子。 - Alex Che
1
@AlexChe 说得好。我的脚本中没有 shebang,并且 crontab shell 设置为 /bin/sh - 显然在 Ubuntu 上是 dash - AndreKR

53

$0 被许多开发人员认为是 不安全易碎 的。我找到了另一种解决方案,它适用于一系列 bash 脚本和 source

如果 a.sh 需要使用一个 bash 进程来执行位于相同文件夹中的脚本 b.sh

#!/bin/bash
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
bash ${__dir}/b.sh

如果 a.sh 需要使用 相同的 bash 进程执行位于同一文件夹中的 b.sh:

#!/bin/bash
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source ${__dir}/b.sh

您的链接指向了一个已删除的评论。 您是否有其他来源来支持不安全声明? 以下是一些解释 $0问题的相关帖子: $0BASH_SOURCE之间进行选择, 如何在 POSIX sh 中获取脚本目录? - Cris Luengo
@CrisLuengo 相信这是链接:http://mywiki.wooledge.org/BashFAQ/028 - John Drinane
@CrisLuengo,实际上至少接受的答案解释了为什么$0是不正确的。因为在脚本被源化的情况下,$0会给出错误的位置,因为此时$0包含进行源化的脚本的位置,而不是被源化的脚本的位置。 - Maarten Derickx
我之前使用${BASH_SOURCE%/*}时,手动执行时可以正常工作,但是当通过cron启动时失败了。将其更改为$(dirname "$0")后,现在一切都正常工作了。 - AndreKR

14

如何可靠地从script.sh中调用helper.sh?并且在任何支持 Bash 的操作系统中都能运行?

大多数情况下,当helper.shscript.sh在同一目录中时,您可以在script.sh命令中使用:

. ${0%/*}/helper.sh

解释:
$0 保存你的进程名称(在大多数情况下它是指向你的脚本的完整路径)。
${parameter%word} 从变量$parameter中移除后缀模式word(在上面的命令中,它从存储在变量$0中的完整路径中删除文件名/*)。

如果由于其他原因(在其他答案中有描述),你不想使用$0,你可以使用$BASH_SOURCE代替:

. ${BASH_SOURCE%/*}/helper.sh

如果你愿意的话,你可以使用source代替.

source ${BASH_SOURCE%/*}/helper.sh

对我来说,这是实现你的目标最简单的方法。


我之前使用${BASH_SOURCE%/*},在手动执行时可以正常工作,但是在由cron启动时失败了。把它改成$(dirname "$0"),现在一切都正常工作了。 - AndreKR

1

这是我在所有脚本中都有的标准变量块:

# Initialize global variables.
{
  declare SCRIPT_INVOKED_NAME="${BASH_SOURCE[${#BASH_SOURCE[@]}-1]}"
  declare SCRIPT_NAME="${SCRIPT_INVOKED_NAME##*/}"
  declare SCRIPT_INVOKED_PATH="$( dirname "${SCRIPT_INVOKED_NAME}" )"
  declare SCRIPT_PATH="$( cd "${SCRIPT_INVOKED_PATH}"; pwd )"
  declare SCRIPT_RUN_DATE="$( date )"
}

然后我使用SCRIPT_PATH来加载同一目录中的其他脚本:

source "${SCRIPT_PATH}/script-helpers.inc.sh"

0

您可以使用$0变量。但这并不像获取当前脚本名称那么简单:

d=${0%/*}
[ x"$d" = x"$0" ] && d=.   # portable variant, use [[ ...]] in bash freely
readonly d                 # optional, for additional safety

# ... later on
. "$d"/helper.sh

这在 set -e 的情况下也能很好地工作。


请问 [ x"$d" = x"$0" ] 这个语法是什么意思?在相等运算符之前的 x 的语法作用是什么?或者在以下习语中 [ -z ${var+x} ] 是什么? - von spotz
1
@vonspotz 针对第一个问题:它可以防止错误的发生,这些错误可能是由于$0$d以短横线 - 开头而引起的,否则它会被解释为对 [ ... ] 命令的指示。 - Vadim Zhukov
1
@vonspotz 对于第二个问题:${var+x}的意思是“如果$var被设置了,就用x替换它”。即使$var为空,这也会导致x字符串被替换。但是如果$var根本没有被设置,那么什么也不会被替换。因此,这与[ x"$foo" = x"$bar" ]的问题完全不同。 - Vadim Zhukov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接