繁体   English   中英

如何从脚本本身中获取 Bash 脚本所在的目录?

[英]How can I get the directory where a Bash script is located from within the script itself?

如何该脚本中获取Bash脚本所在目录的路径?

我想使用 Bash 脚本作为另一个应用程序的启动器。 我想将工作目录更改为 Bash 脚本所在的目录,这样我就可以对该目录中的文件进行操作,如下所示:

$ ./application
#!/usr/bin/env bash

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

是一个有用的单行代码,无论从哪里调用它,它都会为您提供脚本的完整目录名称。

只要用于查找脚本的路径的最后一个组件不是符号链接(目录链接正常),它就可以工作。 如果您还想解析脚本本身的任何链接,则需要一个多行解决方案:

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
  SOURCE=$(readlink "$SOURCE")
  [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )

最后一个可以与别名、 sourcebash -c 、符号链接等的任意组合一起使用。

请注意:如果您在运行此代码段之前cd到不同的目录,结果可能不正确!

此外,如果用户巧妙地覆盖 cd 以将输出重定向到 stderr(包括转义序列,例如在 Mac 上调用update_terminal_cwd >&2时),请注意$CDPATH gotchas和 stderr 输出副作用。 cd命令末尾添加>/dev/null 2>&1将处理这两种可能性。

要了解它是如何工作的,请尝试运行这个更详细的表单:

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET=$(readlink "$SOURCE")
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE=$TARGET
  else
    DIR=$( dirname "$SOURCE" )
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE=$DIR/$TARGET # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR=$( dirname "$SOURCE" )
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

它会打印出如下内容:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

使用dirname "$0"

#!/usr/bin/env bash

echo "The script you are running has basename $( basename -- "$0"; ), dirname $( dirname -- "$0"; )";
echo "The present working directory is $( pwd; )";

如果您不是从脚本所在的目录运行脚本,则单独使用pwd将不起作用。

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp

dirname命令是最基本的,只需从$0 (脚本名称)变量解析到文件名的路径:

dirname -- "$0";

但是,正如matt b所指出的,返回的路径因脚本的调用方式而异。 pwd不做这项工作,因为它只告诉您当前目录是什么,而不是脚本所在的目录。此外,如果执行脚本的符号链接,您将获得一个(可能是相对的)路径到链接所在的位置,而不是实际的脚本。

其他一些人提到了readlink命令,但在最简单的情况下,您可以使用:

dirname -- "$( readlink -f -- "$0"; )";

readlink会将脚本路径解析为文件系统根目录的绝对路径。 因此,任何包含单点或双点、波浪线和/或符号链接的路径都将被解析为完整路径。

这是一个演示这些的脚本whatdir.sh

#!/usr/bin/env bash

echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename -- "$0"`"
echo "dirname: `dirname -- "$0"`"
echo "dirname/readlink: $( dirname -- "$( readlink -f -- "$0"; )"; )"

使用相对路径在我的主目录中运行此脚本:

>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

同样,但使用脚本的完整路径:

>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

现在更改目录:

>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

最后使用符号链接执行脚本:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

然而,当脚本在 bash 中获取(而不是执行)时,有一种情况不起作用:

>>>$ cd /tmp
>>>$ . ~/whatdir.sh  
pwd: /tmp
$0: bash
basename: bash
dirname: .
dirname/readlink: /tmp
pushd . > '/dev/null';
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";

while [ -h "$SCRIPT_PATH" ];
do
    cd "$( dirname -- "$SCRIPT_PATH"; )";
    SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";
done

cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null';
SCRIPT_PATH="$( pwd; )";
popd  > '/dev/null';

它适用于所有版本,包括

  • 通过多深度软链接调用时,
  • 当文件它
  • 当命令“ source ”又名调用脚本时. (点)运算符。
  • 当从调用者修改 arg $0时。
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

或者,如果 Bash 脚本本身是一个相对符号链接,您希望遵循它并返回链接到脚本的完整路径:

pushd . > '/dev/null';
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";

while [ -h "$SCRIPT_PATH" ];
do
    cd "$( dirname -- "$SCRIPT_PATH"; )";
    SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";
done

cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null';
SCRIPT_PATH="$( pwd; )";
popd  > '/dev/null';

SCRIPT_PATH以完整路径给出,无论它如何被调用。

只需确保在脚本开始时找到它。

您可以使用$BASH_SOURCE

#!/usr/bin/env bash

scriptdir="$( dirname -- "$BASH_SOURCE"; )";

请注意,您需要使用#!/bin/bash而不是#!/bin/sh ,因为它是 Bash 扩展。

简短的回答:

"`dirname -- "$0";`"

或(最好):

"$( dirname -- "$0"; )"

这是一个易于记忆的脚本:

DIR="$( dirname -- "${BASH_SOURCE[0]}"; )";   # Get the directory name
DIR="$( realpath -f -- "$DIR"; )";    # Resolve its full path if need be

这应该这样做:

DIR="$(dirname "$(realpath "$0")")"

这适用于路径中的符号链接和空格。

请参阅dirnamerealpath的手册页。

请添加有关如何支持 MacOS 的评论。 对不起,我可以验证它。

pwd可用于查找当前工作目录,而dirname可用于查找特定文件的目录(运行的命令是$0 ,因此dirname $0应该为您提供当前脚本的目录)。

但是, dirname精确地给出了文件名的目录部分,这很可能是相对于当前工作目录的。 如果您的脚本出于某种原因需要更改目录,则dirname的输出将变得毫无意义。

我建议如下:

#!/usr/bin/env bash

reldir="$( dirname -- "$0"; )";
cd "$reldir";
directory="$( pwd; )";

echo "Directory is ${directory}";

这样,您将获得一个绝对目录,而不是相对目录。

由于脚本将在单独的 Bash 实例中运行,因此无需在之后恢复工作目录,但如果您出于某种原因确实想在脚本中改回,您可以轻松地将pwd的值分配给更改目录之前的变量,以备将来使用。

虽然只是

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

解决了问题中的特定场景,我发现绝对路径更普遍更有用。

SCRIPT_DIR=$( cd ${0%/*} && pwd -P )

我认为这并不像其他人所说的那么容易。 pwd不起作用,因为当前目录不一定是脚本所在的目录。 $0也不总是有信息。 考虑以下三种调用脚本的方法:

./script

/usr/bin/script

script

在第一种和第三种方式中, $0没有完整的路径信息。 在第二个和第三个中, pwd不起作用。 以第三种方式获取目录的唯一方法是遍历路径并找到具有正确匹配的文件。 基本上,代码必须重做操作系统所做的事情。

执行您所要求的一种方法是对/usr/share目录中的数据进行硬编码,并通过其完整路径引用它。 无论如何,数据不应该在/usr/bin目录中,所以这可能是要做的事情。

这将获取Mac OS X v10.6.6 (Snow Leopard) 上的当前工作目录:

DIR=$(cd "$(dirname "$0")"; pwd)
$(dirname "$(readlink -f "$BASH_SOURCE")")

这是特定于 Linux 的,但您可以使用:

SELF=$(readlink /proc/$$/fd/255)

这是一个符合 POSIX 的单行:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH

执行此操作的最短和最优雅的方法是:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

这适用于所有平台并且非常干净。

更多细节可以在“那个 bash 脚本在哪个目录? ”中找到。

#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`

