可移植的方式来获取脚本的绝对路径?


29

(zsh)脚本确定其绝对路径的可移植方式是什么?

在Linux上,我使用类似

mypath=$(readlink -f $0)

...但这不是便携式的。(例如,readlink在darwin上,该-f标志无法识别,也没有任何等效的标志。)(当然,readlink为此使用的是一个相当晦涩的hack。)

有什么更便携的方式?


Answers:


28

使用zsh,只是:

mypath=$0:A

现在对于其他炮弹,但realpath()readlink()是标准功能(后者是一个系统调用),realpathreadlink不是标准的命令,虽然一些系统有一个或另一个或两个各种行为和功能集。

通常,出于便携性考虑,您可能需要诉诸perl

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

那会readlink -frealpath()(GNU readlink -e)更像GNU ,因为只要目录名不存在文件就不会抱怨。


注意:这不适用于您.zshrc:请改为参阅此文章
布莱斯·昆塔

24

在zsh中,您可以执行以下操作:

mypath=${0:a}

或者,要获取脚本所在的目录:

mydir=${0:a:h}

来源:zshexpn(1)手册页,“历史扩展”部分,“修饰符”部分(或简称为info -f zsh -n Modifiers)。


甜!多年来,我一直在寻找类似的想法,并阅读了整本zsh的联机帮助页,但是对于我来说,在“历史扩展”下进行查找绝对是不可能的。
VucarTimnärakrul2014年

1
GNU的相当于readlink -f宁愿$0:A
斯特凡Chazelas

11

我已经使用了几年了:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")

1
我喜欢!到目前为止,这很方便。工作在Solaris上,OmniOS,在Linux,Mac,甚至Cygwin的Windows 2008上
蒂姆·肯尼迪

4
它不等同于GNU的地方readlink -f是脚本本身是一个符号链接。
斯特凡Chazelas

在带有Ubuntu 16.04的Linux上zsh,如果我在主目录(/home/ville)中直接执行此操作或在subshel​​l中执行此操作(如建议的那样),则会打印出/home/ville/zsh
Ville

8

这句法应该可以移植到任何Bourne shell的风格解释(与测试bashksh88ksh93zshmkshdashbusybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

此版本增加了对传统AT&T Bourne外壳(非POSIX)的兼容性:

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath

谢谢。不过,我认为$PWD取消设置可能会过大-您可以将其设置为绝对电流,例如cd -P .。我怀疑这是否可以同时使用-但它应该在您第一次测试的所有程序中都有效。反正为我做。
mikeserv

@moose您正在运行什么操作系统?
jlliagre 2014年

谁是驼鹿?什么?
mikeserv

@mikeserv moose是个路人,他发表了一个关于某问题的评论zshdirname但很快撤消了他/她的评论……
jlliagre 2014年

您的Bourne Shell脚本无法与Bourne Shell一起使用,因为Bourne Shell不对cd(1)使用getopt()。
schily 2015年

4

假设您的意思是绝对路径,即来自根目录的路径:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

顺便说一下,这可以在任何Bourne风格的shell中使用。

如果您的意思是解决所有符号链接的路径,那是另一回事。readlink -f适用于Linux(不包括某些精简的BusyBox系统),FreeBSD,NetBSD,OpenBSD和Cygwin,但不适用于OS / X,AIX,HP / UX或Solaris。如果有readlink,则可以循环调用:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

如果没有readlink,则可以使用进行近似ls -n,但这仅ls在文件名中没有任何不可打印字符的情况下才有效。

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

z如果链接目标以换行符结尾,则可能会产生额外的损失,否则命令替换会吃光了。realpath顺便说一句,该函数无法为目录名处理这种情况。)


1
您是否知道在输出未发送到终端时会处理不可打印字符的任何ls实现。
斯特凡Chazelas

1
@StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(如果名称使用UTF-8,则latin1给您一个?)。我想我也已经在较老的商业Unices上看到了。
吉尔斯(Gillles)“所以-别再邪恶了”

@StéphaneChazelas我已经修复了多个错误,但尚未进行广泛的测试。让我知道它是否在某些情况下仍然失败(除了某些目录中没有执行权限之外,我不会处理这种极端情况)。
吉尔(Gilles)“所以,别再邪恶了”

