5658

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

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

$ ./application
4

72 回答 72

7469
#!/usr/bin/env bash

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

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

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

#!/usr/bin/env 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 )

最后一个将与别名、、、符号链接等的任意组合一起source使用bash -c

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

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

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

#!/usr/bin/env 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'
于 2008-10-29T08:36:45.740 回答
1067

使用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
于 2008-09-12T20:49:28.600 回答
603

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
于 2009-09-26T20:38:02.093 回答
195
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以完整路径给出,无论它如何调用。

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

于 2008-10-07T16:12:14.120 回答
125

您可以使用$BASH_SOURCE

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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

于 2008-09-12T20:50:55.930 回答
123

简短的回答:

`dirname $0`

或(最好):

$(dirname "$0")
于 2008-12-03T12:57:40.373 回答
96

Here is an easy-to-remember script:

DIR=$(dirname "${BASH_SOURCE[0]}")   # Get the directory name
DIR=$(realpath "${DIR}")    # Resolve its full path if need be
于 2018-11-07T04:30:59.150 回答
94

This should do it:

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

This works with symlinks and spaces in path.

Please see the man pages for dirname and realpath.

Please add a comment on how to support MacOS. I'm sorry I can verify it.

于 2016-02-12T23:40:21.017 回答
73

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

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

我建议如下:

#!/bin/bash

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

echo "Directory is $directory"

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

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

虽然只是

cd `dirname $0`

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

于 2008-09-15T19:55:06.053 回答
42

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

./script

/usr/bin/script

script

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

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

于 2008-09-13T01:07:11.867 回答
42
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
于 2010-10-07T17:34:23.570 回答
39
$(dirname "$(readlink -f "$BASH_SOURCE")")
于 2011-07-27T07:40:31.753 回答
38

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

DIR=$(cd "$(dirname "$0")"; pwd)
于 2010-12-30T07:20:59.243 回答
29

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

SELF=$(readlink /proc/$$/fd/255)
于 2008-09-16T19:55:33.953 回答
25

这是一个符合 POSIX 的单行:

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

# test
echo $SCRIPT_PATH
于 2013-03-06T19:07:19.720 回答
24

The shortest and most elegant way to do this is:

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

This would work on all platforms and is super clean.

More details can be found in "Which directory is that bash script in?".

于 2019-06-20T22:08:51.397 回答
20
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`
于 2008-09-13T01:28:47.010 回答
19

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

还有很多人认为你是从 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"`"
于 2010-09-09T07:19:22.913 回答
19

Here is the simple, correct way:

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

