如何通过Shell(BASH/ZSH/SH)获取文件的绝对路径?

704

问题:是否有一个简单的sh/bash/zsh/fish/...命令,可以打印出我输入的任何文件的绝对路径?

使用场景:我在目录/a/b中,我想要在命令行中打印文件c的完整路径,以便我可以轻松地将其粘贴到另一个程序中:/a/b/c。这很简单,但是编写一个小程序来完成此任务可能会为我节省5秒钟左右的时间,当处理长路径时这些时间最终会相加。所以让我感到惊讶的是,我找不到一个标准的实用程序来完成这个任务 - 确实没有吗?

这里是一个示例实现,abspath.py:

#!/usr/bin/python
# Author: Diggory Hardy <diggory.hardy@gmail.com>
# Licence: public domain
# Purpose: print the absolute path of all input paths

import sys
import os.path
if len(sys.argv)>1:
    for i in range(1,len(sys.argv)):
        print os.path.abspath( sys.argv[i] )
    sys.exit(0)
else:
    print >> sys.stderr, "Usage: ",sys.argv[0]," PATH."
    sys.exit(1)

我认为@DennisWilliamson的回答(使用-m选项)更优秀,因为它通常更具可移植性,并且可以处理不存在的文件。 - TTT
或者Flimm的答案也很好; 但是Bannier的回答最符合我的原始问题。 - dhardy
3
苹果电脑用户请查看此答案。链接 - Ian
可能是重复的问题Bash: 获取相对路径的绝对路径 - user
23个回答

8

如果您只想使用内置功能,最简单的方法可能是:

find `pwd` -name fileName

只需额外输入两个单词,就可以在所有Unix系统以及OSX上运行。


我会在“-name”之前添加“-maxdepth 1”(假设文件在当前目录中)。如果没有这个选项,它将处理pwd的任何子目录中的文件,但不包括其他目录。 - Walter Tross
确实,你是对的,问题确实指定了文件将在当前目录中。你说的“但不与其他人一起”是什么意思? - Arj
我的意思是 find 命令无法在 pwd 目录树下的目录之外找到文件。例如,如果您尝试使用 find . -name ../existingFile 命令,它会失败。 - Walter Tross
好的,是的,这会假设您想要在当前工作目录中打印文件的完整路径(与原始问题一样),或者在当前工作目录树中的任何位置(不在原始问题中)。 - Arj

6

这个问题的顶级答案在某些情况下可能会误导。假设您想要找到绝对路径的文件在 $PATH 变量中:

# node is in $PATH variable
type -P node
# /home/user/.asdf/shims/node
cd /tmp
touch node  # But because there is a file with the same name inside the current dir check out what happens below
readlink -e node
# /tmp/node
readlink -m node
# /tmp/node
readlink -f node
# /tmp/node
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node
realpath node
# /tmp/node
realpath -e node
# /tmp/node

# Now let's say that for some reason node does not exist in current directory
rm node
readlink -e node
# <nothing printed>
readlink -m node    
# /tmp/node         # Note: /tmp/node does not exist, but is printed
readlink -f node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node         # Note: /tmp/node does not exist, but is printed
realpath node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
realpath -e node
# realpath: node: No such file or directory

基于以上,我可以得出结论: realpath -ereadlink -e 可用于查找文件的绝对路径,我们期望该文件存在于当前目录中,结果不受$PATH变量的影响。唯一的区别是realpath输出到stderr,但如果找不到文件,两者都将返回错误代码:
cd /tmp
rm node
realpath -e node ; echo $?
# realpath: node: No such file or directory
# 1
readlink -e node ; echo $?
# 1

如果您想获取在$PATH目录中存在的文件的绝对路径a,则以下命令适用,无论当前目录中是否存在同名文件。

type -P example.txt
# /path/to/example.txt

# Or if you want to follow links
readlink -e $(type -P example.txt)
# /originalpath/to/example.txt

