从脚本本身获取 Bash 脚本的源目录

如何获取其中的目录路径的 Bash脚本所在,该脚本里面

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

$ ./application

答案

#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

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

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

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$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 ,symlinks 等的任意组合。

请注意:如果在运行此代码段之前先将cd切换到其他目录,则结果可能不正确!

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

要了解其工作原理,请尝试运行以下更详细的形式:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$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"

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

#!/bin/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
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
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]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

SCRIPT_PATH完整路径给出。
只需确保在脚本开始时找到它即可。

此注释和代码为 Copyleft,是 GPL2.0 或更高版本或 CC-SA 3.0(CreativeCommons Share Alike)或更高版本的可选许可证。 (c)2008。保留所有权利。没有任何形式的保证。你被警告了。
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

简短答案:

`dirname $0`

或( 最好 ):

$(dirname "$0")

您可以使用 $ BASH_SOURCE

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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

应该这样做:

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

在路径中使用符号链接和空格。有关目录名readlink 的信息,请参见手册页。

编辑:

从注释轨道看,它似乎不适用于 Mac OS。我不知道为什么会这样。有什么建议么?

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

但是, dirname恰好给出了文件名的目录部分,该部分很可能相对于当前工作目录。如果你的脚本需要出于某种原因更改目录,然后从输出dirname就变得毫无意义。

我建议以下内容:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

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

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

虽然只是

cd `dirname $0`

解决了问题中的特定情况,我发现绝对路径通常更有用。

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

./script

/usr/bin/script

script

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

一种执行您要执行的操作的方法是仅对 / usr / share 目录中的数据进行硬编码,然后按完整路径进行引用。无论如何,数据都不应位于 / usr / bin 目录中,所以这可能是要做的事情。

我厌倦了一次又一次地复制此页面以将单线粘贴到接受的答案中。问题在于它不容易理解和记住。

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

DIR=$(dirname "${BASH_SOURCE[0]}")  # get the directory name
DIR=$(realpath "${DIR}")    # resolve its full path if need be