如何规范化 Bash 中的文件路径?

Posted

技术标签:

【中文标题】如何规范化 Bash 中的文件路径?【英文标题】:How do you normalize a file path in Bash? 【发布时间】:2010-09-22 00:43:04 【问题描述】:

我想将/foo/bar/.. 转换为/foo

有没有 bash 命令可以做到这一点?


编辑:在我的实际情况下,该目录确实存在。

【问题讨论】:

/foo/bar 甚至 /foo 是否真的存在是否重要,或者您只对根据路径名规则的字符串操作方面感兴趣? @twalberg ...这有点做作... @CamiloMartin 一点也不做作 - 它完全按照问题的要求进行 - 将 /foo/bar/.. 转换为 /foo,并使用 bash 命令。如果还有其他没有说明的要求,那么也许它们应该是…… @twalberg 你已经做了太多的 TDD -_-' 【参考方案1】:

如果你想从路径中选择文件名的一部分,“dirname”和“basename”是你的朋友,“realpath”也很方便。

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath 替代品

如果你的shell不支持realpath,你可以试试

readlink -f /path/here/.. 

还有

readlink -m /path/there/../../ 

工作原理与

相同
realpath -s /path/here/../../

因为路径不需要存在就可以标准化。

【讨论】:

对于那些需要 OS X 解决方案的人,请查看下面 Adam Liss 的回答。 ***.com/a/17744637/999943 这是一个高度相关的答案!我在一天之内遇到了这两个 QA 帖子,我想将它们链接在一起。 realpath 似乎已在 2012 年添加到 coreutils。请参阅 github.com/coreutils/coreutils/commits/master/src/realpath.c 的文件历史记录。 realpathreadlink 都来自 GNU 核心实用程序,因此很可能您要么两者都得到,要么没有。如果我没记错的话,Mac 上的 readlink 版本与 GNU 版本略有不同:-\ 不要忘记realpath 上的-m 选项,即使路径不存在也会正常化【参考方案2】:

我不知道有没有直接的 bash 命令可以做到这一点,但我通常会这样做

normalDir="`cd "$dirToNormalize";pwd`"
echo "$normalDir"

而且效果很好。

【讨论】:

这将规范化但不解析软链接。这可能是错误或功能。 :-) 如果定义了$CDPATH也有问题;因为“cd foo”将切换到任何作为 $CDPATH 子目录的“foo”目录,而不仅仅是当前目录中的“foo”。我认为您需要执行以下操作: CDPATH="" cd "$dirToNormalize" && pwd -P. Tim 的回答绝对是最简单、最便携的。 CDPATH 好处理:dir="$(unset CDPATH && cd "$dir" && pwd)" 如果 dirToNormalize 不存在,这可能非常危险 (rm -rf $normalDir)! 是的,根据@DavidBlevins 的评论,最好使用&&【参考方案3】:

试试realpath。以下是全文出处,特此捐赠给公共领域。

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])

    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
    

void usage(void)

    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);

【讨论】:

gnu.org/software/coreutils 用于 readlink,但 realpath 来自这个包:packages.debian.org/unstable/utils/realpath 这是 ubuntu/BSD 的标准,不是 Centos/OSX 的标准 你真的应该用“奖励:它是一个 bash 命令,并且随处可用!”关于realpath 的部分。它也恰好是我最喜欢的,但不是从源代码编译您的工具。这都是重量级的,大多数时候你只想要readlink -f...顺便说一句,readlink 也不是 bash 内置,而是 Ubuntu 上coreutils 的一部分。 你说你要把它捐赠给公共领域,但标题也写着“用于任何非盈利用途”——你真的打算把它捐赠给公共领域吗?这意味着它可以用于商业营利目的以及非营利目的...... 构建一个 OS X 的步骤:(1) 从此页面复制代码 (2) 将代码粘贴到文件中:pbpaste > realpath.c (3) 构建它:gcc -o realpath realpath.c (4) 运行它:./realpath path/to/make/canonical【参考方案4】:

一个可移植且可靠的解决方案是使用 python,它几乎随处可见(包括 Darwin)。你有两个选择:

    abspath 返回绝对路径但不解析符号链接:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

    realpath 返回一个绝对路径,这样做会解析符号链接,生成规范路径:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

在每种情况下,path/to/file 可以是相对路径或绝对路径。

【讨论】:

谢谢,这是唯一有效的方法。 readlink 或 realpath 在 OS X 下不可用。Python 应该在大多数平台上。 澄清一下,readlink 在 OS X 上可用,只是没有 -f 选项。讨论了便携式解决方法here。 如果您不想关注链接,这让我难以置信,这是唯一合理的解决方案。 Unix 方式我的脚。【参考方案5】:

使用 coreutils 包中的 readlink 实用程序。

MY_PATH=$(readlink -f "$0")

【讨论】:

BSD 没有 -f 标志,这意味着即使在最新的 MacOS Mojave 和许多其他系统上也会失败。如果您想要可移植性,请忘记使用 -f,很多操作系统都会受到影响。 @sorin。问题不在于 Mac,而在于 Linux。【参考方案6】:

老问题,但有更简单的方法如果您在外壳级别处理完整路径名

 abspath="$( cd "$path" && pwd )"

由于 cd 发生在子 shell 中,它不会影响主脚本。

假设您的 shell 内置命令接受 -L 和 -P,两种变体是:

abspath="$( cd -P "$path" && pwd -P )" #物理路径解析符号链接 abspath="$( cd -L "$path" && pwd -L )" #逻辑路径保留符号链接

就个人而言,除非出于某种原因我对符号链接着迷,否则我很少需要这种后一种方法。

仅供参考:获取脚本起始目录的变体,即使脚本稍后更改它的当前目录,它也可以工作。

name0="$(basename "$0")"; #脚本的基本名称
dir0="$( cd "$( 目录名 "$0" )" && pwd )"; #绝对起始目录

使用 CD 可确保您始终拥有绝对目录,即使脚本是由 ./script.sh 等命令运行的,如果没有 cd/pwd,通常只会给出 .. 如果脚本执行 cd稍后的。

【讨论】:

【参考方案7】:

readlink 是获取绝对路径的 bash 标准。如果路径或路径不存在,它还具有返回空字符串的优点(给定这样做的标志)。

要获取可能存在也可能不存在但其父级确实存在的目录的绝对路径,请使用:

abspath=$(readlink -f $path)

获取必须与所有父级一起存在的目录的绝对路径:

abspath=$(readlink -e $path)

要规范化给定路径并遵循符号链接(如果它们碰巧存在),否则忽略丢失的目录并返回路径,它是:

abspath=$(readlink -m $path)

唯一的缺点是 readlink 会跟随链接。如果您不想关注链接,可以使用此替代约定:

abspath=$(cd $path%/* && echo $PWD/$path##*/)

这将 chdir 到 $path 的目录部分并打印当前目录以及 $path 的文件部分。如果 chdir 失败,则会在 stderr 上得到一个空字符串和一个错误。

【讨论】:

readlink 是一个不错的选择(如果可用)。 OS X 版本不支持-e-f 选项。在前三个示例中,您应该在 $path 周围加上双引号来处理文件名中的空格或通配符。 +1 用于参数扩展,但这有一个安全漏洞。如果path 为空,这将cd 到您的主目录。你需要双引号。 abspath=$(cd "$path%/*" &amp;&amp; echo "$PWD/$path##*/") 这只是一个例子。如果您非常重视安全性,那么您真的不应该使用 bash 或任何其他 shell 变体。此外,bash 在跨平台兼容性方面也有其自身的问题,以及主要版本之间的功能更改问题。 OSX 只是存在与 shell 脚本相关问题的众多平台之一,更不用说它基于 BSD。当你必须是真正的多平台时,你需要兼容 POSIX,所以参数扩展真的是不可能的。有时间看看 Solaris 或 HP-UX。 这里没有冒犯的意思,但指出诸如此类的晦涩问题很重要。我只是想快速回答这个微不足道的问题,如果不是上面的评论,我会相信带有任何/所有输入的代码。在这些 bash 讨论中支持 OS-X 也很重要。不幸的是,有很多命令在 OS-X 上不受支持,许多论坛在讨论 Bash 时认为这是理所当然的,这意味着我们将继续遇到很多跨平台问题,除非尽早解决。 【参考方案8】:

正如 Adam Liss 所指出的,realpath 并未与每个发行版捆绑在一起。这是一种耻辱,因为它是最好的解决方案。提供的源代码很棒,我现在可能会开始使用它。这是我到目前为止一直在使用的,我在这里分享只是为了完整:

get_abs_path() 
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
 

如果您希望它解析符号链接,只需将 pwd 替换为 pwd -P

【讨论】:

在这种情况下使用pwd -P 选项的一个问题...考虑一下如果$(basename "$1") 是指向另一个目录中文件的符号链接会发生什么。 pwd -P 仅解析路径目录部分中的符号链接,而不解析基本名称部分。【参考方案9】:

我最近的解决方案是:

pushd foo/bar/..
dir=`pwd`
popd

基于 Tim Whitcomb 的回答。

【讨论】:

如果参数不是目录,我怀疑这会失败。假设我想知道 /usr/bin/java 通向哪里? 如果你知道这是一个文件,你可以试试pushd $(dirname /usr/bin/java)【参考方案10】:

不完全是答案,但可能是后续问题(原始问题不明确):

readlink 如果您真的想遵循符号链接,那很好。但也有一个用例仅对 ./../// 序列进行规范化,这可以在纯语法上完成, 规范化符号链接。 readlink 不适合这个,realpath 也不是。

for f in $paths; do (cd $f; pwd); done

适用于现有路径,但适用于其他路径。

sed 脚本似乎是一个不错的选择,除了你不能在不使用 Perl 之类的东西的情况下迭代地替换序列 (/foo/bar/baz/../.. -> /foo/bar/.. -> /foo),这是不安全的假设在所有系统上,或使用一些丑陋的循环来比较 sed 的输出与其输入。

FWIW,使用 Java (JDK 6+) 的单线器:

jrunscript -e 'for (var i = 0; i < arguments.length; i++) println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))' $paths

【讨论】:

realpath 有一个 -s 选项不解析符号链接,只解析对 /.//../ 的引用并删除额外的 / 字符。当与-m 选项结合使用时,realpath 仅对文件名进行操作,而不涉及任何实际文件。它听起来是完美的解决方案。但是很遗憾,realpath 在许多系统上仍然缺失。 在涉及符号链接时,无法在语法上删除 .. 组件。如果two 是指向/foo/bar 的符号链接,则/one/two/../three/one/three 不同。 @jrw32982 是的,正如我在回复中所说,这是针对想要或需要符号链接规范化的用例。 @JesseGlick 这不仅仅是您是否要规范化符号链接的情况。您的算法实际上产生了错误的答案。为了使您的答案正确,您必须先验地知道不涉及符号链接(或者它们仅具有某种形式)。您的回答说您不想想要规范化它们,而不是路径中不涉及符号链接。 有些用例必须在不假设任何固定的现有目录结构的情况下执行规范化。 URI 规范化是类似的。在这些情况下,如果在稍后应用结果的目录附近碰巧有符号链接,结果通常不会正确,这是一个固有的限制。【参考方案11】:

我迟到了,但这是我在阅读了一堆这样的帖子后精心设计的解决方案:

resolve_dir() 
        (builtin cd `dirname "$1/#~/$HOME"`'/'`basename "$1/#~/$HOME"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)

这将解析 $1 的绝对路径,与 ~ 配合使用,将符号链接保留在它们所在的路径中,并且不会弄乱您的目录堆栈。如果它不存在,则返回完整路径或不返回任何内容。它期望 $1 是一个目录,如果不是,它可能会失败,但你自己做一个简单的检查。

【讨论】:

【参考方案12】:

健谈,回答有点晚。我需要写一个,因为我被困在较旧的 RHEL4/5 上。 我处理绝对和相对链接,并简化 //、/./ 和 somedir/../ 条目。

test -x /usr/bin/readlink || readlink () 
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    


test -x /usr/bin/realpath || realpath () 
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in $inputpath//\// 
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "$target:0:1" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath


mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`

【讨论】:

【参考方案13】:

realpath 的问题在于它在 BSD(或 OSX 上)上不可用。这是从a rather old (2009) article from Linux Journal 中提取的一个简单配方,非常便携:

function normpath() 
  # Remove all /./ sequences.
  local path=$1//\/.\//\/

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=$path/$BASH_REMATCH[0]/
  done
  echo $path

请注意,此变体也要求路径存在。

【讨论】:

但这并不能解析符号链接。 Realpath 处理从根开始的路径,并随着它的进展遵循符号链接。所有这一切都是折叠父引用。【参考方案14】:

试试我们新的 Bash 库产品 realpath-lib,我们已将它放在 GitHub 上,免费且不受阻碍地使用。它有完整的文档记录,是一个很好的学习工具。

它解析本地、相对和绝对路径,除了 Bash 4+ 之外没有任何依赖项;所以它应该可以在任何地方工作。它免费、干净、简单且具有指导意义。

你可以这样做:

get_realpath <absolute|relative|symlink|local file path>

这个函数是库的核心:

function get_realpath() 

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "$1%/*")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"$1##*/"
return 0 # success


