Unix shell脚本找出脚本文件所在的目录?

Unix shell script find out which directory the script file resides?

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


在bash中,您应该得到如下所需的:

1
2
3
4
#!/usr/bin/env bash

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


原始日志包含解决方案(忽略响应,它们不添加任何有用的内容)。有趣的工作是由上述带有选项-f的unix命令readlink完成的。当脚本由绝对路径和相对路径调用时工作。

对于bash、sh、ksh:

1
2
3
4
5
6
#!/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:

1
2
3
4
5
6
#!/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时:

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

bash参考手册,5.2 bash变量


假设你在使用bash

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

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

echo $current_dir
echo $script_dir

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

1
2
/
/home/mez

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


如果你在使用bash…

1
2
3
4
5
6
7
8
#!/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}"


正如市场建议:

1
2
BASEDIR=$(dirname $0)
echo $BASEDIR

除非您从脚本所在的同一目录执行脚本,否则这是有效的,在这种情况下,您将得到一个值"."。

要解决该问题,请使用:

1
2
3
4
5
6
7
current_dir=$(pwd)
script_dir=$(dirname $0)

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

现在您可以在整个脚本中使用变量current_dir来引用脚本目录。但是,这可能仍然存在symlink问题。


这个问题的最佳答案是:从内部获取bash脚本的源目录

是:

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

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

要了解它的工作原理,可以执行以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

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

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

让我们把它变成一个posix一行程序:

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

在许多波恩兼容的外壳上测试,包括BSD外壳。

据我所知,我是作者,我把它放到了公共领域。有关详细信息,请参阅:https://www.jasan.tk/posts/2017-05-11-posix-shell-dirname-u替换/


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


如此多的答案,都是合理的,每一个都有赞成和反对的目标(可能每个目标都应该说明)。这是另一个解决方案,它满足了在所有系统、所有bash(不假设bash版本、或readlinkpwd选项)上都保持清晰和工作的主要目标,并且合理地执行您期望发生的事情(例如,解决符号链接是一个有趣的问题,但通常不是您实际想要的),处理边缘情况,如路径中的空格等,忽略任何错误,如果有任何问题,则使用健全的默认值。

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

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


介绍

这个答案纠正了这条线非常破碎但令人震惊的最高投票答案(由市场部撰写):

1
2
3
4
#!/usr/bin/env bash

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

为什么在上面使用dirname"$0"不起作用?

只有当用户以非常特定的方式启动脚本时,dirname$0才有效。我找到了几个这样的情况:这个答案失败了,脚本崩溃了。

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

1
dirname"$0"

$0表示调用脚本的命令的第一部分(基本上是不带参数的输入命令:

1
/some/path/./script argument1 argument2

$0="/some/path//script"

dirname基本上查找字符串中的最后一个/并将其截断。所以如果你这样做:

1
  dirname /usr/bin/sha256sum

你会得到:usr/bin

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

1
  dirname"/some/path/./script"

工作不好,会给你:

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

假设你和你的脚本在同一个目录下,用这个命令启动它。

1
./script

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

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

使用:

1
sh script

如果不输入完整路径,也会给出basedir=""

使用相对目录:

1
 ../some/path/./script

给出目录名$0:

1
 ../some/path/.

如果您在/some目录中,并且以这种方式调用脚本(请注意,开头没有/some目录,再次是相对路径):

1
 path/./script.sh

您将以dirname$0获得此值:

1
 path/.

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

1
 ./path/.

basedir$0的唯一两种情况是,如果用户使用sh或touch启动脚本,因为两者都将导致$0:

1
 $0=/some/path/script

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

解决方案

您将解释并检测上述每一种情况,并在出现以下情况时对其应用修复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/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脚本在哪里,不管您是运行它还是从中获得它。此外,它还解析涉及的任何符号链接,如果是这样的话:

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

顺便说一下,我想您使用的是/bin/bash。


灵感来源于布鲁耶德的回答

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

如果要获取实际的脚本目录(无论是使用symlink还是直接调用脚本),请尝试:

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

这在Linux和MacOS上都有效。我看不到这里有人提到过realpath。不确定这种方法是否有任何缺点。

在MacOS上,需要安装coreutils才能使用realpath。例如:brew install coreutils


这样就可以做到:

1
echo `pwd`/`dirname $0`

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