Explanation:

  • ${BASH_SOURCE[0]} - the full path to the script. The value of this will be correct even when the script is being sourced, e.g. source <(echo 'echo $0') prints bash, while replacing it with ${BASH_SOURCE[0]} will print the full path of the script. (Of course, this assumes you're OK taking a dependency on Bash.)

  • readlink -f - Recursively resolves any symlinks in the specified path. This is a GNU extension, and not available on (for example) BSD systems. If you're running a Mac, you can use Homebrew to install GNU coreutils and supplant this with greadlink -f.

  • And of course dirname gets the parent directory of the path.

于 2016-02-19T20:30:08.713 回答
18

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

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

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

这将防止popd发生故障后pushd。感谢 konsolebox。

于 2010-04-13T22:12:31.037 回答
17

我会使用这样的东西:

# 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
于 2012-11-21T03:57:55.027 回答
17

For systems having GNU coreutils readlink (for example, Linux):

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

There's no need to use BASH_SOURCE when $0 contains the script filename.

于 2014-05-28T07:16:09.843 回答
16

尝试使用:

real=$(realpath "$(dirname "$0")")
于 2012-02-06T10:43:18.737 回答
14

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

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

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

于 2011-09-16T19:05:51.450 回答
14

Summary:

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

Details:

How to obtain the full file path, full directory, and base filename of any script being run OR sourced...

...even when the called script is called from within another bash function!

For many cases, all you need to acquire is the full path to the script you just called. This can be easily accomplished using realpath. Note that realpath is part of GNU coreutils. If you don't have it already installed (it comes default on Ubuntu), you can install it with sudo apt update && sudo apt install coreutils.

get_script_path.sh (for the latest version of this script, see get_script_path.sh in my eRCaGuy_hello_world repo):

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

IMPORTANT note on nested source calls: if "${BASH_SOURCE[-1]}" above doesn't give you quite what you want, try using "${BASH_SOURCE[0]}" instead. The first (0) index gives you the first entry in the array, and the last (-1) index gives you the last last entry in the array. Depending on what it is you're after, you may actually want the first entry. I discovered this to be the case when I sourced ~/.bashrc with . ~/.bashrc, which sourced ~/.bash_aliases with . ~/.bash_aliases, and I wanted the realpath (with expanded symlinks) to the ~/.bash_aliases file, NOT to the ~/.bashrc file. Since these are nested source calls, using "${BASH_SOURCE[0]}" gave me what I wanted: the expanded path to ~/.bash_aliases! Using "${BASH_SOURCE[-1]}", however, gave me what I did not want: the expanded path to ~/.bashrc.

Example command and output:

  1. Running the script:
    ~/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. Sourcing the script with . get_script_path.sh or source get_script_path.sh (the result is the exact same as above because I used "${BASH_SOURCE[-1]}" in the script instead of "$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"
    

If you use "$0" in the script instead of "${BASH_SOURCE[-1]}", you'll get the same output as above when running the script, but this undesired output instead when sourcing the script:

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

And, apparently if you use "$BASH_SOURCE" instead of "${BASH_SOURCE[-1]}", it will not work if the script is called from within another bash function. So, using "${BASH_SOURCE[-1]}" is therefore the best way to do it, as it solves both of these problems! See the references below.

Note that realpath also successfully walks down symbolic links to determine and point to their targets rather than pointing to the symbolic link. If you do NOT want this behavior (sometimes I don't), then add -s to the realpath command above, making that line look like this instead:

# 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]}")"

This way, symbolic links are NOT expanded. Rather, they are left as-is, as symbolic links in the full path.

The code above is now part of my eRCaGuy_hello_world repo in this file here: bash/get_script_path.sh. Reference and run this file for full examples both with and withOUT symlinks in the paths. See the bottom of the file for example output in both cases.

References:

  1. How to retrieve absolute path given relative
  2. taught me about the BASH_SOURCE variable: Unix & Linux: determining path to sourced shell script
  3. taught me that BASH_SOURCE is actually an array, and we want the last element from it for it to work as expected inside a function (hence why I used "${BASH_SOURCE[-1]}" in my code here): Unix & Linux: determining path to sourced shell script
  4. man bash --> search for BASH_SOURCE:

    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]}.

See also:

  1. [my answer] Unix & Linux: determining path to sourced shell script
于 2020-02-10T19:46:22.947 回答
13

这适用于 Bash 3.2:

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

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

这是通过以下(内部A)解决的:

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

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

于 2008-10-14T16:39:03.097 回答
13

These are short ways to get script information:

Folders and files:

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

Using these commands:

    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`

And I got this output:

     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

Also see: https://pastebin.com/J8KjxrPF

于 2018-03-09T12:56:45.277 回答
10

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

  • 绝对路径或相对路径
  • 文件和目录软链接
  • 调用为script, bash script, bash -c script, source 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文件系统,或者您正在尝试查找其他文件的完全解析路径,那么您可能会发现下面的代码很有帮助。这只是对上述单线的轻微修改。如果您正在使用奇怪的目录/文件名,请检查两者的输出并提供ls信息readlinkls输出“简化”路径,代替?换行符之类的东西。

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"
于 2013-04-21T13:40:41.247 回答
9

Try the following cross-compatible solution:

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

As the commands such as realpath or readlink could be not available (depending on the operating system).

Note: In Bash, it's recommended to use ${BASH_SOURCE[0]} instead of $0, otherwise path can break when sourcing the file (source/.).

Alternatively you can try the following function in Bash:

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

This function takes one argument. If argument has already absolute path, print it as it is, otherwise print $PWD variable + filename argument (without ./ prefix).

Related:

于 2013-11-28T12:05:56.390 回答
9

I believe I've got this one. I'm late to the party, but I think some will appreciate it being here if they come across this thread. The comments should explain:

#!/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
于 2013-12-21T17:47:37.047 回答
8

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

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

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

为我工作。

于 2009-01-24T17:01:27.353 回答
7

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

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

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

于 2013-10-08T14:24:13.443 回答
7

You can do that just combining the script name ($0) with realpath and/or dirname. It works for Bash and 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}<-"

The output will be something like this:

# 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 is the name of the script itself

4.4. Special Variable Types

An example: LozanoMatheus/get_script_paths.sh

于 2019-08-26T15:01:25.073 回答
5

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

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

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

于 2010-09-22T23:22:34.757 回答
5

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

script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"
于 2010-10-13T07:42:58.977 回答
5

This worked for me when the other answers here did not:

thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath
于 2017-04-10T21:49:15.017 回答
5

The top response does not work in all cases...

As I had problems with the BASH_SOURCE with the included 'cd' approach on some very fresh and also on less fresh installed Ubuntu 16.04 (Xenial Xerus) systems when invoking the shell script by means of "sh my_script.sh", I tried out something different that as of now seems to run quite smoothly for my purposes. The approach is a bit more compact in the script and is further much lesser cryptic feeling.

This alternate approach uses the external applications 'realpath' and 'dirname' from the coreutils package. (Okay, not anyone likes the overhead of invoking secondary processes - but when seeing the multi-line scripting for resolving the true object it won't be that bad either having it solve in a single binary usage.)

So let’s see one example of those alternate solution for the described task of querying the true absolute path to a certain file:

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

But preferably you should use this evolved version to also support the use of paths with spaces (or maybe even some other special characters):

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

Indeed, if you don’t need the value of the SCRIPT variable then you might be able to merge this two-liner into even a single line. But why really shall you spend the effort for this?

于 2019-10-25T16:32:24.617 回答
5

None of the current solutions work if there are any newlines at the end of the directory name - They will be stripped by the command substitution. To work around this you can append a non-newline character inside the command substitution and then strip just that character off:

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

This protects against two very common situations: Accidents and sabotage. A script shouldn't fail in unpredictable ways just because someone, somewhere, did a mkdir $'\n'.

于 2021-04-15T08:48:07.263 回答
4

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

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
于 2009-04-14T16:00:20.273 回答
4

I usually use:

dirname $(which $BASH_SOURCE)
于 2019-05-17T12:09:27.027 回答
4

This is, annoyingly, the only one-liner I've found that works on both Linux and macOS when the executable script is a symlink:

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

or, similarly, using python3 pathlib module:

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

Tested on Linux and macOS and compared to other solutions in this gist: https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371

于 2021-06-20T13:11:28.417 回答
3

$0 is not a reliable way to get the current script path. For example, this is my .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

So please use BASH_SOURCE instead.

于 2017-02-04T08:56:21.853 回答
3

Here's a command that works under either Bash or zsh, and whether executed stand-alone or sourced:

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

How it works

The zsh current file expansion: ${(%):-%x}

${(%):-%x} in zsh expands to the path of the currently-executing file.

The fallback substitution operator :-

You know already that ${...} substitutes variables inside of strings. You might not know that certain operations are possible (in both Bash and zsh) on the variables during substitution, like the fallback expansion operator :-:

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

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

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

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

The %x prompt escape code

Next, we'll introduce prompt escape codes, a zsh-only feature. In zsh, %x will expand to the path of the file, but normally this is only when doing expansion for prompt strings. To enable those codes in our substitution, we can add a (%) flag before the variable name:

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

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

% cd apath
% source test.sh
test.sh

An unlikely match: the percent escape and the fallback

What we have so far works, but it would be tidier to avoid creating the extra fpath variable. Instead of putting %x in fpath, we can use :- and put %x in the fallback string:

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

% source test.sh
test.sh

Note that we normally would put a variable name between (%) and :-, but we left it blank. The variable with a blank name can't be declared or set, so the fallback is always triggered.

Finishing up: what about print -P %x?

Now we almost have the directory of our script. We could have used print -P %x to get the same file path with fewer hacks, but in our case, where we need to pass it as an argument to dirname, that would have required the overhead of a starting a new subshell:

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

% source apath/test.sh
apath
apath

It turns out that the hacky way is both more performant and succinct.

于 2019-05-22T19:57:49.640 回答
2

This solution applies only to Bash. Note that the commonly supplied answer ${BASH_SOURCE[0]} won't work if you try to find the path from within a function.

I've found this line to always work, regardless of whether the file is being sourced or run as a script.

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

If you want to follow symlinks use readlink on the path you get above, recursively or non-recursively.

Here's a script to try it out and compare it to other proposed solutions. Invoke it as source test1/test2/test_script.sh or bash 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]}
}

The reason the one-liner works is explained by the use of the BASH_SOURCE environment variable and its associated 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 doesn't have any 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.

[Source: Bash manual]

于 2014-08-31T22:10:46.977 回答
2

This is how I work it on my scripts:

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

This will tell you which directory the Launcher (current script) is being executed from.

于 2018-04-17T00:03:22.763 回答
2

If your Bash script is a symlink, then this is the way to do it:

#!/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 is the directory that contains your Bash script (the original file, not the symlink). I swear to God this works, and it is the only way I know of doing this properly.

于 2018-06-01T02:52:46.303 回答
2

The following will return the current directory of the script

  • works if it's sourced, or not sourced
  • works if run in the current directory, or some other directory.
  • works if relative directories are used.
  • works with bash, not sure of other shells.
/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

test.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"
于 2019-10-08T12:37:16.200 回答
2

Python was mentioned a few times. Here is the JavaScript (i.e., Node.js) alternative:

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

echo $baseDir
于 2019-12-26T08:34:40.640 回答
2

One advantage of this method is that it doesn't involve anything outside Bash itself and does not fork any subshell neither.

First, use pattern substitution to replace anything not starting with / (i.e., a relative path) with $PWD/. Since we use a substitution to match the first character of $0, we also have to append it back (${0:0:1} in the substitution).

Now we have a full path to the script; we can get the directory by removing the last / and anything the follows (i.e., the script name). That directory can then be used in cd or as a prefix to other paths relative to your script.

#!/bin/bash

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

cd "$DIR"

If your script may be sourced rather than executed, you can of course replace $0 with ${BASH_SOURCE[0]}, such as:

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

This will work for executable scripts too. It's longer, but more polyvalent.

于 2020-12-23T20:55:00.727 回答
2

I tried the followings with 3 different executions.

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 is basically the same with ${BASH_SOURCE[0]}.

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

Only $(realpath $BASH_SOURCE) seems to be reliable.

于 2021-01-20T03:20:25.963 回答
2

Most answers either don't handle files which are symlinked via a relative path, aren't one-liners or don't handle BSD (Mac). A solution which does all three is:

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

First, cd to bash's conception of the script's directory. Then readlink the file to see if it is a symlink (relative or otherwise), and if so, cd to that directory. If not, cd to the current directory (necessary to keep things a one-liner). Then echo the current directory via pwd.

You could add -- to the arguments of cd and readlink to avoid issues of directories named like options, but I don't bother for most purposes.

You can see the full explanation with illustrations here:

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

于 2021-04-18T13:32:21.303 回答
0

我通常这样做:

LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh
于 2009-01-24T16:32:59.127 回答
0

这是一个纯 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/.
于 2013-02-14T18:14:23.373 回答
0
于 2013-11-26T21:10:31.463 回答
0

The below stores the script's directory path in the dir variable.

(It also tries to support being executed under Cygwin in Windows.)

And at last it runs the my-sample-app executable with all arguments passed to this script using "$@":

#!/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" "$@"
于 2019-03-26T07:50:18.640 回答
0

I think the simplest answer is a parameter expansion of the original variable:

#!/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]%/*}"

It should produce output like:

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

opt2; simple answer  : /var/tmp

The variable/parameter expansion ${BASH_SOURCE[0]%/*}" seems much easier to maintain.

于 2020-06-15T20:35:48.657 回答
-1
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
于 2009-11-29T11:44:13.250 回答
-1

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

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

第一行根据pwdif run from the current path 或dirname如果从其他地方调用的值分配源。

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

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

于 2013-06-09T15:35:27.027 回答
-1

尝试这样的事情:

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
于 2013-10-03T07:35:55.767 回答
-1
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`
于 2014-05-24T15:47:25.460 回答
-1

No forks (besides subshell) and can handle "alien" pathname forms like those with newlines as some would claim:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")
于 2014-07-03T04:58:44.697 回答
-1

The key part is that I am reducing the scope of the problem: I forbid indirect execution of the script via the path (as in /bin/sh [script path relative to path component]).

This can be detected because $0 will be a relative path which does not resolve to any file relative to the current folder. I believe that direct execution using the #! mechanism always results in an absolute $0, including when the script is found on the path.

I also require that the pathname and any pathnames along a chain of symbolic links only contain a reasonable subset of characters, notably not \n, >, * or ?. This is required for the parsing logic.

There are a few more implicit expectations which I will not go into (look at this answer), and I do not attempt to handle deliberate sabotage of $0 (so consider any security implications). I expect this to work on almost any Unix-like system with a Bourne-like /bin/sh.

#!/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
)
于 2015-10-09T08:16:09.547 回答
-1