@吉尔斯-这busybox是什么?根据git的 说法,busybox ls自2011 busybox ls年以来没有进行过代码更改。我(大约在2013年)没有执行此操作。这一个 -大约2012 - 可以解释原因。您是否已建立busybox了Unicode支持-包括wchar支持?您可能需要尝试一下,否则请检查mkinitcpio busybox软件包中的构建选项。
mikeserv

吉尔斯-我相信我最初对这个答案的判断有误-或至少是其中的一部分。尽管我坚信您修改文件名的口头禅完全是谬论,但绝对可以说您的bad_mans_readlink做得很好。如果您愿意进行编辑-任何编辑都可以-然后对我进行ping操作,那么我想对此表示反对。
mikeserv

1

如果您具有对当前目录(或从中执行Shell脚本的目录)的执行权限,则如果要获得目录的绝对路径,则只需要一个cd

cd规范的第10步

如果该-P选项有效,则将$PWD环境变量设置为将会输出的字符串pwd -P。如果对新目录或该目录的任何父目录没有足够的权限来确定当前的工作目录,$PWD则未指定环境变量的值。

然后 pwd -P

写入标准输出的路径名不得包含任何引用符号链接类型文件的组件。如果pwd实用程序可以将多个路径名写入标准输出,一个以一个/斜杠字符开头,一个或多个以两个/斜杠字符开头,那么它应该以一个/斜杠字符开头写该路径名。路径名不得在前一或两个/斜杠字符之后包含任何不必要的/斜杠字符。

这是因为cd -P有设置当前工作目录是什么pwd -P应该,否则打印认为cd -具有打印$OLDPWD,下面的工作:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

输出值

/home/mikeserv/test/ln

等一下...

cd -P . ; cd . ; cd -

输出值

/home/mikeserv/test/dir

当我与cd -我打印时$OLDPWD。现在就cd设置$PWDcd -P . $PWD绝对路径/-我不需要任何其他变量。实际上,我什至不需要尾随,.但在不经过修饰的情况下,有一种特定的行为会重置$PWD$HOME交互式外壳 cd程序。因此,这只是养成的好习惯。

因此,仅对in的路径进行上述操作就${0%/*}足以验证$0的路径,但是$0不幸的是,在本身是软链接的情况下,您可能无法将目录更改为该路径。

这是一个可以处理的函数:

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

它努力在当前shell中尽其所能-不调用子shell-尽管为错误和未指向目录的软链接调用了子shell。它取决于POSIX兼容的外壳程序和POSIX兼容的ls以及干净的_function()名称空间。没有后者,它仍然可以正常工作,尽管unset在这种情况下,它可能会覆盖一些当前的Shell函数。通常,所有这些依赖项在Unix机器上应该是相当可靠的。

无论有无参数调用,首先$PWD将其重置为规范值-必要时将其中的任何链接解析为目标。调用时不带参数,仅此而已;但与他们一起调用,它将为每个问题解析并规范化路径,否则会打印一条消息,stderr说明为什么不这样做。

因为它主要在当前shell中运行,所以它应该能够处理任意长度的参数列表。它还会寻找$_zdlm变量unset在通过时也将显示该变量),并在其每个参数的右边立即打印其C转义的值,每个参数之后始终始终带有一个\n单行字符。

它做了很多目录更改,但是除了将其设置为规范值之外,它不会影响$PWD,尽管在$OLDPWD通过时无法以任何方式进行计数。

它试图尽快退出其每个参数。它首先尝试cd进入$1。如果可以,则将参数的规范路径打印到stdout。如果不能,则检查是否$1存在并且不是软链接。如果为true,则打印。

这样,它可以处理外壳程序有权访问的任何文件类型参数,除非$1它是不指向目录的符号链接。在这种情况下,它将while在子Shell中调用loop。

它调用ls以读取链接。为了可靠地处理任何引用路径,必须首先将当前目录更改为其初始值,因此,在命令替换子Shell中,该函数执行以下操作:

cd -...ls...echo /

它从ls输出的左侧剥离的内容要少得多,以完全包含链接的名称和字符串->。虽然我一开始尝试避免使用shift$IFS但事实证明,这是我认为最可靠的方法。这与Gilles的poor_mans_readlink一样,而且做得很好。

它将循环重复此过程,直到从 ls绝对不是软链接。此时,它会像以前一样规范化该路径,cd然后进行打印。

用法示例:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

输出值

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

也许...

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

输出值

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.