# If the file you are looking for is an executable (and wrap again through `readlink -e` for following links )
which executablefile
# /opt/bin/executablefile

如果缺失,就回退到$PATH,例如:

cd /tmp
touch node
echo $(readlink -e node || type -P node)
# /tmp/node
rm node
echo $(readlink -e node || type -P node)
# /home/user/.asdf/shims/node

6

这里是关于dogbane答案,描述了正在发生的事情:

#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

解释:

  1. 这个脚本将相对路径作为参数传入 "$1"
  2. 然后我们获取该路径的 dirname 部分(您可以将目录或文件传递给此脚本):dirname "$1"
  3. 然后我们 cd "$(dirname "$1") 进入此相对目录,并通过运行 pwd shell 命令获取其绝对路径
  4. 之后,我们将 basename 添加到绝对路径中:$(basename "$1")
  5. 最后一步是使用 echo 命令输出结果

5

使用Homebrew回答

realpath是最佳答案,但如果您没有安装它,则必须先运行brew install coreutils,这将安装coreutils,其中包含许多强大的功能。编写自定义函数并导出它对于像此类事情来说太麻烦且存在风险,下面是两行代码:

$ brew install coreutils
$ realpath your-file-name.json 

realpath解析符号链接。 - balupton
在较新版本的realpath中,如果要获取文件的完整路径而不解析符号链接(正如您所指出的,默认情况下会解析符号链接),可以使用realpath --no-symlinks <file>。我现在正在使用Fedora 35,并确认GNU realpath v8.32支持此功能。在旧版本中也可能有这个选项;我没有在任何基于Debian的发行版上进行测试/研究该选项何时添加。也没有测试BSD版本,因此不清楚Mac / OpenBSD /等的情况。因此,在Mac / homebrew方面,您需要使用gnu coretools来实现此目的。 - zpangwin

2
这不是对问题的回答,而是针对那些进行脚本编写的人:
echo `cd "$1" 2>/dev/null&&pwd||(cd "$(dirname "$1")";pwd|sed "s|/*\$|/${1##*/}|")`

它能正确地处理/.../等内容。它似乎也可以在OSX上工作。


2

我在我的系统上放置了以下脚本,并将其作为bash别名调用,以便快速获取当前目录中文件的完整路径:

#!/bin/bash
/usr/bin/find "$PWD" -maxdepth 1 -mindepth 1 -name "$1"

我不确定为什么,在OS X中,当脚本调用"$PWD"时,它会扩展为绝对路径。当在命令行上调用find命令时,它不会这样做。但是它实现了我的要求...请享用。


$PWD always has the working directory's absolute path. Also, find won't tolerate slashes, so you're not answering the question as asked. A quicker way to do what you're looking to do would simply be echo "$PWD/$1" - Adam Katz

2

对于目录,dirname 在遇到 ../ 时会返回 ./

nolan6000 的函数 可以修改解决这个问题:

get_abs_filename() {
  # $1 : relative filename
  if [ -d "${1%/*}" ]; then
    echo "$(cd ${1%/*}; pwd)/${1##*/}"
  fi
}

1
欢迎来到SO!对于小的更改和备注,添加评论到现有答案可能比添加答案更合适,特别是如果这个复制的答案缺少原始答案的很多其他信息。一旦您收集了更多的声望,您就可以向其他人的答案添加评论。现在,尝试通过提出独特、有价值的问题或提供独立的好答案来积累声望。 - cfi
1
由于您参考了nolan6000的答案,请注意,发布者dhardy已经评论说他不会接受nolan6000的答案,因为他不是在寻找一个脚本。所以严格来说,您的回答并没有回答问题。 - cfi
该函数可以添加到.profile文件中,因此可供交互式使用。 - adib
如果$1是一个普通的文件名,这个方法将不起作用:get_abs_filename foo -> 什么也没有(或者如果foo是一个目录,则为<current_dir>/foo/foo)。 - ivan_pozdeev

1
#! /bin/bash

file="$@"
realpath "$file" 2>/dev/null || eval realpath $(echo $file | sed 's/ /\\ /g')

这弥补了realpath的不足之处,将其存储在一个名为fullpath的shell脚本中。现在你可以调用:
$ cd && touch a\ a && rm A 2>/dev/null 
$ fullpath "a a"
/home/user/a a
$ fullpath ~/a\ a
/home/user/a a
$ fullpath A
A: No such file or directory.

它解决了什么问题?realpath“foo bar”-> /home/myname/foo bar。如果您不想引用命令行参数,那不是realpath的错。 - ivan_pozdeev
同意,它更多是一个方便的封装。 - ShellFish

1

在Ruby中获取绝对路径的另一种方法:

realpath() {ruby -e "require 'Pathname'; puts Pathname.new('$1').realpath.to_s";}

适用于没有参数(当前文件夹)以及相对和绝对文件或文件夹路径作为参数。


1
如果您的脚本需要存在bash或兼容bash的shell,则Alexander Klimetschek的答案是可以的。它不能与仅符合POSIX的shell一起使用。
此外,当最终文件是根目录中的文件时,输出将是“//file”,这在技术上并不是不正确的(系统会将双“/”视为单个“/”),但看起来很奇怪。
下面是一个适用于每个符合POSIX标准的shell的版本,它使用的所有外部工具也都是POSIX标准所需的,并且明确处理了根文件情况:
#!/bin/sh

abspath ( ) {
    if [ ! -e "$1" ]; then
        return 1
    fi

    file=""
    dir="$1"
    if [ ! -d "$dir" ]; then
        file=$(basename "$dir")
        dir=$(dirname "$dir")
    fi

    case "$dir" in
        /*) ;;
        *) dir="$(pwd)/$dir"
    esac
    result=$(cd "$dir" && pwd)

    if [ -n "$file" ]; then
        case "$result" in
            */) ;;
             *) result="$result/"
        esac
        result="$result$file"
    fi

    printf "%s\n" "$result"
}

abspath "$1"

将其放入文件并使其可执行,您就有了一个CLI工具,可以快速获取文件和目录的绝对路径。或者只需复制该函数并在自己的POSIX兼容脚本中使用它。它将相对路径转换为绝对路径,并按原样返回绝对路径。
有趣的修改:
如果您将result=$(cd "$dir" && pwd)替换为result=$(cd "$dir" && pwd -P),则路径中到最终文件的所有符号链接也将解析。
如果您对第一个修改不感兴趣,可以通过提前返回来优化绝对情况:
abspath ( ) {
    if [ ! -e "$1" ]; then
        return 1
    fi

    case "$1" in
        /*)
            printf "%s\n" "$1"
            return 0
    esac

    file=""
    dir="$1"
    if [ ! -d "$dir" ]; then
        file=$(basename "$dir")
        dir=$(dirname "$dir")
    fi

    result=$(cd "$dir" && pwd)

    if [ -n "$file" ]; then
        case "$result" in
            */) ;;
            *) result="$result/"
        esac
        result="$result$file"
    fi

    printf "%s\n" "$result"
}

既然问题会出现:为什么要使用printf而不是echo

echo主要用于将消息打印到标准输出(stdout)中,但很多脚本编写者依赖的echo行为实际上没有被规定。即使是著名的-n也没有被标准化,或者使用\t表示制表符也没有被标准化。POSIX标准说:

要写入标准输出的字符串。如果第一个操作数是-n,或者任何操作数包含字符,结果由实现定义
- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html

因此,每当您想要将某些内容写入stdout并且它不是为了向用户打印消息时,建议使用printf,因为printf的行为完全被定义。我的函数使用stdout传递结果,这不是用户的消息,因此只有使用printf才能保证完美的可移植性。


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