从脚本本身获取Bash脚本的源目录

如何获取其中的目录路径的Bash脚本所在,该脚本里面

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

$ ./application

#1楼

我会用这样的东西:

# 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

#2楼

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

#3楼

这是符合POSIX的单线:

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

# test
echo $SCRIPT_PATH

#4楼

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

  • 绝对路径或相对路径
  • 文件和目录软链接
  • 作为scriptbash scriptbash -c scriptsource script或调用. 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会添加自己的换行符(如果不使用我们以前的readlink ,通常会在命令替换中吃掉换行符),所以我们也必须摆脱它。 使用$''引用方案最容易实现这一点,该方案使我们可以使用转义序列(例如\\n来表示换行符(这也是您可以轻松地创建名称明确的目录和文件的方式)。

上面的内容可以满足您在Linux上查找当前正在运行的脚本的需求,但是如果您没有proc文件系统可供使用,或者如果您要查找其他文件的完全解析路径,那么也许您可以会发现以下代码很有帮助。 这只是上述单线的略微修改。 如果您正在使用奇怪的目录/文件名,请同时检查lsreadlink的输出是否有用,因为ls将输出“简化的”路径,用? 用于换行。

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"

#5楼

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

适用于所有版本,包括

  • 通过多个深度软链接调用时,
  • 当文件
  • 当脚本被命令“ source ”调用时. (点)运算符。
  • 从调用方修改arg $0时。
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

另外,如果bash脚本本身是一个相对的符号链接,则跟随它并返回链接脚本的完整路径:

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

SCRIPT_PATH完整路径给出。
只需确保在脚本开始时找到它即可。

此注释和代码为Copyleft,是GPL2.0或更高版本或CC-SA 3.0(CreativeCommons Share Alike)或更高版本的可选许可证。 (c)2008。保留所有权利。 没有任何形式的保证。 你被警告了。
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


#6楼

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

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

除了Bash之外,没有任何依赖。 使用dirnamereadlinkbasename最终会导致兼容性问题,因此如果可能的话,最好避免使用它们。


#7楼

这在bash-3.2中有效:

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

这是一个用法示例:

假设您有一个〜/ bin目录,该目录位于$ PATH中 。 您在此目录中有脚本A。的剧本〜/斌/ lib中/ B。 您知道所包含的脚本相对于原始脚本在何处(子目录lib ),但相对于用户当前目录却不知道。

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

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

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


#8楼

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

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

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

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

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

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

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

有关:


#9楼

所以...我相信我有这个。 晚会晚了,但我想有人会欣赏这里,因为他们碰到了这个话题。 评论应解释。

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

#10楼

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

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

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


#11楼

#!/bin/bash

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

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

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

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

最后一个将使用别名, sourcebash -c ,symlinks等的任意组合。

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

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

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

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE="$TARGET"
  else
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

它会打印类似:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

#12楼

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

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

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

编辑:阻止失败后弹出,多亏了konsolebox


#13楼

简短答案:

`dirname $0`

或( 最好 ):

$(dirname "$0")

#14楼

应该这样做:

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

在路径中使用符号链接和空格。 有关目录名readlink的信息,请参见手册页。

编辑:

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


#15楼

这是简单正确的方法:

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

说明:

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

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

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


#16楼

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

也有很多人认为您是从Shell运行脚本的,因此在打开新脚本时会忘记它默认为您的家。

尝试以下目录获取大小:

/ var /没有人/没有想法/关于空格正在/目录中/名称/这是您的file.text

不管您如何运行或在何处运行,它都可以正确实现。

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

为了使它真正有用,这里是如何更改运行脚本的目录:

cd "`dirname "$0"`"

希望能有所帮助


#17楼

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

#18楼

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

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

#19楼

嗯,如果在路径名中,基名和目录名只是不会被剪切,并且走路径很困难(如果父项未导出PATH会怎样!)。 但是,shell必须为其脚本打开一个句柄,在bash中,句柄为#255。

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

为我工作。


#20楼

总结许多答案:

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

使用的命令

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

输出:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

参见https://pastebin.com/J8KjxrPF


#21楼

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

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

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

#22楼

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

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

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

可以在这里找到更多详细信息: https : //www.electrictoolbox.com/bash-script-directory/


#23楼

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

#24楼

您可以使用$ BASH_SOURCE

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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


#25楼

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

./script

/usr/bin/script

script

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

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


#26楼

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

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

scriptdir=`dirname "$PRG"`

#27楼

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

但是, dirname恰好给出了文件名的目录部分,该部分很可能相对于当前工作目录。 如果你的脚本需要出于某种原因更改目录,然后从输出dirname就变得毫无意义。

我建议以下内容:

#!/bin/bash

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

echo "Directory is $directory"

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

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

虽然只是

cd `dirname $0`

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


#28楼

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

#29楼

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

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

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


#30楼

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

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

#31楼

尝试使用:

real=$(realpath $(dirname $0))
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值