在OS X中使用Bash脚本的绝对路径

150

我正在尝试获取在OS X上当前运行脚本的绝对路径。

我看到很多回复都使用readlink -f $0。然而,由于OS X的readlink与BSD的相同,所以它不起作用(它在GNU版本中有效)。

是否有开箱即用的解决方案?


33
$( cd "$(dirname "$0")" ; pwd -P ) - Jason S
6
安装核心工具,请执行 brew install coreutils - Vojtěch
18个回答

162

这三个简单的步骤将解决此问题以及其他许多OS X问题:

  1. 安装Homebrew
  2. brew install coreutils
  3. grealpath .

(3) 可能会更改为 realpath,请参见 (2) 的输出


5
这解决了问题,但需要安装一些可能不想要或不需要的东西,或者可能在目标系统上没有,例如 OS X。 - Jason S
10
@JasonS GNU Coreutils太好用了,不能不用。它包括很多优秀的工具。实际上,Linux内核没有它就没什么用了,这也是为什么有些人称其为GNU/Linux的原因。Coreutils非常棒。优秀的答案,应该选择此答案。 - user9903
16
问题明确提到需要一个“开箱即用的解决方案”(适用于OS X)。无论coreutils有多强大,它都不是OS X上的“开箱即用”解决方案,因此答案不够好。这不是评价coreutils好坏或者是否比OS X自带的工具更好的问题。有一些“开箱即用”的解决方案,例如$( cd "$(dirname "$0")" ; pwd -P ) 对我来说就很好用。 - Jason S
2
OS X的一个“特性”是目录和文件名不区分大小写。因此,如果您有一个名为XXX的目录,有人输入cd xxx,那么pwd将返回.../xxx。除了这个答案外,上面的所有解决方案都会返回xxx,而您真正想要的是XXX。谢谢! - Andrew
1
我不知道无休止地复制、粘贴和重新发明代码比现有的包管理器解决方案更好在哪里。如果你需要 realpath,那么当你几乎肯定需要 coreutils 中的其他项目时会发生什么?也要在 bash 中重写这些函数吗?:P - Ezekiel Victor
显示剩余4条评论

128

有一个C函数realpath()可以完成这个任务,但我没有看到任何可用于命令行的东西。这里是一个快速而肮脏的替代方法:

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

如果路径以 / 开头,则原样打印该路径。否则,它必须是相对路径,因此将 $PWD 添加到前面。 #./ 部分从 $1 的前面删除了 ./


1
我也注意到了C函数,但是找不到任何对应的二进制文件或其他东西。无论如何,你的函数正好符合我的需求。谢谢! - The Mighty Rubber Duck
11
请注意,这不会取消符号链接的引用。 - Adam Vandenberg
23
realpath ../something 返回 $PWD/../something。其中,realpath 是一个命令,用于获取输入路径的绝对路径;而 $PWD 则是当前工作目录的环境变量。 - Lri
10
如果 realpath 命令存在,则不执行任何操作。如果不存在,则定义一个名为 realpath() 的函数并执行其中的代码。 - Kara Brightwell
1
@BrandonL 为什么?$0是脚本本身,$1是脚本的第一个参数,不是我们想要的。 - John Kugelman
显示剩余4条评论

45

出于几个原因,我觉得答案有点欠缺:
特别是,它们不能解决多级符号链接,并且它们非常“Bash-y”。

虽然原始问题明确要求一个“Bash脚本”,但它也提到了Mac OS X的类似BSD的非GNU readlink

所以这里尝试了一些合理的可移植性方法(我已经使用bash作为'sh'和dash进行了检查),可以解决任意数量的符号链接;它还应该适用于路径中的空格。

此答案先前已进行编辑,重新添加了local bashism。此答案的重点是一个可移植的POSIX解决方案。我已将其编辑为通过更改为子shell函数而不是内联函数来解决变量作用域。请勿编辑。

#!/bin/sh
realpath() (
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
)
realpath "$@"

希望这对某人有所帮助。


3
我建议仅在函数内部定义变量时使用“local”关键字以避免污染全局命名空间。例如,使用“local OURPWD=...”。至少在Bash中适用。 - Michael Paesold
1
另外,在代碼中不應使用大寫字母來表示私有變量。大寫變量是系統保留的。 - tripleee
感谢提供脚本。如果链接和实际文件具有不同的基本名称,最好在while循环内添加BASENAME=$(basename "$LINK"),并在第二个LINK设置器和REALPATH设置器中使用它。 - stroborobo
3
这个函数处理符号链接和 .. 父级引用的方式并不像 realpath 那样。如果安装了 homebrew 的 coreutils,可以尝试运行 ln -s /var/log /tmp/linkexample,然后运行 realpath /tmp/linkexample/../;这将打印出 /private/var。但你的函数则生成了 /tmp/linkexample/..,因为 .. 不是一个符号链接。 - Martijn Pieters
@Martin 有趣。我会研究一下这个。 - Geoff Nixon

19

Python解决方案的更加适合命令行的变体:

python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' ./my/path