这是简单,正确的方法:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

解释:

  • ${BASH_SOURCE[0]} - 脚本的完整路径。 即使在获取脚本时,它的值也是正确的,例如source <(echo 'echo $0')打印bash ,而将其替换为${BASH_SOURCE[0]}将打印脚本的完整路径。 (当然,这假设您可以依赖 Bash。)

  • readlink -f - 递归解析指定路径中的任何符号链接。 这是一个 GNU 扩展,在(例如)BSD 系统上不可用。 如果您运行的是 Mac,则可以使用 Homebrew 安装 GNU coreutils并用greadlink -f代替它。

  • 当然dirname获取路径的父目录。

我尝试了所有这些,但都没有奏效。 一个非常接近,但它有一个小虫子把它弄坏了; 他们忘记将路径用引号引起来。

还有很多人认为你是从 shell 运行脚本,所以他们忘记了当你打开一个新脚本时,它默认为你的家。

试试这个目录的大小:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

无论您如何或在何处运行它,这都是正确的:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

所以为了让它真正有用,这里是如何切换到运行脚本的目录:

cd "`dirname "$0"`"

这是对 e-satis 和 3bcdnlklvc04a 在他们的回答中指出的解决方案的轻微修改:

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}

这应该在他们列出的所有情况下仍然有效。

这将在pushd失败后阻止popd 感谢 konsolebox。

概括:

FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"

# OR, if you do NOT need it to work for **sourced** scripts too:
# FULL_PATH_TO_SCRIPT="$(realpath "$0")"

# OR, depending on which path you want, in case of nested `source` calls
# FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[0]}")"

# OR, add `-s` to NOT expand symlinks in the path:
# FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

细节:

如何获取正在运行来源的任何脚本的完整文件路径完整目录基本文件名...

...即使从另一个 bash 函数或脚本中调用被调用的脚本,或者使用嵌套源时!

在许多情况下,您只需要获取刚刚调用的脚本的完整路径。 这可以使用realpath轻松完成。 请注意, realpathGNU coreutils的一部分。 如果您还没有安装它(它是 Ubuntu 的默认设置),您可以使用sudo apt update && sudo apt install coreutils安装它。

get_script_path.sh (有关此脚本的最新版本,请参阅我的eRCaGuy_hello_world存储库中的get_script_path.sh ):

#!/bin/bash

# A. Obtain the full path, and expand (walk down) symbolic links
# A.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.
# FULL_PATH_TO_SCRIPT="$(realpath "$0")"
# A.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even
# if the script is called from within another bash function!
# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use
# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.
FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"
# B.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.
# FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "$0")"
# B.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even
# if the script is called from within another bash function!
# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use
# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.
FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "${BASH_SOURCE[-1]}")"

# You can then also get the full path to the directory, and the base
# filename, like this:
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

# Now print it all out
echo "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""
echo "SCRIPT_DIRECTORY    = \"$SCRIPT_DIRECTORY\""
echo "SCRIPT_FILENAME     = \"$SCRIPT_FILENAME\""

关于嵌套source调用的重要说明:如果上面的"${BASH_SOURCE[-1]}"不能满足您的要求,请尝试使用"${BASH_SOURCE[0]}" 第一个 ( 0 ) 索引为您提供数组中的第一个条目,最后一个 ( -1 ) 索引为您提供数组中的最后一个条目。 根据您所追求的,您实际上可能想要第一个条目。 当我使用~/.bashrc采购时,我发现情况就是这样. ~/.bashrc . ~/.bashrc ,它的来源~/.bash_aliases. ~/.bash_aliases . ~/.bash_aliases ,我想要~/.bash_aliases realpath的真实路径(带有扩展的符号链接),而不是~/.bashrc文件。 由于这些是嵌套source调用,因此使用"${BASH_SOURCE[0]}"给了我想要的东西: ~/.bash_aliases的扩展路径! 但是,使用"${BASH_SOURCE[-1]}"给了我我想要的东西: ~/.bashrc的扩展路径。