它还包含 get_dirname、get_filename、get_stemname 和 validate_path 函数。跨平台尝试,并帮助改进它。

【讨论】:

【参考方案15】:

根据@Andre 的回答,我可能有一个更好的版本,以防有人追求无循环、完全基于字符串操作的解决方案。对于那些不想取消引用任何符号链接的人也很有用,这是使用 realpathreadlink -f 的缺点。

它适用于 bash 版本 3.2.25 及更高版本。

shopt -s extglob

normalise_path() 
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="$path//\/*([!\/])\/\.\./"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="$path//@(\/\.\/|\/+(\/))//"
    # remove the last '/.'
    echo "$path%%/."


$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config

【讨论】:

这是个好主意,但我浪费了 20 分钟试图让它在各种不同版本的 bash 上工作。事实证明,需要打开 extglob shell 选项才能使其正常工作,并且默认情况下它不是。当谈到 bash 功能时,指定所需版本和非默认选项很重要,因为这些细节可能因操作系统而异。例如,最新版本的 Mac OSX (Yosemite) 仅附带过时版本的 bash (3.2)。 对不起@ricovox;我现在已经更新了这些。我很想知道你那里的 Bash 的确切版本。上面的公式(更新)适用于 CentOS 5.8,它带有 bash 3.2.25 很抱歉给您带来了困惑。一旦我打开了 extglob,这段代码就可以在我的 Mac OSX bash (3.2.57) 版本上运行。我关于 bash 版本的注释是一个更通用的注释(实际上更多地适用于此处关于 bash 中正则表达式的另一个答案)。 我很感激你的回答。我用它作为我自己的基础。顺便说一句,我注意到您的失败的几种情况:(1)相对路径hello/../world(2)文件名中的点/hello/..world(3)双斜杠后的点/hello//../world(4)双斜杠之前或之后的点/hello//./world/hello/.//world (5) 当前父后:/hello/./../world/ (6) 父后父:/hello/../../world 等 -- 其中一些可以通过使用循环进行修正,直到路径停止改变。 (同时删除dir/../,不是/dir/..,而是从末尾删除dir/..。)【参考方案16】:

我制作了一个仅内置函数来处理这个问题,重点是尽可能提高性能(为了好玩)。它不解析符号链接,所以和realpath -sm基本一样。

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath ()  
  $*+false &&  >&2 echo $FUNCNAME: missing operand; return 1; ;
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="$s%/*";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "$s:-/";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;

注意:此函数不处理以// 开头的路径,因为路径开头的两个双斜杠是实现定义的行为。但是,它可以很好地处理//// 等等。

这个函数似乎可以正确处理所有边缘情况,但可能还有一些我没有处理过的情况。

性能说明:当使用数千个参数调用时,abspath 的运行速度比realpath -sm 慢约 10 倍;当使用单个参数调用时,abspath 在我的机器上运行速度比 realpath -sm 快 110 倍以上,主要是因为不需要每次都执行新程序。

【讨论】:

【参考方案17】:

如果你只是想规范化一个路径,存在或不存在,不接触文件系统,不解析任何链接,不使用外部工具,这里是一个纯 Bash 函数,从 Python 的 @ 翻译而来987654321@.

#!/usr/bin/env bash

