Unix shell脚本找出脚本文件位于哪个目录?


510

基本上,我需要使用与Shell脚本文件位置相关的路径来运行脚本,如何将当前目录更改为与脚本文件所在的目录相同的目录?


12
那真的是重复的吗?这个问题是关于“ unix shell脚本”的,另一个是关于Bash的。
michaeljt 2015年

2
@BoltClock:该问题未正确关闭。链接的问题与Bash有关。这个问题是关于Unix Shell编程的。请注意,接受的答案完全不同!
Dietrich Epp

@ Dietrich Epp:你是对的。似乎询问者选择了可接受的答案,并添加了[bash]标签(可能是对此的回应),导致我将其标记为重复,以响应标记。
BoltClock


Answers:


566

在Bash中,您应该像这样获得所需的东西:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

17
如果您通过其他目录中的符号链接调用了脚本,则此方法将无效。要完成这项工作,您还需要使用readlink(请参见下面的“答案”)
AndrewR

44
在bash中,使用$BASH_SOURCE来代替,更安全$0,因为$0它并不总是包含被调用脚本的路径,例如“采购”脚本时。
mklement0

2
$BASH_SOURCE是Bash特定的,问题通常是关于shell脚本的。
阮国阳

7
@auraham:CUR_PATH=$(pwd)pwd返回当前目录(不必是脚本的父目录)!
安德烈亚斯·迪特里希

5
我尝试使用推荐的方法@ mklement0 $BASH_SOURCE,它返回了我需要的东西。我的脚本正在从另一个脚本中调用,并在$0返回.$BASH_SOURCE返回正确的子目录(在我的情况下scripts)。
David Rissato Cruz

401

原始帖子包含解决方案(忽略响应,它们不会添加任何有用的信息)。有趣的工作由提到的readlink带有option的unix命令完成-f。当绝对路径和相对路径都调用脚本时有效。

对于bash,sh,ksh:

#!/bin/bash 
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH

对于tcsh,csh:

#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

另请参阅:https : //stackoverflow.com/a/246128/59087


12
注意:并非所有系统都有readlink。这就是为什么我建议使用push / popd(bash内置)的原因。
docwhat 2011年

23
-f选项readlink确实在OS X(狮子),并可能BSD不同的东西。stackoverflow.com/questions/1055671/...
Ergwun

9
澄清@Ergwun的评论:-fOS X完全不支持(从Lion开始);在那里,您可以放弃-f解决最多一个间接级别的问题,例如pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")",或者可以滚动自己的递归symlink-following脚本,如链接文章中所示。
mklement0

2
我仍然不明白,为什么OP需要绝对路径。报告“。” 如果您想访问相对于脚本路径的文件并且您调用了./myscript.sh之类的脚本,应该可以正常工作
Stefan Haberl

10
@StefanHaberl我认为如果您在当前工作目录与脚本位置不同的sh /some/other/directory/script.sh)情况下运行脚本,则将是一个问题(例如,在这种情况下.将是您的密码,而不是/some/other/directory
Jon z

51

早先对一个答案的评论说了这一点,但是很容易在所有其他答案中错过。

使用bash时:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Bash参考手册,5.2 Bash变量


3
只有这一个可以在环境路径中与脚本一起使用,投票最多的不起作用。谢谢!
七月

您应该改用dirname "$BASH_SOURCE"$ BASH_SOURCE中的空格。
Mygod '18年

1
根据工具ShellCheck,更清晰的目录显示方式为:“ $(dirname” $ {BASH_SOURCE {0}}“))”,因为BASH_SOURCE是一个数组,并且没有下标,默认情况下使用第一个元素。
Adrian M.

1
@AdrianM。,您需要括号而不是括号作为索引:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown

对我没有好处..仅打印一个点(即大概是当前目录)
JL_SO 19'Aug

40

假设您正在使用bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname $0)

echo $current_dir
echo $script_dir

此脚本应打印您所在的目录,然后打印该脚本所在的目录。例如,当/使用中的脚本进行调用时/home/mez/,它将输出

/
/home/mez

请记住,在从命令的输出中分配变量时,将命令包装在$()-中,否则您将无法获得所需的输出。


3
当我从当前目录调用脚本时,这将无法工作。
Eric Wang

1
@EricWang您始终在当前目录中。
ctrl-alt-delor

对我来说,$ current_dir确实是我从中调用脚本的路径。但是,$ script_dir不是脚本的目录,而只是一个点。
迈克尔

31

如果您正在使用bash ...

#!/bin/bash

pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null

echo "${basedir}"

4
您可以将pushd/ 替换为popdcd $(dirname "${0}")cd -以使其在其他外壳上运行(如果它们具有)pwd -L
docwhat 2011年

为什么在这里使用push和popd?
qodeninja 2014年

1
因此,我不必将原始目录存储在变量中。我在函数等中使用了很多模式。它嵌套得很好,很好。
docwhat

无论脚本中是否引用了变量,它仍被存储在内存中(在变量中)。另外,我认为执行push和popd的成本远远超过了在CPU周期和可读性方面节省的在脚本中不创建本地Bash变量的成本。
ingyhere

20

正如Marko所建议的:

BASEDIR=$(dirname $0)
echo $BASEDIR

