在sh中,与%~dp0(获取源文件名)相对应的是什么?

17
我正在将一些Windows批处理文件转换为使用sh的Unix脚本。我遇到了问题,因为某些行为依赖于批处理文件中可用的%~dp0宏。
是否有与此等效的sh版本? 有没有方法可以获取执行脚本所在目录?
```html

我正在将一些Windows批处理文件转换为使用sh的Unix脚本。我遇到了问题,因为某些行为依赖于批处理文件中可用的%~dp0宏。

是否有与此等效的sh版本? 有没有方法可以获取执行脚本所在目录?

```

设计Bash时似乎没有考虑这个功能... - n611x007
请注意,这个问题明确询问的是 sh -- 有很多回答谈论的是 bash,但它们并不是同一件事情。 - Charles Duffy
9个回答

12

$0 的问题(对于您而言)是它被设置为用于调用脚本的命令行,而不是脚本本身所在的位置。这可能会使得获取包含脚本的目录的完整路径变得困难,在 Windows 批处理文件中可以通过 %~dp0 来获取。

例如,考虑以下脚本 dollar.sh

#!/bin/bash
echo $0

如果你运行它,你将得到以下输出:

# ./dollar.sh
./dollar.sh
# /tmp/dollar.sh
/tmp/dollar.sh

因此,要获取脚本的完全限定目录名,我执行以下操作:

cd `dirname $0`
SCRIPTDIR=`pwd`
cd -

这样工作:

  1. 使用相对路径或绝对路径从命令行中 cd 到脚本的目录。
  2. 获取该目录的绝对路径并将其存储在 SCRIPTDIR 中。
  3. 使用 "cd -" 返回到以前的工作目录。

1
如果更改工作目录是不可取的,我添加了另一种方法。 - Sarien
2
如果脚本在路径中而不是直接被调用,那么$0将只是脚本的名称,这种方法将无法工作。 - Steve Baker
因此,也可以调用'which'命令来解决问题('which'命令告诉您命令的完整路径,但大多数人似乎不知道如果它已经是绝对路径,则会回显完整路径)。 - Hakanai

7

是的,你可以!它在参数中。 :)

看看

${0}

结合这个

{$var%Pattern}
Remove from $var  the shortest part of $Pattern that matches the back end of $var.

