如何从脚本本身中获取Bash脚本的源目录


4948

如何在脚本内部获取Bash脚本所在目录的路径?

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

$ ./application

69
如果目录名末尾有任何换行符,则当前解决方案均不起作用-将通过命令替换将其删除。要解决此问题,您可以在命令替换-内附加一个非换行符DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)",并在不使用命令替换-的情况下将其删除DIR="${DIR%x}"
l0b0 2012年

80
@ jpmc26有两种非常常见的情况:事故和破坏活动。脚本不应仅仅因为某人在某处执行了操作而以无法预测的方式失败mkdir $'\n'
l0b0

24
任何允许人们以这种方式破坏其系统的人都不应将其付诸实践以发现此类问题……更不用说雇用能够犯这种错误的人了。在使用bash的25年中,我从未见过这种事情在任何地方发生....这就是为什么我们有诸如perl之类的东西和诸如污点检查之类的东西(我可能会因为这样说而被激怒了:)
osirisgothra 2015-2-5

3
@ l0b0考虑到您需要对进行相同的保护dirname,并且目录可以以-(例如--help)开头。DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}。也许这是矫kill过正?
2015年

55
我强烈建议阅读有关该主题的Bash常见问题解答
Rany Albeg Wein

Answers:


6565
#!/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,符号链接等。

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

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

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

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

27
您可以通过user25866融合的答案这种方法在一个解决方案,工程与到达source <script>bash <script>DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
丹·

21
有时会cd打印一些东西到STDOUT!例如,如果您$CDPATH.。为解决此问题,请使用DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468

182
这个公认的答案不好,它不适用于符号链接,而且过于复杂。dirname $(readlink -f $0)是正确的命令。参见gist.github.com/tvlooy/cbfbdb111a4ebad8b93e了解一个测试用例
tvlooy 2015年

