Unix shell脚本找出脚本文件驻留在哪个目录?

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

684080 次浏览

在Bash中,你应该像这样得到你需要的东西:

#!/usr/bin/env bash


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

假设您正在使用bash

#!/bin/bash


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


echo $current_dir
echo $script_dir

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

/
/home/mez

请记住,当从命令的输出分配变量时,请将命令包装在$()中—否则将得不到所需的输出。

这应该能奏效:

echo `pwd`/`dirname $0`

它可能看起来很丑,这取决于它是如何被调用和cwd,但应该得到你需要去的地方(或者你可以调整字符串,如果你关心它的外观)。

最初的帖子包含了解决方案(忽略回复,他们没有添加任何有用的东西)。有趣的工作是由前面提到的unix命令readlink和选项-f完成的。当以绝对路径和相对路径调用脚本时,此方法有效。

对于bash, sh, ksh:

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

对于tcsh, csh:

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

参见:https://stackoverflow.com/a/246128/59087

如果您正在使用bash....

#!/bin/bash


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


echo "${basedir}"
cd $(dirname $(readlink -f $0))

正如marko所言:

BASEDIR=$(dirname $0)
echo $BASEDIR

除非你在脚本所在的目录中执行脚本,在这种情况下,你会得到一个值'。'

要解决这个问题,请使用:

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


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

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

灵感来自< >强旅行包的回答< / >强

read < <(readlink -f $0 | xargs dirname)
cd $REPLY

之前对一个答案的评论说过,但在所有其他答案中很容易被忽略。

使用bash时:

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

Bash Reference Manual, 5.2 Bash Variables . sh

让我们把它变成一个POSIX在线程序:

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

在包括BSD在内的许多兼容bourne的shell上进行了测试。

据我所知,我是作者,我把它放到了公共领域。更多信息见: # EYZ0 < / p >
这个问题的最佳答案是:
# EYZ0 < / p >

它是:

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

一行代码,它将提供脚本的完整目录名,无论从哪里调用脚本。

要了解它是如何工作的,你可以执行以下脚本:

#!/bin/bash


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

这么多的答案,都是可信的,每一个都有赞成和反对的观点。略有不同的目标(每个目标都应该说明)。下面是另一个解决方案,它满足了一个主要目标,即在所有系统上,在所有bash上(不假设bash版本,或readlinkpwd选项),并且合理地完成了您期望发生的事情(例如,解决符号链接是一个有趣的问题,但通常不是您真正想要的),处理路径中的空格等边缘情况,忽略任何错误,并在出现任何问题时使用正常的默认值。

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

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

介绍

这个答案< em > < / em >批改是这个帖子(由TheMarko撰写)中非常糟糕但令人震惊的投票结果:

#!/usr/bin/env bash


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

为什么使用dirname "$0"对它自己不工作?

< em >目录名$ 0 < / em >只在用户以非常特定的方式启动脚本时才会工作。我找到了几种这种答案失败并使脚本崩溃的情况。

首先,让我们理解这个答案是如何工作的。他通过做

dirname "$0"

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

/some/path/./script argument1 argument2

$ 0 =“/一些/道路/ /脚本”。

Dirname基本上找到字符串中的最后一个/并在那里截断它。所以如果你这样做:

  dirname /usr/bin/sha256sum

你会得到:/usr/bin

这个例子工作得很好,因为/usr/bin/sha256sum是一个格式正确的路径,但是

  dirname "/some/path/./script"

不会很好地工作,并且会给你:

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

假设您位于与脚本相同的目录中,并使用以下命令启动它

./script

$0在这种情况下将是./script, dirname $0将给出:

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

使用:

sh script

不输入完整路径也会给出一个BASEDIR="。"

使用相对目录:

 ../some/path/./script

给出dirname $0的值:

 ../some/path/.

如果你在/some目录下,你以这种方式调用脚本(注意开头没有/,同样是一个相对路径):

 path/./script.sh

你会得到dirname $0的值:

 path/.

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

 ./path/.

只有两种情况< em > basedir $ 0 < / em >将工作:如果用户使用sh或touch启动脚本,因为这两种情况都会导致$0:

 $0=/some/path/script

这将为您提供一个可以使用dirname的路径。

解决方案

你必须解释并检测上面提到的每一种情况,并在出现时应用修复程序:

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


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


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




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


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


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


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

这一行代码告诉shell脚本是你自己做的还是你自己做的都不重要的位置。此外,它还会解析所涉及的任何符号链接,如果是这样的话:

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

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

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

如果你想获得实际的脚本目录(不管你是使用符号链接还是直接调用脚本),尝试:

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

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

在macOS上,你需要安装coreutils才能使用realpath。如:# EYZ2。

使用tcsh,您可以使用:h变量修饰符来检索路径。

需要注意的是,如果脚本以tcsh myscript.csh的形式执行,那么您只能得到脚本名称。一个解决方法是验证路径,如下所示。

#!/bin/tcsh


set SCRIPT_PATH = $0:h
if ( $SCRIPT_PATH == $0 ) then
set SCRIPT_PATH = "."
endif


$SCRIPT_PATH/compile.csh > $SCRIPT_PATH/results.txt

关于变量修饰符的更多信息可以在https://learnxinyminutes.com/docs/tcsh/上找到

基本版:

dir=$(dirname $0)

如果脚本可以通过$PATH调用,那么:

dir=$(dirname $(which $0))

如果脚本可能像这样调用:bash script.sh,那么:

dir=$(dirname $(which $0 2>/dev/null || realpath ./$0))

如果你感到极度不安全,那么:

dir="$(dirname -- "$(which -- "$0" 2>/dev/null || realpath -- "./$0")")"

我发现的最有效的方法之一是

#!/bin/sh
relative_dir=`perl -e 'use Cwd "realpath";$pwd = realpath(shift); $pwd =~ s/\/[^\/]*$//; print $pwd' $0`
cd $relative_dir

使用符号链接工作,并为我的许多同事工作,无论他们选择的shell类型