你想要的只是
${0%/*}

我建议您阅读《Advanced Bash Scripting Guide》(上述信息也来自该书)。 特别是其中的《将DOS批处理文件转换为Shell脚本》部分可能对您有用。 :)
如果我误解了您,您可能需要将其与“pwd”的输出结合起来。因为它只包含调用脚本的路径!请尝试以下脚本:
#!/bin/bash
called_path=${0%/*}
stripped=${called_path#[^/]*}
real_path=`pwd`$stripped
echo "called path: $called_path"
echo "stripped: $stripped"
echo "pwd: `pwd`"
echo "real path: $real_path

这还需要一些改进。 除非不可能,我建议使用Dave Webb的方法。


3
在Linux下的Bash中,您可以使用以下命令获取命令的完整路径:
readlink /proc/$$/fd/255

获取目录的方法如下:

dir=$(dirname $(readlink /proc/$$/fd/255))

虽然很丑,但我还没有找到其他方法。


能否详细解释一下为什么这种方法比其他提出的答案更好,尤其是当脚本在路径中且被间接调用时的情况,并且它是如何工作的,它依赖于什么(我期望像$$这样的bash特性和Unix文件系统,请注意,Cygwin或MingW上的bash可以在没有POSIX兼容的文件系统结构的情况下启动)? - n611x007

2

当执行或引用时,此脚本可以获取shell文件的实际路径。
bash,zsh,ksh,dash中测试通过。

顺便说一下:您应该自己清理冗长的代码。

#!/usr/bin/env bash

echo "---------------- GET SELF PATH ----------------"
echo "NOW \$(pwd) >>> $(pwd)"
ORIGINAL_PWD_GETSELFPATHVAR=$(pwd)

echo "NOW \$0 >>> $0"
echo "NOW \$_ >>>  $_"
echo "NOW \${0##*/} >>> ${0##*/}"

if test -n "$BASH"; then
    echo "RUNNING IN BASH..."
    SH_FILE_RUN_PATH_GETSELFPATHVAR=${BASH_SOURCE[0]}
elif test -n "$ZSH_NAME"; then
    echo "RUNNING IN ZSH..."
    SH_FILE_RUN_PATH_GETSELFPATHVAR=${(%):-%x}
elif test -n "$KSH_VERSION"; then
    echo "RUNNING IN KSH..."
    SH_FILE_RUN_PATH_GETSELFPATHVAR=${.sh.file}
else
    echo "RUNNING IN DASH OR OTHERS ELSE..."
    SH_FILE_RUN_PATH_GETSELFPATHVAR=$(lsof -p $$ -Fn0 | tr -d '\0' | grep "${0##*/}" | tail -1 | sed 's/^[^\/]*//g')
fi

echo "EXECUTING FILE PATH: $SH_FILE_RUN_PATH_GETSELFPATHVAR"

cd "$(dirname "$SH_FILE_RUN_PATH_GETSELFPATHVAR")" || return 1

SH_FILE_RUN_BASENAME_GETSELFPATHVAR=$(basename "$SH_FILE_RUN_PATH_GETSELFPATHVAR")

# Iterate down a (possible) chain of symlinks as lsof of macOS doesn't have -f option.
while [ -L "$SH_FILE_RUN_BASENAME_GETSELFPATHVAR" ]; do
    SH_FILE_REAL_PATH_GETSELFPATHVAR=$(readlink "$SH_FILE_RUN_BASENAME_GETSELFPATHVAR")
    cd "$(dirname "$SH_FILE_REAL_PATH_GETSELFPATHVAR")" || return 1
    SH_FILE_RUN_BASENAME_GETSELFPATHVAR=$(basename "$SH_FILE_REAL_PATH_GETSELFPATHVAR")
done

# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
SH_SELF_PATH_DIR_RESULT=$(pwd -P)
SH_FILE_REAL_PATH_GETSELFPATHVAR=$SH_SELF_PATH_DIR_RESULT/$SH_FILE_RUN_BASENAME_GETSELFPATHVAR
echo "EXECUTING REAL PATH: $SH_FILE_REAL_PATH_GETSELFPATHVAR"
echo "EXECUTING FILE DIR: $SH_SELF_PATH_DIR_RESULT"

cd "$ORIGINAL_PWD_GETSELFPATHVAR" || return 1
unset ORIGINAL_PWD_GETSELFPATHVAR
unset SH_FILE_RUN_PATH_GETSELFPATHVAR
unset SH_FILE_RUN_BASENAME_GETSELFPATHVAR
unset SH_FILE_REAL_PATH_GETSELFPATHVAR
echo "---------------- GET SELF PATH ----------------"
# USE $SH_SELF_PATH_DIR_RESULT BEBLOW

2
我试图找到一个被另一个脚本引用的脚本的路径。这就是我的问题所在,当源代码被引用时,文本只会被复制到调用脚本中,所以$0总是返回有关调用脚本的信息。
我找到了一个解决方法,只适用于bash。$BASH_SOURCE始终具有有关所引用脚本的信息。即使脚本被引用,它也会正确地解析为原始(引用)脚本。

2
正确答案是这个: 如何确定我的脚本的位置?我想从同一位置读取一些配置文件。 重要的是要认识到,在一般情况下,这个问题是没有解决方案的。你可能听说过任何方法,以下详细介绍的任何方法都有缺陷,并且只能在特定情况下工作。首先,尽量避免整个问题,不要依赖于您的脚本的位置!
在我们深入解决方案之前,让我们澄清一些误解。重要的是要理解:
您的脚本实际上没有位置!无论字节来自哪里,都没有“一个规范路径”。从$0中获取的结果并不能解决您的问题。如果您认为它可以,请停止阅读并编写更多错误,或者接受这个事实并继续阅读。

2

试试这个:

${0%/*}

1
这对于 bash shell 应该有效:
dir=$(dirname $(readlink -m $BASH_SOURCE))

测试脚本:
#!/bin/bash
echo $(dirname $(readlink -m $BASH_SOURCE))

Run test:

$ ./somedir/test.sh 
/tmp/somedir
$ source ./somedir/test.sh 
/tmp/somedir
$ bash ./somedir/test.sh 
/tmp/somedir
$ . ./somedir/test.sh 
/tmp/somedir

你缺少了一些引号 -- 如果你的文件来自一个带空格的目录(或者特别是两边都有空格的 * ),那么尝试这个方法,因为给定的代码将无法正常运行。更正确的做法是 dir=$(dirname "$(readlink -m "$BASH_SOURCE")") - Charles Duffy

0

我之前尝试过$0,即:

dirname $0

但是即使脚本被另一个脚本引用时,它仍然返回“.”:

. ../somedir/somescript.sh


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接