示例命令和输出:

  1. 运行脚本:
     ~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.sh FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh" SCRIPT_DIRECTORY = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash" SCRIPT_FILENAME = "get_script_path.sh"
  2. 使用. get_script_path.sh . get_script_path.shsource get_script_path.sh (结果与上面完全相同,因为我在脚本中使用了"${BASH_SOURCE[-1]}"而不是"$0" ):
     ~/GS/dev/eRCaGuy_hello_world/bash$ . get_script_path.sh FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh" SCRIPT_DIRECTORY = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash" SCRIPT_FILENAME = "get_script_path.sh"

如果您在脚本中使用"$0"而不是"${BASH_SOURCE[-1]}" ,则在运行脚本时会得到与上面相同的输出,但在获取脚本时会出现这种不希望的输出:

~/GS/dev/eRCaGuy_hello_world/bash$ . get_script_path.sh 
FULL_PATH_TO_SCRIPT               = "/bin/bash"
SCRIPT_DIRECTORY                  = "/bin"
SCRIPT_FILENAME                   = "bash"

而且,显然如果您使用"$BASH_SOURCE"而不是"${BASH_SOURCE[-1]}" ,如果从另一个 bash 函数中调用脚本,它将不起作用 因此,使用"${BASH_SOURCE[-1]}"是最好的方法,因为它解决了这两个问题! 请参阅下面的参考资料。

realpathrealpath -s之间的区别:

请注意, realpath还成功地遍历符号链接以确定并指向其目标,而不是指向符号链接。 如果您不想要这种行为(有时我不想要),那么将-s添加到上面的realpath命令中,使该行看起来像这样:

# Obtain the full path, but do NOT expand (walk down) symbolic links; in
# other words: **keep** the symlinks as part of the path!
FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

这样,符号链接不会被扩展。 相反,它们保持原样,作为完整路径中的符号链接。

上面的代码现在是我的eRCaGuy_hello_world 存储库的一部分,位于此文件中: bash/get_script_path.sh 参考并运行此文件以获取路径中带有和不带有符号链接的完整示例。 请参阅文件底部的两种情况下的示例输出。

参考:

  1. 如何检索给定相对路径的绝对路径
  2. 教了我关于BASH_SOURCE变量的知识: Unix 和 Linux:确定源 shell 脚本的路径
  3. 告诉我BASH_SOURCE实际上是一个数组,我们希望它的最后一个元素在函数中按预期工作(因此我在这里的代码中使用"${BASH_SOURCE[-1]}" ): Unix & Linux : 确定源 shell 脚本的路径
  4. man bash --> 搜索BASH_SOURCE

    BASH_SOURCE

    一个数组变量,其成员是源文件名,其中定义了FUNCNAME数组变量中的相应 shell 函数名称。 shell 函数${FUNCNAME[$i]}在文件${BASH_SOURCE[$i]}中定义并从${BASH_SOURCE[$i+1]}调用。

也可以看看:

  1. [我的答案] Unix & Linux:确定源 shell 脚本的路径

我会使用这样的东西:

# Retrieve the full pathname of the called script
scriptPath=$(which $0)

# Check whether the path is a link or not
if [ -L $scriptPath ]; then

    # It is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # Otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi

对于具有 GNU coreutils readlink的系统(例如 Linux):

$(readlink -f "$(dirname "$0")")

$0包含脚本文件名时,无需使用BASH_SOURCE

尝试使用:

real=$(realpath "$(dirname "$0")")

$_作为$0的替代品值得一提。 如果您从 Bash 运行脚本,则接受的答案可以缩短为:

DIR="$( dirname "$_" )"

请注意,这必须是脚本中的第一条语句。

这适用于 Bash 3.2:

path="$( dirname "$( which "$0" )" )"

如果您的$PATH中有~/bin目录,则此目录中有A 它获取脚本~/bin/lib/B 您知道包含的脚本在lib子目录中相对于原始脚本的位置,但不知道它相对于用户当前目录的位置。

这可以通过以下方式解决(在A内部):

source "$( dirname "$( which "$0" )" )/lib/B"

用户在哪里或他/她如何调用脚本并不重要。 这将始终有效。

这些是获取脚本信息的简短方法:

文件夹和文件:

    Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

使用这些命令:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

我得到了这个输出:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

另见: https ://pastebin.com/J8KjxrPF

我比较了许多给出的答案,并提出了一些更紧凑的解决方案。 这些似乎可以处理由您最喜欢的组合引起的所有疯狂边缘情况:

  • 绝对路径或相对路径
  • 文件和目录软链接
  • 作为scriptbash scriptbash -c scriptsource script. script . script
  • 目录和/或文件名中的空格、制表符、换行符、Unicode 等
  • 以连字符开头的文件名

如果您在 Linux 上运行,似乎使用proc句柄是定位当前运行脚本的完全解析源的最佳解决方案(在交互式会话中,链接指向相应的/dev/pts/X ):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

这有点难看,但修复程序紧凑且易于理解。 我们不仅仅使用 bash 原语,但我对此表示同意,因为readlink大大简化了任务。 echo X在变量字符串的末尾添加了一个X ,这样文件名中的任何尾随空格都不会被吃掉,并且行尾的参数替换${VAR%X}去掉了X 因为readlink添加了它自己的换行符(如果不是我们之前的诡计,它通常会在命令替换中被吃掉),我们也必须摆脱它。 使用$''引用方案最容易做到这一点,它允许我们使用转义序列,例如\n来表示换行符(这也是您可以轻松创建命名不正确的目录和文件的方法)。