# Normalize path, eliminating double slashes, etc.
# Usage: new_path="$(normpath "$old_path")"
# Translated from Python's posixpath.normpath:
# https://github.com/python/cpython/blob/master/Lib/posixpath.py#L337
normpath() 
  local IFS=/ initial_slashes='' comp comps=()
  if [[ $1 == /* ]]; then
    initial_slashes='/'
    [[ $1 == //* && $1 != ///* ]] && initial_slashes='//'
  fi
  for comp in $1; do
    [[ -z $comp || $comp == '.' ]] && continue
    if [[ $comp != '..' || (-z $initial_slashes && $#comps[@] -eq 0) || (\
      $#comps[@] -gt 0 && $comps[-1] == '..') ]]; then
      comps+=("$comp")
    elif (($#comps[@])); then
      unset 'comps[-1]'
    fi
  done
  comp="$initial_slashes$comps[*]"
  printf '%s\n' "$comp:-."

例子:

new_path="$(normpath '/foo/bar/..')"
echo "$new_path"
# /foo

normpath "relative/path/with trailing slashs////"
# relative/path/with trailing slashs

normpath "////a/../lot/././/mess////./here/./../"
# /lot/mess

normpath ""
# .
# (empty path resolved to dot)

就个人而言,我无法理解为什么 Shell(一种经常用于处理文件的语言)不提供处理路径的基本功能。在 python 中,我们有很好的库,如 os.path 或 pathlib,它们提供了一大堆工具来提取文件名、扩展名、基本名、路径段、拆分或连接路径,获取绝对或规范化路径,确定路径之间的关系,做任何事情都没有太多的大脑。他们会处理边缘情况,而且很可靠。在 Shell 中,要执行任何这些操作,我们要么调用外部可执行文件,要么必须使用这些极其原始和神秘的语法重新发明***......

【讨论】:

【参考方案18】:

基于loveborg优秀的python sn-p,我写了这个:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done

【讨论】:

【参考方案19】:
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

即使文件不存在,这也有效。它确实需要包含该文件的目录存在。

【讨论】:

GNU Realpath 也不需要路径中的最后一个元素存在,除非您使用 realpath -e【参考方案20】:

我需要一个能同时满足这三个方面的解决方案:

在普通 Mac 上工作。 realpathreadlink -f 是插件 解析符号链接 有错误处理

没有一个答案同时包含#1 和#2。我添加了 #3 以节省其他人进一步的牦牛剃须。

#!/bin/bash

P="$1?Specify a file path"

[ -e "$P" ] ||  echo "File does not exist: $P"; exit 1; 

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

这是一个简短的测试用例,路径中有一些扭曲的空格,以充分执行引用

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "

【讨论】:

【参考方案21】:

我知道这是一个古老的问题。我仍在提供替代方案。最近我遇到了同样的问题,发现没有现有的便携式命令可以做到这一点。因此,我编写了以下 shell 脚本,其中包含一个可以解决问题的函数。

#! /bin/sh                                                                                                                                                

function normalize 
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

【讨论】:

【参考方案22】:

由于所提供的解决方案都不适合我,因此在文件不存在的情况下,我实现了我的想法。 André Anjos 的解决方案存在以 ../../ 开头的路径被错误解析的问题。例如 ../../a/b/ 变成了 a/b/。

function normalize_rel_path()
  local path=$1
  result=""
  IFS='/' read -r -a array <<< "$path"
  i=0
  for (( idx=$#array[@]-1 ; idx>=0 ; idx-- )) ; do
    c="$array[idx]"
    if [ -z "$c" ] || [[ "$c" == "." ]];
    then
      continue
    fi
    if [[ "$c" == ".." ]]
    then
      i=$((i+1))
    elif [ "$i" -gt "0" ];
    then
      i=$((i-1))
    else
      if [ -z "$result" ];
      then
        result=$c
      else
        result=$c/$result
      fi
    fi
  done
  while [ "$i" -gt "0" ]; do
    i=$((i-1))
    result="../"$result
  done  
  unset IFS
  echo $result

【讨论】:

【参考方案23】:

我今天发现你可以使用stat 命令来解析路径。

所以对于像“~/Documents”这样的目录:

你可以运行这个:

stat -f %N ~/Documents

获取完整路径:

/Users/me/Documents

对于符号链接,您可以使用 %Y 格式选项:

stat -f %Y example_symlink

这可能会返回如下结果:

/usr/local/sbin/example_symlink

格式选项在其他版本的 *NIX 上可能不同,但这些在 OSX 上对我有用。

【讨论】:

stat -f %N ~/Documents 行是一条红鲱鱼...你的外壳正在用/Users/me/Documents 替换~/Documents,而stat 只是逐字打印它的参数。【参考方案24】:

使用node.js的简单解决方案:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));

【讨论】:

以上是关于如何规范化 Bash 中的文件路径?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 bash 脚本将消息记录到特定路径中的日志文件

使用 bash,如何从目录中的所有文件中创建类路径?

如何将文件路径作为 bash 脚本中的参数传递给函数? [复制]

仅从 Bash 脚本中的路径获取文件名 [重复]

linux 如何获取当前文件路径(source如何获取当前文件路径 bash如何获取当前文件路径)

如何使用 bash 在日志文件中存储备份文件的绝对路径?