从脚本本身中获取bash脚本的源目录

 2019-04-14 

Get the source directory of a Bash script from within the script itself

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

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

1
$ ./application


1
2
3
#!/bin/bash

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

是一个有用的一行程序,它将为您提供脚本的完整目录名,无论从何处调用脚本。

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

1
2
3
4
5
6
7
8
9
#!/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等任意组合使用。

注意:如果您在运行此代码段之前将cx1〔2〕转到其他目录,那么结果可能不正确!

另外,注意$CDPATHgotchas和stderr输出副作用,如果用户聪明地重写了cd将输出重定向到stderr(包括转义序列,例如在mac上调用update_terminal_cwd >&2时)。在你的cd命令末尾加上>/dev/null 2>&1将考虑这两种可能性。

要了解其工作原理,请尝试运行更详细的表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/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'"

它将打印如下内容:

1
2
3
4
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"

1
2
3
#!/bin/bash
echo"The script you are running has basename `basename"$0"`, dirname `dirname"$0"`"
echo"The present working directory is `pwd`"

如果您没有从包含脚本的目录运行脚本,那么单独使用pwd将不起作用。

1
2
3
4
5
6
7
8
9
[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(脚本名)变量的文件名路径:

1
dirname"$0"

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

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

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

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

下面是一个演示这些内容的脚本,whatdir.sh:

1
2
3
4
5
6
#!/bin/bash
echo"pwd: `pwd`"
echo"\$0: $0"
echo"basename: `basename $0`"
echo"dirname: `dirname $0`"
echo"dirname/readlink: $(dirname $(readlink -f $0))"

使用相对路径在home dir中运行此脚本:

1
2
3
4
5
6
>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

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

1
2
3
4
5
6
>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

正在更改目录:

1
2
3
4
5
6
7
>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

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

1
2
3
4
5
6
7
>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat


1
2
3
4
5
6
7
8
9
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脚本本身是一个相对的symlink,那么您希望跟踪它并返回链接到脚本的完整路径:

1
2
3
4
5
6
7
8
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是全路径给出的,不管它是如何调用的。只要确保在脚本的开头找到这个。

此注释和代码是gpl2.0或更高版本或cc-sa 3.0(creativecommons share like)或更高版本下的copyleft、可选许可证。(c)2008。版权所有。没有任何形式的保证。有人警告过你。http://www.gnu.org/licenses/gpl-2.0.txthttp://creativecommons.org/licenses/by-sa/3.0/18EEDFE1C99DF68DC94D4A947112A71AAA8E1E9E36ACF421B9463DD2BAA02906D0D6656


简短回答:

1
`dirname $0`

或者(最好):

1
$(dirname"$0")


您可以使用$bash_source

1
2
3
#!/bin/bash

scriptdir=`dirname"$BASH_SOURCE"`

请注意,您需要使用!/垃圾桶/巴什而不是!/bin/sh,因为它是bash扩展


应该这样做:

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

使用路径中的符号链接和空格。有关dirname和readlink,请参阅手册页。

编辑:

从评论跟踪来看,它似乎不适用于Mac操作系统。我不知道为什么。有什么建议吗?


pwd可以用来查找当前的工作目录,dirname可以查找特定文件的目录(运行的命令是$0,所以dirname $0应该给你当前脚本的目录)。

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

我建议如下:

1
2
3
4
5
6
7
#!/bin/bash

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

echo"Directory is $directory"

这样,就得到了一个绝对目录,而不是相对目录。

由于脚本将在单独的bash实例中运行,因此以后不需要恢复工作目录,但是如果您出于某种原因想在脚本中进行更改,可以在更改目录之前轻松地将pwd的值赋给一个变量,以备将来使用。

虽然只是

1
cd `dirname $0`

解决了问题中的具体场景,我发现有绝对路径更有用。


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

1
2
3
4
5
./script

/usr/bin/script

script

在第一和第三种情况下,$0没有完整的路径信息。在第二和第三阶段,PWD不工作。以第三种方式获取dir的唯一方法是通过路径运行并找到正确匹配的文件。基本上,代码必须重做操作系统的功能。

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


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


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

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

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

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

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


下面是一个符合POSIX的一行程序:

1
2
3
4
SCRIPT_PATH=`dirname"$0"`; SCRIPT_PATH=`eval"cd "$SCRIPT_PATH" && pwd"`

# test
echo $SCRIPT_PATH


我厌倦了一遍又一遍地来这一页,把这一行字复制粘贴在我接受的答案里。但问题是不容易理解和记住。

下面是一个易于记忆的脚本:

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


以下是简单、正确的方法:

1
2
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,则可以使用自制来安装GNU coreutils,并将其替换为greadlink -f

  • 当然,dirname会得到路径的父目录。


我试过每一个都没用。其中一个离得很近,但有一个很小的bug把它弄坏了;他们忘了用引号把路径包起来。

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

尝试此目录的大小:

/var/no one/thought/about spaces being/in a directory/name/这是您的文件。文本

无论您如何运行或在何处运行,这都能使它正确运行。

1
2
3
4
5
#!/bin/bash
echo"pwd: `pwd`"
echo"\$0: $0"
echo"basename: `basename"$0"`"
echo"dirname: `dirname"$0"`"

因此,为了使它真正有用,下面介绍如何更改到正在运行的脚本的目录:

1
cd"`dirname"$0"`"

希望有所帮助


我会用这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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


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

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

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

编辑:由于KonsoleBox,防止在Pushd失败后弹出


1
2
3
4
5
6
7
8
9
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname"$PRG"`

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

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

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

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

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

上面应该说明您在Linux上定位当前运行的脚本的需要,但是如果您没有可以使用的proc文件系统,或者如果您试图定位其他文件的完全解析路径,那么您可能会发现下面的代码很有用。这只是上面一个内衬的一个小改动。如果你在玩奇怪的目录/文件名,那么用lsreadlink检查输出是很有用的,因为ls将输出"简化"路径,用?替换换行符。

1
2
3
4
5
6
7
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"
'
"$absolute_path"


值得一提的是,$作为0美元的替代品。如果您运行的是bash脚本,那么可以将接受的答案缩短为:

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

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


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

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

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


尝试使用:

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


所以…我想我有这个。参加晚会迟到了,但我想有些人会很感激他们在这里遇到了这个问题。评论应该解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/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:
\t$argv ->
\t$argv
"
;}
dangling(){ printf"Broken symlink:
\t$argv ->
\t"
$(readlink"$argv")"
"
;}
errnoent(){ printf"No such file:"$@"
"
;} # 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"
$@
"; exit 0; else printf"$(pwd)/$(basename"$@")
"; 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")
"; exit 0                 ;;
          *) link="
$newlink" && pathfull"$link"                           ;;
   esac
 done
 printf"
$(pwd)/$(basename"$newlink")
"
}

## 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"

\033[3mfrom/as: \033[4m$0\033[0m

\033[1mUSAGE:\033[0m  "
 printf"
\033[4m$scriptname\033[24m [ link | file | dir ]

        "
 printf"
Recursive readlink for the authoritative file, symlink after"
 printf"
symlink.


         \033[4m$scriptname\033[24m

       "
 printf"
From within an invocation of a script, locate the script's"
 printf"own file
         (no matter where it has been linked or"
 printf"from where it is being called).

"

else pathfull"$@"
fi

Try the following cross-compatible solution:

1
CWD="$(cd -P --"$(dirname --"$0")" && pwd -P)"

由于realpathreadlink命令并不总是可用(取决于操作系统),因此${BASH_SOURCE[0]}仅在bash shell中可用。

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

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

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

相关:

  • 如何将当前工作目录设置为脚本的目录?
  • 使用OSX的bash脚本绝对路径
  • bash脚本获取完整路径的可靠方法?


嗯,如果在路径basename&dirname中不打算剪切它走这条路很难(如果父级没有导出路径呢!).但是,shell必须有一个打开的脚本句柄,并且猛击手柄是255。

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

为我工作。


这在bash-3.2中有效:

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

下面是它的用法示例:

假设您有一个~/bin目录,它在$path中。在这个目录中有脚本A。它源于script~/bin/lib/b。您知道所包含的脚本相对于原始脚本(子目录lib)的位置,但不知道它相对于用户当前目录的位置。

这可以通过以下方法解决(在a中):

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

不管用户在哪里,也不管他如何调用脚本,这总是有效的。


总结许多答案:

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

使用命令

1
2
3
4
5
6
7
8
9
10
11
    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`

输出:

1
2
3
4
5
6
7
8
9
10
11
     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


当这里的其他答案没有:

1
2
3
thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath


这是我找到的唯一可靠的方法:

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


这些都不适用于由finder在OSX中启动的bash脚本-我最终使用了:

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

不漂亮,但能完成任务。


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

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

除了巴什什么都不依赖。使用dirnamereadlinkbasename最终会导致兼容性问题,因此最好尽可能避免使用。


使用readlink的组合来规范化名称(如果是symlink,还可以跟踪它的源代码)和dirname来提取目录名:

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


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

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

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

1
2
3
4
/home/puchuu/.xprofile  
/home/puchuu/.xprofile
-bash  
/home/puchuu/.xprofile

所以请使用BASH_SOURCE


如果您的bash脚本是一个符号链接,那么这就是实现它的方法

1
2
3
4
5
6
7
#!/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脚本(原始文件,而不是symlink)的目录。我向上帝发誓这是我所知道的唯一正确的方法。


我通常这样做:

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

这就是我在脚本上的工作方式:pathvar="$( cd"$( dirname $0 )" && pwd )"这将告诉您从哪个目录执行启动器(当前脚本)。


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

…嗯,正如你所看到的,在一些帮助下,我找到了一个非常简单和强大的解决方案:

我可以将函数作为一种信令变量传递,并根据需要将结果函数参数的$1名称与eval的任何显式用法取消引用,并且在函数例程完成时,我使用eval和反斜杠引用技巧来分配我的信令变量,而无需K。现在它的名字。

完全公开,…[我找到了这个消息的变量部分]在Rich的sh技巧中,我还摘录了他页面的相关部分,在我自己的答案摘录下面。

…摘录:…

虽然还没有严格的posix,但realpath自2012年以来就是GNU的核心应用程序。完全公开:在我在info coreutilstoc中注意到它之前,我从未听说过它,并立即想到了(相关的)问题,但使用下面所演示的功能应该是可靠的,(很快就可能?)我希望能有效率向调用者提供绝对来源的$0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% _abs_0() {
> o1="${1%%/*}"; ${o1:="${1}
<hr><P>下面将脚本的目录路径存储在<wyn>dir</wyn>变量中</P><P>(它还试图支持使用<wyn>Windows php</wyn><wyn>Cygwin</wyn>中执行)</P><P>最后运行<wyn>my-sample-app</wyn>可执行文件,所有参数都使用<wyn>"$@"</wyn>传递给这个脚本。</P>[cc lang="bash"]#!/usr/bin/env sh

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

if [ -d /proc/cygdrive ]; then
    case $(which php) in
        $(readlink -n /proc/cygdrive)/*)
            # We are in Cygwin using Windows php, so the path must be translated
            dir=$(cygpath -m"$dir");
            ;;
    esac
fi

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

另一个赞美所有其他优秀答案的选择

$(cd"$(dirname"${BASH_SOURCE[0]}")"; pwd)"


1
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`

讨论到很晚,但请尝试以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function get_realpath() {

if [[ -f"$1" ]]
then
    # file *must* exist
    if cd"$(echo"${1%/*}")" &>/dev/null
    then
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else
        # file *must* be local
        local tmppwd="$PWD"
    fi
else
    # 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并且没有依赖性。也适合跨平台使用。因此,对于上面的示例,在脚本中,您可以简单地:

1
2
3
4
5
6
7
8
source '/path/to/realpath-lib'

get_dirname"$0"

if (( $? )) # true when non-zero.
then
    exit 1 # failure
fi

这就是全部!


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

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

如果从当前路径运行,则第一行根据EDOCX1的值(20)分配源;如果从其他位置调用,则根据dirname分配源。

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

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


(注:这个答案经过了许多修改,因为我在原来的基础上做了改进。最后一次修订时,还没有人发表评论或投票。)

为了我自己的利益,我添加了这个答案——记住它并收集评论——和其他人一样。答案的关键部分是我正在缩小问题的范围:我禁止通过路径(如/bin/sh[相对于路径组件的脚本路径])间接执行脚本。这是可以检测到的,因为$0将是一个相对路径,不会解析为与当前文件夹相关的任何文件。我相信直接执行使用"!"。机制总是产生绝对值$0,包括在路径上找到脚本时。我还要求路径名和符号链接链上的任何路径名只包含合理的字符子集,特别是不包含''、'>'、'*'或'?'。这是解析逻辑所必需的。还有一些更隐含的期望,我不会去做(见前面的答案),我不会试图处理故意破坏$0(所以考虑任何安全影响)。我希望这可以在几乎所有类似于Unix的系统上使用类似bourne的/bin/sh。

欢迎评论和建议!

<1>https://stackoverflow.com/a/4794711/213180

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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
)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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


没有分叉(除了子shell),并且可以处理"外星人"路径名表单,如一些人所说的带有新行的表单:

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

这个解决方案只适用于bash。注意,如果您试图从函数中找到路径,那么通常提供的答案${BASH_SOURCE[0]}将不起作用。

我发现这一行总是有效的,不管文件是源文件还是作为脚本运行。

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

如果您想使用symlinks,可以在上面的路径上使用readlink,可以是递归的,也可以是非递归的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#
# 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

An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.

FUNCNAME

An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is"main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.

[来源:bash手册]


看看底部的测试,上面有奇怪的目录名。

要将工作目录更改为bash脚本所在的目录,您应该尝试使用shellcheck解决方案进行简单、测试和验证:

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

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ 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
\v\f
\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
\v\f
\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
\v\f
\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf"
SUCCESS
"""
SUCCESS
$ *symlink/application && printf"
SUCCESS
"""
SUCCESS

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

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

1
2
3
ME=`type -p $0`
MDIR="${ME%/*}"
WORK_DIR=$(cd $MDIR && pwd)


选择的答案很有效。我要发布我的解决方案,供任何寻找较短的替代方案的人使用,这些替代方案仍然可以解决寻源、执行、完整路径、相对路径和符号链接等问题。最后,这将在MacOS上工作,因为不能假定GNU的coreutils版本的readlink可用。

关键是它不使用bash,但在bash脚本中很容易使用。虽然OP没有对解决方案的语言设置任何限制,但大多数人最好还是留在bash世界中。这只是一个替代方案,而且可能不受欢迎。

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

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


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

1
cd $(dirname $(which $0) )

之后,如果您真的想知道自己在哪里运行,请运行下面的命令。

1
DIR=$(/usr/bin/pwd)


这一行程序在cygwin上工作,即使脚本是从带有bash -c 的Windows中调用的:

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

1
FOLDERNAME=${PWD##*/}

这是我知道的最快的方法