以上内容应满足您在 Linux 上查找当前正在运行的脚本的需求,但如果您没有proc文件系统可供使用,或者您正在尝试查找其他文件的完全解析路径,那么也许您会发现下面的代码很有帮助。 这只是对上述单线的轻微修改。 如果您正在使用奇怪的目录/文件名,请使用lsreadlink检查输出信息,因为ls将输出“简化”路径,替换? 对于换行符之类的东西。

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"

尝试以下交叉兼容的解决方案:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

因为诸如realpathreadlink之类的命令可能不可用(取决于操作系统)。

注意:在 Bash 中,建议使用${BASH_SOURCE[0]}而不是$0 ,否则在获取文件时路径可能会中断( source / . )。

或者,您可以在 Bash 中尝试以下功能:

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

这个函数接受一个参数。 如果参数已经有绝对路径,则按原样打印,否则打印$PWD变量 + 文件名参数(不带./前缀)。

有关的:

我相信我有这个。 我参加聚会迟到了,但我想有些人如果遇到这个帖子会很高兴来到这里。 评论应解释:

#!/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

嗯,如果在路径中, basenamedirname只是不会切断它,并且很难走路径(如果父级没有导出 PATH 怎么办?!)。

但是,shell 必须对其脚本有一个打开的句柄,而在 Bash 中,句柄是#255。

SELF=`readlink /proc/$$/fd/255`

为我工作。

在我看来,最好的紧凑型解决方案是:

"$( cd "$( echo "${BASH_SOURCE[0]%/*}" )"; pwd )"

除了 Bash,不依赖任何东西。 使用dirnamereadlinkbasename最终会导致兼容性问题,因此尽可能避免使用它们。

您只需将脚本名称 ( $0 ) 与realpath和/或dirname组合起来即可。 它适用于 Bash 和 Shell。

#!/usr/bin/env bash

RELATIVE_PATH="${0}"
RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"

echo "RELATIVE_PATH->${RELATIVE_PATH}<-"
echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"
echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"
echo "FULL_PATH->${FULL_PATH}<-"

输出将是这样的:

# RELATIVE_PATH->./bin/startup.sh<-
# RELATIVE_DIR_PATH->./bin<-
# FULL_DIR_PATH->/opt/my_app/bin<-
# FULL_PATH->/opt/my_app/bin/startup.sh<-

$0 是脚本本身的名称

4.4. 特殊变量类型

一个例子: LozanoMatheus/get_script_paths.sh

这些其他答案都不适用于FinderOS X中启动的 Bash 脚本。 我最终使用:

SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
sed s:.*/Volumes/:/Volumes/:`"

它不漂亮,但它完成了工作。

使用readlink的组合来规范化名称(如果它是符号链接,则可以将其返回到其源)和dirname来提取目录名称:

script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"

当这里的其他答案没有时,这对我有用:

thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath

顶级响应并非在所有情况下都有效......

由于我在通过“sh my_script.sh”调用shell脚本时在一些非常新鲜和不太新鲜安装的Ubuntu 16.04 (Xenial Xerus)系统上使用包含“cd”方法的BASH_SOURCE出现问题,我尝试了一些东西不同的是,到目前为止,就我的目的而言,它似乎运行得相当顺利。 该方法在脚本中更加紧凑,并且更加不那么神秘。

这种替代方法使用 coreutils 包中的外部应用程序“ realpath ”和“ dirname ”。 (好吧,没有人喜欢调用辅助进程的开销——但是当看到用于解析真实对象的多行脚本时,让它在单个二进制用法中解决也不会那么糟糕。)

因此,让我们看一下用于查询特定文件的真实绝对路径的描述任务的替代解决方案的一个示例:

PATH_TO_SCRIPT=`realpath -s $0`
PATH_TO_SCRIPT_DIR=`dirname $PATH_TO_SCRIPT`

但最好你应该使用这个进化版本来支持使用带空格的路径(或者甚至可能是其他一些特殊字符):

PATH_TO_SCRIPT=`realpath -s "$0"`
PATH_TO_SCRIPT_DIR=`dirname "$PATH_TO_SCRIPT"`

事实上,如果您不需要 SCRIPT 变量的值,那么您也许可以将这两条线合并成一行。 但你为什么真的要为此付出努力呢?

如果目录名称末尾有任何换行符,当前的解决方案都不起作用 - 它们将被命令替换剥离。 要解决此问题,您可以在命令替换中附加一个非换行符,然后仅删除该字符:

dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd && echo x)"
dir="${dir%x}"

这可以防止两种非常常见的情况:事故和破坏。 脚本不应该仅仅因为某人在某处做了mkdir $'\n'就以不可预知的方式失败。

烦人的是,当可执行脚本是符号链接时,这是我发现的唯一一种适用于 Linux 和 macOS 的单行程序:

SCRIPT_DIR=$(python -c "import os; print(os.path.dirname(os.path.realpath('${BASH_SOURCE[0]}')))")

或者,类似地,使用 python3 pathlib 模块:

SCRIPT_DIR=$(python3 -c "from pathlib import Path; print(Path('${BASH_SOURCE[0]}').resolve().parent)")

在 Linux 和 macOS 上进行了测试,并与此要点中的其他解决方案进行了比较: https ://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371

我通常使用:

dirname $(which $BASH_SOURCE)

这是我发现可靠地告诉的唯一方法:

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))

$0不是获取当前脚本路径的可靠方法。 例如,这是我的.xprofile

#!/bin/bash
echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/my_script.sh &

cd /tmp && ~/.xprofile && source ~/.xprofile

/home/puchuu/.xprofile
/home/puchuu/.xprofile
-bash
/home/puchuu/.xprofile

所以请改用BASH_SOURCE

这是一个在 Bash 或 zsh 下工作的命令,无论是独立执行还是源代码执行:

[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \
    || this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")

这个怎么运作

zsh 当前文件扩展名: ${(%):-%x}

zsh 中的${(%):-%x}扩展为当前执行文件的路径。

后备替换运算符:-

您已经知道${...}替换字符串中的变量。 您可能不知道在替换期间可以对变量进行某些操作(在Bashzsh中),例如后备扩展运算符:- :

% x=ok
% echo "${x}"
ok

% echo "${x:-fallback}"
ok

% x=
% echo "${x:-fallback}"
fallback

% y=yvalue
% echo "${x:-$y}"
yvalue

%x提示符转义码

接下来,我们将介绍提示转义码,这是 zsh 独有的功能。 在 zsh 中, %x会扩展为文件的路径,但通常只有在对提示字符串进行扩展时才会这样做。 为了在我们的替换中启用这些代码,我们可以在变量名之前添加一个(%)标志:

% cat apath/test.sh
fpath=%x
echo "${(%)fpath}"

% source apath/test.sh
apath/test.sh

% cd apath
% source test.sh
test.sh

不太可能的匹配:百分比逃逸和后备

到目前为止我们所做的工作,但避免创建额外的fpath变量会更整洁。 我们可以使用:-并将%x放在后备字符串中,而不是将%x放在fpath中:

% cat test.sh
echo "${(%):-%x}"

% source test.sh
test.sh

请注意,我们通常会在(%):-之间放置一个变量名,但我们将其留空。 无法声明或设置具有空白名称的变量,因此始终会触发回退。

完成: print -P %x怎么样?

现在我们几乎有了脚本的目录。 我们本可以使用print -P %x来获得相同的文件路径,但使用更少的 hack,但在我们的例子中,我们需要将它作为参数传递给dirname ,这将需要启动新子 shell 的开销:

% cat apath/test.sh
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "${(%):-%x}"

% source apath/test.sh
apath
apath

事实证明,hacky 方式更加高效和简洁。

这种方法的一个优点是它不涉及 Bash 本身之外的任何东西,也不分叉任何子外壳。

首先,使用模式替换将不以/开头的任何内容(即相对路径)替换$PWD/ 由于我们使用替换来匹配$0的第一个字符,因此我们还必须将其附加回来(替换中的${0:0:1} )。

现在我们有了脚本的完整路径; 我们可以通过删除最后一个/和后面的任何内容(即脚本名称)来获取目录。 然后可以在cd中使用该目录或作为与您的脚本相关的其他路径的前缀。

#!/bin/bash

BIN=${0/#[!\/]/"$PWD/${0:0:1}"}
DIR=${BIN%/*}

cd "$DIR"

如果您的脚本可能是来源而不是执行,您当然可以将$0替换为${BASH_SOURCE[0]} ,例如:

BIN=${BASH_SOURCE[0]/#[!\/]/"$PWD/${BASH_SOURCE[0]:0:1}"}

这也适用于可执行脚本。 它更长,但更多价。

此解决方案仅适用于 Bash。 请注意,如果您尝试从函数中查找路径,通常提供的答案${BASH_SOURCE[0]}将不起作用。

我发现这一行总是有效的,无论文件是作为脚本获取还是作为脚本运行。

dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

如果您想遵循符号链接,请在上面的路径上使用readlink递归或非递归。

这是一个脚本,用于尝试并将其与其他建议的解决方案进行比较。 将其作为source test1/test2/test_script.shbash test1/test2/test_script.sh调用。

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

单线工作的原因是通过使用BASH_SOURCE环境变量及其相关联的FUNCNAME来解释的。

BASH_SOURCE

一个数组变量,其成员是源文件名,其中定义了 FUNCNAME 数组变量中的相应 shell 函数名称。 shell 函数 ${FUNCNAME[$i]} 在文件 ${BASH_SOURCE[$i]} 中定义并从 ${BASH_SOURCE[$i+1]} 调用。

功能名称

一个数组变量,包含当前在执行调用堆栈中的所有 shell 函数的名称。 索引为 0 的元素是任何当前正在执行的 shell 函数的名称。 最底部的元素(索引最高的元素)是“main”。 此变量仅在执行 shell 函数时存在。 对 FUNCNAME 的分配没有任何效果并返回错误状态。 如果未设置 FUNCNAME,它将失去其特殊属性,即使它随后被重置。

此变量可与 BASH_LINENO 和 BASH_SOURCE 一起使用。 FUNCNAME 的每个元素在 BASH_LINENO 和 BASH_SOURCE 中都有对应的元素来描述调用堆栈。 例如,从文件 ${BASH_SOURCE[$i+1]} 的行号 ${BASH_LINENO[$i]} 调用 ${FUNCNAME[$i]}。 内置调用者使用此信息显示当前调用堆栈。

[来源:Bash 手册]

这就是我在脚本上的工作方式:

pathvar="$( cd "$( dirname $0 )" && pwd )"

这将告诉您正在从哪个目录执行 Launcher(当前脚本)。

如果您的 Bash 脚本是 symlink ,那么这就是执行此操作的方法:

#!/usr/bin/env bash

dirn="$(dirname "$0")"
rl="$(readlink "$0")";
exec_dir="$(dirname $(dirname "$rl"))";
my_path="$dirn/$exec_dir";
X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"

X 是包含 Bash 脚本的目录(原始文件,而不是符号链接)。 我向上帝发誓这是行得通的,这是我所知道的正确执行此操作的唯一方法。

以下将返回脚本的当前目录

  • 如果它有来源或没有来源,则有效
  • 如果在当前目录或其他目录中运行,则可以工作。
  • 如果使用相对目录,则有效。
  • 适用于 bash,不确定其他 shell。
/tmp/a/b/c $ . ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ cd

~ $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ . ../../tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ /tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ ../../tmp/a/b/c/test.sh
/tmp/a/b/c

测试.sh

#!/usr/bin/env bash

# snagged from: https://stackoverflow.com/a/51264222/26510
function toAbsPath {
    local target
    target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

function getScriptDir(){
  local SOURCED
  local RESULT
  (return 0 2>/dev/null) && SOURCED=1 || SOURCED=0

  if [ "$SOURCED" == "1" ]
  then
    RESULT=$(dirname "$1")
  else
    RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
  fi
  toAbsPath "$RESULT"
}

SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"

多次提到 Python。 这是 JavaScript(即Node.js )替代方案:

baseDirRelative=$(dirname "$0")
baseDir=$(node -e "console.log(require('path').resolve('$baseDirRelative'))") # Get absolute path using Node.js

echo $baseDir

我尝试了以下 3 种不同的执行方式。

echo $(realpath $_)

. application         # /correct/path/to/dir or /path/to/temporary_dir
bash application      # /path/to/bash
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $(dirname $0))

. application         # failed with `realpath: missing operand`
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $BASH_SOURCE)

$BASH_SOURCE${BASH_SOURCE[0]}基本相同。

. application         # /correct/path/to/dir
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

只有$(realpath $BASH_SOURCE)似乎是可靠的。

大多数答案要么不处理通过相对路径符号链接的文件,要么不处理单行文件,要么不处理 BSD (Mac)。 一个解决所有三个问题的解决方案是:

HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo .)")"; pwd)

首先, cd 到 bash 脚本目录的概念。 然后读取文件以查看它是否是符号链接(相对或其他),如果是,则 cd 到该目录。 如果没有,则 cd 到当前目录(必须保持单线)。 然后通过pwd回显当前目录。

您可以将--添加到 cd 和 readlink 的参数中,以避免出现类似选项的目录问题,但我不会为大多数目的而烦恼。

您可以在此处查看带有插图的完整说明:

https://www.binaryphile.com/bash/2020/01/12/determining-the-location-of-your-script-in-bash.html

这是一个纯 Bash 解决方案

$ cat a.sh
BASENAME=${BASH_SOURCE/*\/}
DIRNAME=${BASH_SOURCE%$BASENAME}.
echo $DIRNAME

$ a.sh
/usr/local/bin/.

$ ./a.sh
./.

$ . a.sh
/usr/local/bin/.

$ /usr/local/bin/a.sh
/usr/local/bin/.

这是我对shell 脚本的回答的摘录:检查目录名称并转换为小写,其中我不仅演示了如何使用非常基本的 POSIX 指定的实用程序解决这个问题,而且还介绍了如何非常简单地将函数的结果存储在返回的多变的...

...好吧,正如您所看到的,在一些帮助下,我找到了一个非常简单且非常强大的解决方案:

我可以向函数传递一种信使变量,并根据需要取消对结果函数参数的$1名称的任何显式使用与eval的引用,并且在函数例程完成后,我使用eval和反斜杠引用技巧来为我的信使变量分配值我渴望而不必知道它的名字。

在充分披露中,...(我发现了这个的信使变量部分)和Rich 的 sh 技巧,我还在我自己的答案摘录下方摘录了他页面的相关部分。

...摘录: ...

虽然还不是严格意义上的 POSIX,但realpath是自 2012 年以来的 GNU 核心应用程序 完全披露:在我在info coreutils TOC 中注意到它并立即想到 [the linked] 问题之前从未听说过它,但是使用以下演示的功能应该可靠(很快 POSIXLY?),并且我希望能有效地提供它绝对来源$0的来电者:

% _abs_0() {
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}";
> }
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh

值得强调的是,该解决方案使用POSIX 参数扩展来首先检查路径是否真的需要扩展和解析,然后再尝试这样做。 这应该通过一个信使变量返回一个绝对来源的$0 (除了它将保留symlinks ,就像我想象的那样有效无论路径是否已经是绝对的。 ...

次要编辑:在文档中找到realpath之前,我至少削减了我的版本(以下版本)不依赖于时间字段(就像它在第一个ps命令中所做的那样),但是,在测试之后,公平警告有些我不太相信ps在其命令路径扩展能力方面完全可靠

另一方面,您可以这样做:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"

eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

...以及来自Rich 的 sh 技巧 ...

从 shell 函数返回字符串

从上述命令替换的陷阱可以看出,标准输出不是 shell 函数将字符串返回给调用者的好方法,除非输出采用尾随换行符不重要的格式。 当然,对于旨在处理任意字符串的函数,这种做法是不可接受的。 那么,可以做些什么呢?

尝试这个:

func () {
body here
eval "$1=\${foo}"
}

当然, ${foo}可以用任何形式的替换来替换。 这里的关键技巧是 eval 行和转义的使用。 当 eval 的参数由主命令解析器构造时, “$1”被扩展。 但是“${foo}”在这个阶段没有展开,因为“$”已经被引用了。 相反,当 eval 评估其参数时,它会被扩展。 如果不清楚为什么这很重要,请考虑以下情况:

foo='hello ; rm -rf /'
dest=bar
eval "$dest=$foo"

但当然,以下版本是完全安全的:

foo='hello ; rm -rf /'
dest=bar
eval "$dest=\$foo"

请注意,在原始示例中, “$1”用于允许调用者将目标变量名称作为参数传递给函数。 如果您的函数需要使用 shift 命令,例如将剩余的参数处理为“$@” ,那么将“$1”的值保存在函数开头的临时变量中可能会很有用。

我通常这样做:

LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh

下面将脚本的目录路径存储在dir变量中。

(它还尝试支持在 Windows 中的Cygwin下执行。)

最后它运行my-sample-app可执行文件,所有参数都使用"$@"传递给该脚本:

#!/usr/bin/env sh

dir=$(cd "${0%[/\\]*}" > /dev/null && pwd)

if [ -d /proc/cygdrive ]; then
    case "$(uname -s)" in
        CYGWIN*|MINGW32*|MSYS*|MINGW*)
            # We are under Windows, so translate path to Windows format.
            dir=$(cygpath -m "$dir");
            ;;
    esac
fi

# Runs the executable which is beside this script
"${dir}/my-sample-app" "$@"

我认为最简单的答案是原始变量的参数扩展:

#!/usr/bin/env bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "opt1; original answer: $DIR"
echo ''

echo "opt2; simple answer  : ${BASH_SOURCE[0]%/*}"

它应该产生如下输出:

$ /var/tmp/test.sh
opt1; original answer: /var/tmp

opt2; simple answer  : /var/tmp

变量/参数扩展${BASH_SOURCE[0]%/*}"似乎更容易维护。

如果不是来自父脚本且不是 symlinked ,则$0就足够了:

script_path="$0"

如果来自父脚本而不是symlinked ,请使用$BASH_SOURCE${BASH_SOURCE[0]}

script_path="$BASH_SOURCE"

如果symlinked ,使用带有realpathreadlink -f$BASH_SOURCE来获取真实的文件路径:

script_path="$(realpath "$BASH_SOURCE")"

此外, realpathreadlink -f返回绝对路径

要获取脚本的目录,请使用dirname

script_directory="$(dirname "$script_path")"

笔记

您可以通过以下简短方式从脚本本身中获取 Bash 脚本的源目录:

script_path=$(dirname "$(readlink -f "$0")")"/"
echo "$script_path"

样本输出:

/home/username/desktop/

我通常在我的脚本顶部包含以下内容,这在大多数情况下都有效:

[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');

如果从当前路径运行,第一行根据pwd的值分配源,如果从其他地方调用,则根据dirname分配源。

第二行检查路径以查看它是否是符号链接,如果是,则将 SOURCE_DIR 更新为链接本身的位置。

那里可能有更好的解决方案,但这是我自己想出的最干净的解决方案。

function getScriptAbsoluteDir { # fold>>
    # @description used to get the script path
    # @param $1 the script $0 parameter
    local script_invoke_path="$1"
    local cwd=`pwd`

    # absolute path ? if so, the first character is a /
    if test "x${script_invoke_path:0:1}" = 'x/'
    then
        RESULT=`dirname "$script_invoke_path"`
    else
        RESULT=`dirname "$cwd/$script_invoke_path"`
    fi
} # <<fold

尝试这样的事情:

function get_realpath() {

if [[ -f "$1" ]]
then
    # The file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then
        # The file *may* not be local.
        # The exception is ./file.ext
        # tTry 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else
        # file *must* be local
        local tmppwd="$PWD"
    fi
else
    # The file *cannot* exist
    return 1 # Failure
fi

# Reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # Success

}

function get_dirname(){

local realpath="$(get_realpath "$1")"
if (( $? )) # True when non-zero.
then
    return $? # Failure
fi
echo "${realpath%/*}"
return 0 # Success

}

# Then from the top level:
get_dirname './script.sh'

# Or within a script:
get_dirname "$0"

# Can even test the outcome!
if (( $? )) # True when non-zero.
then
    exit 1 # Failure
fi

这些功能和相关工具是我们产品的一部分,已免费提供给社区,可以在 GitHub 上以realpath-lib 的形式找到。 它简单、干净、文档齐全(非常适合学习)、纯 Bash 并且没有依赖项。 也适合跨平台使用。 因此,对于上面的示例,您可以在脚本中简单地:

source '/path/to/realpath-lib'

get_dirname "$0"

if (( $? )) # True when non-zero.
then
    exit 1 # Failure
fi
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`