4
万一有人疯狂到只为了执行一个命令而启动 Python 解释器... - Bachsau
3
我很生气,但使用了 python -c "import os; import sys; print(os.path.realpath(sys.argv[1]))" - Alex Chamberlain
@Bachsau,您是否已经检查过平均Linux系统中有多少“单个命令”只是一个Python脚本坐落在/usr/bin目录下?您可能会惊讶于自己已经在使用多少。 - Philip Couling
@PhilipCouling,“单个命令”并不是指我在命令行上输入的单个命令,而是由单个命令组成的脚本内容。 - Bachsau
1
@Bachsau 我知道,但请仔细考虑这两者之间的确切区别是什么(并不多)。Python作为解释器已经足够高效,可以用于小型实用程序。以上述方式使用它与调用sed或awk...或sh -c几乎没有什么区别...它们都是解释器。 - Philip Couling

14

我检查了每一个回答,但错过了Jason S于2016年7月14日3:12提供的最佳答案(在我看来),并留下了评论区。

所以在这里,如果有像我一样倾向于检查已回答问题而没有时间阅读每一个评论的人:

$( cd "$(dirname "$0")" ; pwd -P )

帮助:

NAME
     pwd -- return working directory name

SYNOPSIS
     pwd [-L | -P]

DESCRIPTION
     The pwd utility writes the absolute pathname of the current working
     directory to the standard output.

     Some shells may provide a builtin pwd command which is similar or identi-
     cal to this utility.  Consult the builtin(1) manual page.

     The options are as follows:

     -L      Display the logical current working directory.

     -P      Display the physical current working directory (all symbolic
             links resolved).

1
这是我最喜欢的解决方案。它简单、真正便携,并且不需要使用Homebrew。如果你深入研究Homebrew,最终会在你的Mac上管理两个软件包系统,带来很多麻烦。好处必须值得这一切——在这种简单情况下,它们绝对不值得。 - András Aszódi

14

正如其他人所指出的那样,由于存在realpath

// realpath.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
  if (argc > 1) {
    for (int argIter = 1; argIter < argc; ++argIter) {
      char *resolved_path_buffer = NULL;
      char *result = realpath(argv[argIter], resolved_path_buffer);

      puts(result);

      if (result != NULL) {
        free(result);
      }
    }
  }

  return 0;
}

Makefile:

#Makefile
OBJ = realpath.o

%.o: %.c
      $(CC) -c -o $@ $< $(CFLAGS)

realpath: $(OBJ)
      gcc -o $@ $^ $(CFLAGS)

然后使用make进行编译,并使用以下命令创建软链接:
ln -s $(pwd)/realpath /usr/local/bin/realpath


我们可以使用 gcc realpath.c -o /usr/local/bin/realpath 吗? - Alexander Mills
1
@AlexanderMills 你不应该以root身份运行编译器;如果你不这样做,你就没有写入/usr/local/bin的权限。 - tripleee

12
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirname会返回目录路径,例如/path/to/file的结果是/path/to

cd /path/to; pwd确保路径是绝对路径。

basename会返回文件名,例如在/path/to/file中,结果是file


2
虽然这份代码或许回答了问题,但是提供关于为什么或者怎样回答问题的额外背景信息会增加其长期价值。 - Igor F.

9

我正在寻找一个用于系统设置脚本的解决方案,即在Homebrew甚至未安装之前运行。由于没有合适的解决方案,我只能将任务转交给跨平台语言,例如Perl:

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

更常见的需求是获取所在目录:
here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")

太好了!Perl非常适合处理小开销!你可能可以将第一个版本简化为FULLPATH=$(perl -e"use Cwd 'abs_path'; print abs_path('$0')")。有什么反对的理由吗? - F Pereira
1
从未转义的用户提供的字符串生成程序代码绝不是一个好主意。''并非万无一失。例如,如果$0包含单引号,则会出现错误。一个非常简单的例子:在/tmp/'/test.sh中尝试您的版本,并通过其完整路径调用/tmp/'/test.sh - 4ae1e1
甚至更简单的是,/tmp/'.sh - 4ae1e1

7

使用Python获取它:

#!/usr/bin/env python
import os
import sys

print(os.path.realpath(sys.argv[1]))

6

Mac OS X 的 realpath

realpath() {
    path=`eval echo "$1"`
    folder=$(dirname "$path")
    echo $(cd "$folder"; pwd)/$(basename "$path"); 
}

相关路径的示例:

realpath "../scripts/test.sh"

以家目录为例

realpath "~/Test/../Test/scripts/test.sh"

1
很好的简单解决方案,我只发现一个问题,当使用 .. 调用它时,它不能产生正确的答案,因此我添加了一个检查,如果给定的路径是目录:if test -d $path ; then echo $(cd "$path"; pwd) ; else [...] - Herbert Poul
对我没用,而 "$(dirname $(dirname $(realpath $0)))" 可以工作,所以需要其他的东西... - Alexander Mills
不应该使用无用的echo - tripleee
1
这实际上并没有像realpath一样解析符号链接。它在解析符号链接之前解析..父引用,而不是之后;尝试使用homebrew coreutils安装创建链接ln -s /var/log /tmp/linkexample,然后运行realpath /tmp/linkexample/../;这将打印/private/var。但是您的函数却产生了/tmp/linkexample/..,因为cdpwd仍然显示/tmp/linkexample - Martijn Pieters

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