165
@tvlooy IMO,您的回答也不是完全正确,因为当路径中有空格时它会失败。与换行符相反,这并非不可能,甚至不罕见。dirname "$(readlink -f "$0")"不会增加复杂性,并且可以将故障最小化,并且更加合理。
阿德里安·昆特(

9
@tvlooy,您的评论与macOS(或大概是BSD)不兼容,而可接受的答案是。readlink -f $0readlink: illegal option -- f
亚历山大·隆伯格

875

用途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

25
对于超出bash的可移植性,$ 0可能并不总是足够的。如果在路径上找到命令,则可能需要替换“ type -p $ 0”以使此工作有效。
达伦(Darron)

10
@Darron:您只能type -p在脚本可执行的情况下使用。如果使用脚本执行bash test2.sh脚本,并且在其他地方可以执行相同名称的另一个脚本,这也可能会带来一个微妙的漏洞。
D.Shawley,2010年

90
@Darron:但是由于这个问题已被标记bash,并且哈希爆炸行明确提及,/bin/bash所以我要说依靠bashisms是相当安全的。
Joachim Sauer 2010年

34
+1,但是使用的问题dirname $0是,如果该目录是当前目录,则会得到.。很好,除非您要更改脚本中的目录并希望使用从中获得的路径就dirname $0好像它是绝对路径一样。要获取绝对路径:pushd `dirname $0` > /dev/nullSCRIPTPATH=`pwd`popd > /dev/nullpastie.org/1489386(但肯定有一个更好的方式来拓展这条道路?)
TJ克罗德

9
@TJ Crowder我不确定dirname $0如果将其分配给变量然后使用它来启动类似$dir/script.sh; 的脚本,是否会出现问题?我可以想象这是90%的情况下这种类型的用例。./script.sh会很好的。
matt b

513

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

13
readlink在默认安装中的某些平台上将不可用。如果可以,请尽量避免使用它
TL 2012年

43
请谨慎引用所有内容,以避免出现空格问题:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul 2013年

13
在OSX中,优胜美地10.10.1 -f无法识别为的选项readlink。使用stat -f代替可以完成工作。谢谢
cucu8

11
在OSX中,存在greadlink,这基本上是readlink我们都熟悉的。这是独立于平台的版本:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
罗伯特

6
好的,@ robert。仅供参考,greadlink可以通过自制程序轻松安装:brew install coreutils
phatblat

184
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”(又名“。” .)调用脚本时。
  • $0从调用方修改arg时。
  • "./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


4
真好!可以更短一些,用SCRIPT_PATH =替换“ pushd [...] popd / dev / null” readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis

5
而不是使用pushed ...; 使用$(cd dirname "${SCRIPT_PATH}"&& pwd)会更好吗?但是无论如何,很棒的脚本!
ovanes

6
脚本cd退出当前目录以希望cd稍后再次返回是很危险的:脚本可能无权将目录更改回调用该目录时的当前目录。(推动/弹出相同)
Adrian Pronk

7
readlink -f是特定于GNU的。BSD readlink没有该选项。
卡拉布赖特韦尔2014年

3
所有不必要的子shell都有什么用?([ ... ])效率比差[ ... ],并且没有利用隔离来换取此处的性能下降。
2014年

110

简短答案:

`dirname $0`

或(最好):

$(dirname "$0")

17
如果您获取脚本,它将无法正常工作。“来源my / script.sh”
Arunprasad Rajkumar

我一直在我的bash脚本中一直使用这种脚本,这些脚本可以自动执行操作,并经常在同一目录中调用其他脚本。我永远不会source在这些上使用,并且cd $(dirname $0)很容易记住。
kqw

16
@vidstige:${BASH_SOURCE[0]}不是$0将工作带source my/script.sh
蒂莫西·琼斯

@TimothyJones如果从除bash之外的任何其他shell中获取,将在100%的时间内失败。${BASH_SOURCE[0]}一点都不令人满意。${BASH_SOURCE:-0}好多了。
Mathieu CAROFF

106

您可以使用$BASH_SOURCE

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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


14
当我这样做./foo/script,然后$(dirname $BASH_SOURCE)./foo
直到

1
@直到,在这种情况下,我们可以使用realpath命令来获取./foo/script的完整路径。因此dirname $(realpath ./foo/script) 将给出脚本的路径。
purushothaman poovai

73

应该这样做:

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

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

请参阅手册页dirnamereadlink

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


6
与您的解决方案,调用脚本类似./script.sh节目.,而不是完整的目录路径
布鲁诺NegrãoZICA

5
MacOS上的readlink没有-f选项。使用stat代替。但是,它仍然显示.您是否位于“ this”目录中。
丹尼斯的威胁

2
您需要coreutils从Homebrew 安装并用于在MacOS上greadlink获得该-f选件,因为它是* BSD的封面,而不是Linux。
dragon788

您应该在所有右侧添加双引号:DIR="$(dirname "$(readlink -f "$0")")"
hagello

60

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`

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


9
您可以像这样在一行中完成所有操作:DIRECTORY = $(cd dirname $0&& pwd)
dogbane

如果脚本源另一个脚本,并且您想知道后者的名称,则此方法不起作用。
reinierpost 2014年

52

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

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

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

2
或者,更晦涩的是,在一行上: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc

为什么这不是公认的答案?使用realpath“手动”解决循环有什么区别readlink?甚至readlink手册页上都说Note realpath(1) is the preferred command to use for canonicalization functionality.
User9123

1
顺便说一句,我们不应该realpath在之前dirname而不是之后申请吗?如果脚本文件本身是一个符号链接...,它将给出类似的信息DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"。实际上非常接近西蒙提出的答案。
User9123

@ User9123我认为可以接受的是尝试与所有流行的shell /发行版兼容。此外,在大多数情况下,取决于您要执行的操作,人们希望获得符号链接所在的目录,而不是实际源的目录。

37

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

./script

/usr/bin/script

script

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

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


9
如果您想反驳他的评论,请通过代码示例证明脚本可以访问其存储位置。
理查德·杜尔

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

这比选择的答案短。并且似乎也可以正常工作。这应该获得1000票,只是人们不会忽略它。
帕特里克

2
正如前面的许多答案所详细解释的那样,根据脚本的调用方式,$0也不pwd保证也没有正确的信息。
IMSoP 2013年

34
$(dirname "$(readlink -f "$BASH_SOURCE")")

我比较喜欢,$BASH_SOURCE$0,因为它是明确的,即使读者在bash不精通。 $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster

32

这将在Mac OS X 10.6.6上获取当前的工作目录:

DIR=$(cd "$(dirname "$0")"; pwd)

27

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

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

1
它也是特定于bash的,但是bash的行为可能已经改变了吗?/proc/fd/$$/255似乎指向tty,而不是目录。例如,在我当前的登录shell中,文件描述符0、1、2和255都引用/dev/pts/4。无论如何,bash手册没有提到fd 255,因此依靠这种行为可能是不明智的。\
Keith Thompson

2
交互式外壳!=脚本。无论如何,realpath ${BASH_SOURCE[0]};这似乎是最好的方法。
史蒂夫·贝克

23

这是符合POSIX的单线:

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

# test
echo $SCRIPT_PATH

4
当我自己运行脚本或使用sudo运行脚本时,我取得了成功,但是当调用source ./script.sh时却无济于事
Michael R

cd配置为打印新路径名时,它将失败。
亚伦·迪古拉

18

我尝试了所有这些方法,但都无济于事。一个非常接近,但是有一个小错误严重破坏了它。他们忘记将路径用引号引起来。

也有很多人认为您是从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"`"

4
如果脚本是从另一个脚本中获取的,则不起作用。
reinierpost 2014年

如果$ 0的最后一部分是指向另一个目录(ln -s ../bin64/foo /usr/bin/foo)的符号链接,则此操作不起作用。
哈杰罗

17

这是简单正确的方法:

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

说明:

  • ${BASH_SOURCE[0]}-脚本的完整路径。即使脚本是源文件,此值也将是正确的,例如source <(echo 'echo $0')print bash,而用bash替换${BASH_SOURCE[0]}将打印脚本的完整路径。(当然,这假设您可以依赖Bash。)

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

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


1
greadlink -f不幸的是source,在Mac上运行脚本时,效率不高:(
Gabe Kopley

17

最简单,最优雅的方法是:

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

这将适用于所有平台,并且超级干净。

可以在“ 该bash脚本所在的目录? ”中找到更多详细信息。


不错的解决方案,但是如果文件被符号链接,则将无法使用。
ruuter

16

我会用这样的东西:

# 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

这是真实的!也很简单shdirname "$0"基于简单解决方案的问题:如果脚本位于中$PATH并且在没有路径的情况下调用,则它们将给出错误的结果。
Notinlist

@Notinlist并非如此。如果通过找到脚本PATH$0将包含绝对文件名。如果使用包含的相对或绝对文件名调用脚本/$0则将包含该文件名。
尼尔·梅

不适用于源脚本。
阿米特·奈杜

16

这是一个轻微的修订,以解决电子SATIS和3bcdnlklvc04a中指出,他们的答案

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

在他们列出的所有情况下,这仍然应该起作用。

由于konsolebox,这将防止popd出现故障之后pushd


这非常适合获取“真实的”目录名,而不仅仅是符号链接的名称。谢谢!
杰伊·泰勒

1
更好SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox 2014年

@konsolebox,您要防御什么?我通常是内联逻辑条件的支持者,但是您在推送中看到的具体错误是什么?我想找到一种直接处理它的方法,而不是返回一个空的SCRIPT_DIR。
Fuwjax 2015年

@Fuwjax自然的做法是避免popdpushd失败的情况下(即使在极少数情况下)进行操作。如果pushd失败,您认为的值是SCRIPT_DIR多少?动作可能会有所不同,具体取决于看似合乎逻辑的内容或一个用户可能喜欢的内容,但可以肯定,这样做popd是错误的。
konsolebox

pushd popd只需将其丢弃并使用命令替换中的cd+ 即可避免所有这些危险pwdSCRIPT_DIR=$(...)
阿米特·奈杜

16

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

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

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


2
除非脚本的来源是。或“源”,在这种情况下,它将仍然是源于它的脚本,或者,如果从命令行,则是“ -bash”(tty登录)或“ bash”(通过“ bash -l”调用)或“ / bin / bash”(作为交互式非登录shell调用)
osirisgothra,2015年

我在dirname通话周围添加了第二对报价。如果目录路径包含空格,则为必需。
user1338062

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

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

scriptdir=`dirname "$PRG"`

我没有在不同的系统上进行过测试。但是对我来说,这种解决方案至少可以立即在Ubuntu上运行!
Natus Drew

$0不适用于源脚本
Amit Naidu

13

$_ 值得一提的是 $0。如果您正在从Bash运行脚本,则可以将接受的答案缩短为:

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

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


4
如果您source.脚本破坏了它。在这种情况下,$_将包含您在之前运行的最后一个命令的最后一个参数.$BASH_SOURCE每次都能工作。
clacke 2014年

11

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

  • 绝对路径或相对路径
  • 文件和目录软链接
  • 调用为scriptbash scriptbash -c scriptsource 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"

/dev/pts/30在Ubuntu 14.10 Desktop上使用bash。
Dan Dascalescu 2015年

@DanDascalescu使用单线?还是底部的完整代码段?您是否给它添加了任何棘手的路径名?
billyjmc 2015年

一条线加上另一条线路echo $resolved,我保存为dchmod +x d./d
Dan Dascalescu 2015年

@DanDascalescu脚本中的第一行必须是#!/bin/bash
billyjmc

10

尝试使用:

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

1
我只想知道,为什么这种方式不好?对我来说,这似乎并不坏且正确。谁能解释为什么它被否决了?
寿雅

7
realpath不是标准实用程序。
史蒂夫·本内特

2
在Linux上,realpath是标准实用程序(是GNU coreutils软件包的一部分),但它不是内置的bash(即bash本身提供的功能)。如果您正在运行Linux,则此方法可能会起作用,尽管我将用代替$0${BASH_SOURCE[0]}以便该方法可在任何地方(包括函数中)使用。
Doug Richardson

3
此答案中的操作顺序是错误的。您需要首先解析符号链接,然后再执行操作,dirname因为的最后一部分$0可能是符号链接,该符号链接指向的文件与符号链接本身不在同一目录中。此答案中描述的解决方案仅获取其符号链接存储的目录的路径,而不是目标的目录。此外,此解决方案缺少引号。如果路径包含特殊字符,则它将不起作用。
hagello 2015年

3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko,

9

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

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

因为诸如realpath或的命令readlink可能不可用(取决于操作系统)。

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

另外,您可以在bash中尝试以下功能:

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

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

有关:


请解释更多有关realpath函数的信息。
克里斯(Chris

1
@Chris realpath函数带有1个参数。如果参数已经具有绝对路径,则按原样打印,否则打印$PWD+文件名(不带./前缀)。
kenorb

当脚本被符号链接时,您的交叉兼容解决方案不起作用。
Jakub Jirutka 2015年

9

我相信我有这个。我来晚了,但是我想如果有人碰到这个话题,有人会很感激。评论应说明:

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

8

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

文件夹和文件:

    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



我认为我的回答是可以的,因为很难找到一个简单的工作版本。在这里,您可以使用喜欢的代码,例如cd + pwd,dirname + realpath或dirname + readlink。我不确定所有部分是否都存在,大多数答案是否复杂和繁重。在这里,您可以选择想要使用的代码。至少请不要将来删除它:D
User8461

8

这在bash-3.2中有效:

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

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

这可以通过以下方法(在里面A)解决:

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

无论用户身在何处或如何调用脚本,都将始终有效。


3
关键which是有争议的。typehash和其他内建函数在bash中做得更好。which有点可移植,尽管它which与tcsh等其他shell 确实不一样,但将其作为内置函数使用。
恢复莫妮卡2014年

“总是”?一点也不。which作为外部工具,您没有理由相信它的行为与父shell相同。
2014年

7

我认为最好的紧凑型解决方案是:

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

除了Bash之外,没有任何其他依赖。使用的dirnamereadlinkbasename最终将导致兼容性问题,所以他们最好的,如果在所有可能避免。


2
您可能应该在其中添加斜线:"$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )"。如果没有,您将在根目录上遇到问题。另外,为什么还要使用echo?
konsolebox 2014年

dirname并且basenamePOSIX是标准化的,那么为什么要避免使用它们呢?友情链接:dirnamebasename
myrdd

阻止两个额外的流程分支并坚持使用内置的shell可能是原因之一。
阿米特·奈杜
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.