没有分叉(除了子shell),并且可以处理“外星人”路径名形式,就像有些人声称的那样:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")

关键部分是我正在缩小问题的范围:我禁止通过路径间接执行脚本(如/bin/sh [script path relative to path component] )。

这可以被检测到,因为$0将是一个相对路径,它不会解析为相对于当前文件夹的任何文件。 我相信直接执行使用#! 机制总是导致绝对$0 ,包括在路径上找到脚本时。

我还要求路径名和符号链接链上的任何路径名只包含合理的字符子集,特别是不包含\n>*? . 这是解析逻辑所必需的。

还有一些我不会讨论的隐含期望(请看这个答案),并且我不会尝试处理$0的蓄意破坏(因此请考虑任何安全隐患)。 我希望这可以在几乎任何具有类似 Bourne /bin/sh的类 Unix 系统上工作。

#!/bin/sh
(
    path="${0}"
    while test -n "${path}"; do
        # Make sure we have at least one slash and no leading dash.
        expr "${path}" : / > /dev/null || path="./${path}"
        # Filter out bad characters in the path name.
        expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
        # Catch embedded new-lines and non-existing (or path-relative) files.
        # $0 should always be absolute when scripts are invoked through "#!".
        test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
        # Change to the folder containing the file to resolve relative links.
        folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
        path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
        cd "${folder}"
        # If the last path was not a link then we are in the target folder.
        test -n "${path}" || pwd
    done
)