Look at the test at bottom with weird directory names.

To change the working directory to the one where the Bash script is located, you should try this simple, tested and verified with shellcheck solution:

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

The test:

$ 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
于 2016-03-31T00:49:14.143 回答
-2

Based on this answer, I suggest the clarified version that gets SCRIPT_HOME as the containing folder of any currently-running Bash script:

s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`
echo $SCRIPT_HOME
于 2016-10-24T11:45:41.723 回答
-2

I want to comment on the previous answer up there (How can I get the source directory of a Bash script from within the script itself?), but don't have enough reputation to do that.

I found a solution for this two years ago on Apple's documentation site: https://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/AdvancedTechniques/AdvancedTechniques.html. And I stuck to this method afterwards. It cannot handle soft link, but otherwise works pretty well for me. I'm posting it here for any who needs it and as a request for comment.

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

As shown by the code, after you get the absolute path of the script, then you can use the dirname command to get the path of the directory.

于 2019-08-15T05:53:26.790 回答
-2

This is what I crafted throughout the years to use as a header on my Bash scripts:

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

At this point, your variables SW, SWDIR, and SWBIN contain what you need.

于 2019-08-27T00:31:07.953 回答
-3

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

cd $(dirname $(which $0) )

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

DIR=$(/usr/bin/pwd)
于 2009-02-12T15:06:33.450 回答
-3

This one-liner works on Cygwin even if the script has been called from Windows with bash -c <script>:

set mydir="$(cygpath "$(dirname "$0")")"
于 2017-02-03T13:57:28.917 回答
-3

There is no 100% portable and reliable way to request a path to a current script directory. Especially between different backends like Cygwin, MinGW, MSYS, Linux, etc. This issue was not properly and completely resolved in Bash for ages.

For example, this could not be resolved if you want to request the path after the source command to make nested inclusion of another Bash script which is in turn use the same source command to include another Bash script and so on.

In case of the source command, I suggest to replace the source command with something like this:

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 ...
}

From now on, the use of include(...) is based on previous CURRENT_SCRIPT_DIR in your script.

This only works when you can replace all source commands by include command. If you can't, then you have no choice. At least until developers of the Bash interpreter make an explicit command to request the current running script directory path.

My own closest implementation to this: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/bash_tacklelib
https://github.com/andry81/tacklelib/tree/trunk/bash/tacklelib/bash_tacklelib

(search for the tkl_include function)

于 2019-05-07T22:06:25.190 回答
-5

The chosen answer works very well. I'm posting my solution for anyone looking for shorter alternatives that still addresses sourcing, executing, full paths, relative paths, and symlinks. Finally, this will work on macOS, given that it cannot be assumed that GNU's coreutils' version of readlink is available.

The gotcha is that it's not using Bash, but it is easy to use in a Bash script. While the OP did not place any constraints on the language of the solution, it's probably best that most have stayed within the Bash world. This is just an alternative, and possibly an unpopular one.

PHP is available on macOS by default, and installed on a number of other platforms, though not necessarily by default. I realize this is a shortcoming, but I'll leave this here for any people coming from search engines, anyway.

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
于 2019-01-10T16:33:48.727 回答
-6

Keep it simple.

#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir
于 2020-02-24T17:58:05.090 回答
-7
FOLDERNAME=${PWD##*/}

That is the quickest way I know.

于 2014-11-14T16:35:04.463 回答