简介
这个答案更正这个线程的非常破碎但令人震惊的最高投票答案(由 TheMarko 撰写):
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
echo "$BASEDIR"
为什么在它自己上使用 dirname "$0" 不起作用?
dirname $0 仅在用户以非常特定的方式启动脚本时才有效。我能够找到几种这种答案失败并导致脚本崩溃的情况。
首先,让我们了解这个答案是如何工作的。他正在通过
获取脚本目录
dirname "$0"
$0 表示调用脚本的命令的第一部分(基本上是不带参数的输入命令:
/some/path/./script argument1 argument2
$0="/some/path/./script"
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
给出一个目录名 $0:
../some/path/.
如果您在 /some 目录中并以这种方式调用脚本(注意开头没有 /,同样是相对路径):
path/./script.sh
你会得到 dirname $0 的这个值:
path/.
和 ./path/./script (相对路径的另一种形式)给出:
./path/.
basedir $0 唯一有效的两种情况是用户使用 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