查看底部带有奇怪目录名称的测试。

要将工作目录更改为 Bash 脚本所在的目录,您应该尝试这个简单、经过测试和验证的shellcheck解决方案:

#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2

考试:

$ ls 
application
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko   46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
SUCCESS
$ *symlink/application && printf "SUCCESS\n" ""
SUCCESS

基于这个答案,我建议将SCRIPT_HOME作为任何当前运行的 Bash 脚本的包含文件夹的澄清版本:

s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`
echo $SCRIPT_HOME

我想评论上面的答案( 如何从脚本本身获取 Bash 脚本的源目录? ),但没有足够的声誉来做到这一点。

两年前,我在 Apple 的文档网站上找到了解决方案: https ://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/AdvancedTechniques/AdvancedTechniques.html。 后来我坚持使用这种方法。 它不能处理软链接,但对我来说效果很好。 我将它发布在这里,供任何需要它的人使用,并作为评论请求。

#!/bin/sh

# Get an absolute path for the poem.txt file.
POEM="$PWD/../poem.txt"

# Get an absolute path for the script file.
SCRIPT="$(which $0)"
if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; then
    SCRIPT="$PWD/$SCRIPT"
fi

如代码所示,得到脚本的绝对路径后,就可以使用dirname命令来获取目录的路径。

即使该脚本已使用bash -c <script>Windows调用,此单行代码也适用于Cygwin

set mydir="$(cygpath "$(dirname "$0")")"

我想确保脚本在其目录中运行。 所以

cd $(dirname $(which $0) )

在此之后,如果您真的想知道您正在运行的位置,请运行以下命令。

DIR=$(/usr/bin/pwd)

没有 100% 可移植且可靠的方法来请求当前脚本目录的路径。 特别是在CygwinMinGWMSYS 、 Linux 等不同的后端之间。这个问题在 Bash 中很久没有得到正确和完全的解决。

例如,如果您想在source命令之后请求路径以嵌套包含另一个 Bash 脚本,而后者又使用相同的source命令包含另一个 Bash 脚本等等,则无法解决此问题。

如果是source命令,我建议将source命令替换为以下内容:

function include()
{
  if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
    local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
    local include_file_path=...
  else
    local dir_path=... request the directory from the "$1" argument using one of answered here methods...
    local include_file_path=...
  fi
  ... push $CURRENT_SCRIPT_DIR in to stack ...
  export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
  source "$include_file_path"
  ... pop $CURRENT_SCRIPT_DIR from stack ...
}

从现在开始, include(...)的使用基于脚本中先前的CURRENT_SCRIPT_DIR

这仅在您可以通过include命令替换所有source命令时才有效。 如果你不能,那么你别无选择。 至少在 Bash 解释器的开发人员做出明确的命令来请求当前正在运行的脚本目录路径之前。

我自己最接近此的实现: https ://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/bash_tacklelib
https://github.com/andry81/tacklelib/tree/trunk/bash/tacklelib/bash_tacklelib

(搜索tkl_include函数)

这是我多年来精心设计的,用作我的 Bash 脚本的标题:

## BASE BRAIN - Get where you're from and who you are.
MYPID=$$
ORIGINAL_DIR="$(pwd)" # This is not a hot air balloon ride..
fa="$0" # First Assumption
ta= # Temporary Assumption
wa= # Weighed Assumption
while true; do
    [ "${fa:0:1}" = "/" ] && wa=$0 && break
    [ "${fa:0:2}" = "./" ] && ta="${ORIGINAL_DIR}/${fa:2}" && [ -e "$ta" ] && wa="$ta" && break
    ta="${ORIGINAL_DIR}/${fa}" && [ -e "$ta" ] && wa="$ta" && break
done
SW="$wa"
SWDIR="$(dirname "$wa")"
SWBIN="$(basename "$wa")"
unset ta fa wa
( [ ! -e "$SWDIR/$SWBIN" ] || [ -z "$SW" ] ) && echo "I could not find my way around :( possible bug in the TOP script" && exit 1

此时,您的变量 SW、SWDIR 和 SWBIN 包含您需要的内容。

选择的答案效果很好。 我将我的解决方案发布给任何寻找更短的替代方案的人,这些替代方案仍然可以解决采购、执行、完整路径、相对路径和符号链接。 最后,这将适用于macOS ,因为不能假设 GNU 的 coreutils 版本的readlink可用。

问题是它没有使用 Bash,但它很容易在 Bash 脚本中使用。 虽然 OP 对解决方案的语言没有任何限制,但最好让大多数人留在 Bash 世界中。 这只是一种选择,而且可能不受欢迎。

PHP 默认在 macOS 上可用,并安装在许多其他平台上,但不一定是默认情况下。 我意识到这是一个缺点,但无论如何,我会把它留给来自搜索引擎的任何人。

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
FOLDERNAME=${PWD##*/}

这是我知道的最快的方法。

把事情简单化。

#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM