如何在该脚本中获取Bash脚本所在目录的路径?
我想使用 Bash 脚本作为另一个应用程序的启动器。我想将工作目录更改为 Bash 脚本所在的目录,这样我就可以对该目录中的文件进行操作,如下所示:
$ ./application
#!/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 上调用时) ,请注意$CDPATH
gotchas和 stderr 输出副作用。在命令末尾update_terminal_cwd >&2
添加将处理这两种可能性。>/dev/null 2>&1
cd
要了解它是如何工作的,请尝试运行这个更详细的表单:
#!/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'
使用dirname "$0"
:
#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"
如果您不是从脚本所在的目录运行脚本,则单独使用pwd
将不起作用。
[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
该dirname
命令是最基本的,只需从$0
(脚本名称)变量解析到文件名的路径:
dirname "$0"
但是,正如matt b所指出的,返回的路径因脚本的调用方式而异。pwd
不做这项工作,因为它只告诉你当前目录是什么,而不是脚本所在的目录。此外,如果执行脚本的符号链接,你将获得一个(可能是相对的)路径链接所在的位置,而不是实际的脚本。
其他一些人提到了该readlink
命令,但最简单的是,您可以使用:
dirname "$(readlink -f "$0")"
readlink
将脚本路径解析为文件系统根目录的绝对路径。因此,任何包含单点或双点、波浪线和/或符号链接的路径都将被解析为完整路径。
这是一个演示每一个的脚本,whatdir.sh
:
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"
使用相对路径在我的主目录中运行此脚本:
>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat
同样,但使用脚本的完整路径:
>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
现在更改目录:
>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
最后使用符号链接执行脚本:
>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
while([ -h "${SCRIPT_PATH}" ]); do cd "$(dirname "$SCRIPT_PATH")";
SCRIPT_PATH=$(readlink "${SCRIPT_PATH}"); done
fi
cd "$(dirname ${SCRIPT_PATH})" > /dev/null
SCRIPT_PATH=$(pwd);
popd > /dev/null
它适用于所有版本,包括
source
”又名.
(点)运算符调用脚本时。$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
以完整路径给出,无论它如何调用。
只需确保在脚本开始时找到它。
您可以使用$BASH_SOURCE
:
#!/bin/bash
scriptdir=`dirname "$BASH_SOURCE"`
请注意,您需要使用#!/bin/bash
而不是#!/bin/sh
因为它是 Bash 扩展。
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
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.
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`
解决了问题中的特定场景,我发现绝对路径更普遍更有用。
我认为这并不像其他人所说的那么容易。 pwd
不起作用,因为当前目录不一定是脚本所在的目录。 $0
也不总是有信息。考虑以下三种调用脚本的方法:
./script
/usr/bin/script
script
第一种和第三种方式$0
没有完整的路径信息。在第二个和第三个,pwd
不起作用。以第三种方式获取目录的唯一方法是遍历路径并找到具有正确匹配的文件。基本上,代码必须重做操作系统所做的事情。
执行您所要求的一种方法是对/usr/share
目录中的数据进行硬编码,并通过其完整路径引用它。无论如何,数据不应该在/usr/bin
目录中,所以这可能是要做的事情。
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
$(dirname "$(readlink -f "$BASH_SOURCE")")
这将获取Mac OS X v10.6.6 (Snow Leopard)上的当前工作目录:
DIR=$(cd "$(dirname "$0")"; pwd)
这是特定于 Linux 的,但您可以使用:
SELF=$(readlink /proc/$$/fd/255)
这是一个符合 POSIX 的单行:
SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
# test
echo $SCRIPT_PATH
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?".
#!/bin/sh
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
PRG=`readlink "$PRG"`
done
scriptdir=`dirname "$PRG"`
我尝试了所有这些,但都没有奏效。一个非常接近,但它有一个小虫子把它弄坏了;他们忘记将路径用引号引起来。
还有很多人认为你是从 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"`"
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.
这是对 e-satis 和 3bcdnlklvc04a 在他们的回答中指出的解决方案的轻微修改:
SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
SCRIPT_DIR="$PWD"
popd > /dev/null
}
这应该在他们列出的所有情况下仍然有效。
这将防止popd
发生故障后pushd
。感谢 konsolebox。
我会使用这样的东西:
# 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
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.
尝试使用:
real=$(realpath "$(dirname "$0")")
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")"
...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:
~/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"
. 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.
BASH_SOURCE
variable: Unix & Linux: determining path to sourced shell scriptBASH_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 scriptman 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]}
.
这适用于 Bash 3.2:
path="$( dirname "$( which "$0" )" )"
如果你有一个~/bin
目录在你的$PATH
,你 A
在这个目录里面。它来源脚本~/bin/lib/B
。您知道包含的脚本在lib
子目录中相对于原始脚本的位置,但不知道它相对于用户当前目录的位置。
这是通过以下(内部A
)解决的:
source "$( dirname "$( which "$0" )" )/lib/B"
用户在哪里或他/她如何调用脚本并不重要。这将始终有效。
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
我比较了许多给出的答案,并提出了一些更紧凑的解决方案。这些似乎可以处理由您最喜欢的组合引起的所有疯狂边缘情况:
script
, bash script
, bash -c script
, source script
, 或. script
如果您从 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
信息readlink
,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"
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:
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
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
An example: LozanoMatheus/get_script_paths.sh
This worked for me when the other answers here did not:
thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath
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?
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'
.
这是我发现可靠地告诉的唯一方法:
SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
I usually use:
dirname $(which $BASH_SOURCE)
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
$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.
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}")
${(%):-%x}
${(%):-%x}
in zsh expands to the path of the currently-executing file.
:-
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
%x
prompt escape codeNext, 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
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.
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.
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]
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.
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.
/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
#!/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"
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
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.
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.
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
我通常这样做:
LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh
这是一个纯 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/.
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" "$@"
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.
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
我通常在我的脚本顶部包含以下内容,这在大多数情况下都有效:
[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');
第一行根据pwd
if run from the current path 或dirname如果从其他地方调用的值分配源。
第二行检查路径以查看它是否是符号链接,如果是,则将 SOURCE_DIR 更新为链接本身的位置。
那里可能有更好的解决方案,但这是我自己想出的最干净的解决方案。
尝试这样的事情:
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
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`
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")
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
)
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
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
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.
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.
我想确保脚本在其目录中运行。所以
cd $(dirname $(which $0) )
在此之后,如果您真的想知道您正在运行的位置,请运行以下命令。
DIR=$(/usr/bin/pwd)
This one-liner works on Cygwin even if the script has been called from Windows with bash -c <script>
:
set mydir="$(cygpath "$(dirname "$0")")"
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)
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]}")"
Keep it simple.
#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir
FOLDERNAME=${PWD##*/}
That is the quickest way I know.