除非您从脚本所在的同一目录中执行脚本,否则将获得值“。”,否则此方法将起作用。

要解决该问题,请使用:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

现在,您可以在整个脚本中使用变量current_dir来引用脚本目录。但是,这可能仍然存在符号链接问题。


20

回答此问题的最佳答案是:
从内部获取Bash脚本的源目录。

它是:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

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

要了解其工作原理,可以执行以下脚本:

#!/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" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"


10

让我们使其成为POSIX oneliner:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

在包括BSD在内的许多Bourne兼容外壳上进行了测试。

据我所知,我是作者,因此将其置于公共领域。有关更多信息,请参见:https : //www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/


1
如所写,cd: too many arguments如果路径中有空格,则返回$PWD。(一个明显的解决方法,但仅显示实际上有多少个极端情况)
michael

1
会投票。除了@michael的评论,它在路径中带有空格后失败了...是否有解决方法?
Spechter

1
@spechter是的,有一个解决方法。见更新jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
扬Sáreník

@Der_Meister-请更具体。或写(也可能是加密)在jasan.tk电子邮件至jasan
扬Sáreník

2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2(应该显示原始脚本的路径)
Der_Meister

10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"

3
我知道的最可靠的非打击特定方式。
eddygeek

9

如果要获取实际的脚本目录(无论您是使用符号链接还是直接调用脚本),请尝试:

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

这适用于linux和macOS。我看不到有人在这里提起realpath。不确定这种方法是否有任何缺点。

在macOS上,您需要安装coreutils才能使用realpath。例如:brew install coreutils


6

介绍

这个答案纠正了这个线程(由TheMarko编写)的非常破碎但令人震惊的最高投票答案:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

为什么在自己的目录上不能使用目录名“ $ 0”?

目录名$ 0仅在用户以非常特定的方式启动脚本时才有效。我能够找到几种情况,其中该答案失败并导致脚本崩溃。

首先,让我们了解此答案的工作原理。他通过执行来获取脚本目录

dirname "$0"

$ 0代表调用脚本的命令的第一部分(基本上是没有参数的输入命令:

/some/path/./script argument1 argument2

$ 0 =“ / some / path /./ script”

dirname基本上在字符串中找到最后一个/并将其截断。因此,如果您这样做:

  dirname /usr/bin/sha256sum

您将获得:/ usr / bin

此示例运行良好,因为/ usr / bin / sha256sum是格式正确的路径,但

  dirname "/some/path/./script"

效果不好,会给您:

  BASENAME="/some/path/." #which would crash your script if you try to use it as a path

假设您与脚本位于同一目录,并使用此命令启动它

./script   

$ 0在这种情况下将是./script和目录名$ 0将给出:

. #or BASEDIR=".", again this will crash your script

使用:

sh script

如果不输入完整路径,还将给出BASEDIR =“。

使用相对目录:

 ../some/path/./script

给出$ 0的目录名:

 ../some/path/.

如果您位于/ some目录中,并且以这种方式调用脚本(请注意,开头没有/,同样是相对路径):

 path/./script.sh

您将获得目录名$ 0的以下值:

 path/. 

和./path /./ script(相对路径的另一种形式)给出:

 ./path/.

basedir $ 0只能工作的两种情况是用户使用sh或touch启动脚本,因为这两种情况都将导致$ 0:

 $0=/some/path/script

这将为您提供可与目录名一起使用的路径。

解决方案

您将考虑并检测上述每种情况,并在出现这种情况时对其进行修复:

#!/bin/bash
#this script will only work in bash, make sure it's installed on your system.

#set to false to not see all the echos
debug=true

if [ "$debug" = true ]; then echo "\$0=$0";fi


#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
                                                                     #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
                                                                     #situation3: different dir but relative path used to launch script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 

if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1

_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
        if [ "$B_2" = "./" ]; then
                #covers ./relative_path/(./)script
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
        else
                #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
        fi
fi

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi

4

这个单行代码告诉您shell脚本在哪里,无论您运行它还是获取它都没有关系。此外,如果是这种情况,它将解析涉及的任何符号链接:

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

顺便说一句,我想您正在使用/ bin / bash


2

如此之多的答案,都似乎是合理的,每个答案都有赞成和反对的目的,并且目标略有不同(可能应该为每一个陈述)。这是另一个解决方案,它满足了一个基本目标,即在所有bash上清晰且可在所有系统上正常工作(不假设bash版本readlinkpwd选项的,并且可以合理地预期实现(例如,解决符号链接是有趣的问题,但通常不是您真正想要的),处理诸如路径中的空格之类的边缘情况,忽略任何错误,并在出现任何问题时使用合理的默认值。

每个组件都存储在一个单独的变量中,您可以单独使用:

# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}      # this script's name
PROG_NAME=${PROG_PATH##*/}       # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"


-4

这应该够了吧:

echo `pwd`/`dirname $0`

根据调用方式和cwd的不同,它看起来可能很丑陋,但是应该可以带您到达需要的位置(或者,如果您关心字符串的外观,可以对其进行调整)。


1
这里的stackoverflow逃逸问题:它肯定应该看起来像这样:`pwd`/`dirname $0`但是在符号链接上可能仍然会失败
Andreas Dietrich 2014年
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.