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

2024-10-18 09:00:00
admin
原创
68
摘要:问题描述:我想/foo/bar/..转型/foo是否有一个 bash 命令可以执行此操作?编辑:就我的实际情况而言,该目录确实存在。解决方案 1:如果你想从路径中截取部分文件名,“dirname”和“basename”是你的好朋友,“realpath”也很方便。dirname /foo/bar/baz # ...

问题描述:

我想/foo/bar/..转型/foo

是否有一个 bash 命令可以执行此操作?


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


解决方案 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替代方案

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

readlink -f /path/here/.. 

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

工作原理相同

realpath -s /path/here/../../

因为该路径不需要存在才能被规范化。

解决方案 2:

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

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

而且效果很好。

解决方案 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
", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH
", s_pMyName);
    exit(1);
}

解决方案 4:

一个可移植且可靠的解决方案是使用 Python,它几乎在任何地方都预装了(包括 Darwin)。您有两个选择:

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

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

  1. realpath返回绝对路径,并在此过程中解析符号链接,生成规范路径:

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

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

解决方案 5:

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

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

解决方案 6:

老问题,但如果你在 shell 级别处理完整路径名,还有更简单的方法:

   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 "$( dirname "$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 上得到一个错误。

解决方案 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"
} 

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

解决方案 9:

我最近的解决方案是:

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

根据 Tim Whitcomb 的回答。

解决方案 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

解决方案 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)上不可用。以下是从 Linux Journal 的一篇相当老的文章(2009 年)中提取的简单配方,非常易于移植:

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

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

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

解决方案 14:

我制作了一个内置函数来处理这个问题,重点是尽可能提高性能(为了好玩)。它不解析符号链接,因此它与 基本相同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
'` instead
  done
  return $r;
}

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

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

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

解决方案 15:

尝试一下我们新的 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 函数。跨平台试用,并帮助改进。

解决方案 16:

根据@Andre 的回答,如果有人想要一个无循环、完全基于字符串操作的解决方案,我可能会有一个稍微好一点的版本。它对那些不想取消引用任何符号链接的人来说也很有用,这是使用realpath或 的缺点readlink -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

解决方案 17:

如果您只想规范化一个路径,无论存在与否,而不触及文件系统,不解析任何链接,也不需要外部实用程序,这里有一个从 Python 翻译而来的纯Bashposixpath.normpath函数。

#!/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) || (\n      ${#comps[@]} -gt 0 && ${comps[-1]} == '..') ]]; then
      comps+=("${comp}")
    elif ((${#comps[@]})); then
      unset 'comps[-1]'
    fi
  done
  comp="${initial_slashes}${comps[*]}"
  printf '%s
' "${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:

我需要一个可以解决这三个问题的解决方案:

  • 可在普通 Mac 上运行。realpath并且readlink -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 "

解决方案 19:

根据 loveborg 的优秀 python 代码片段,我写了以下内容:

#!/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

解决方案 20:

FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

即使文件不存在,此方法也能奏效。它要求包含该文件的目录存在。

解决方案 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:

对于绝对的、规范化的、可能缺失的路径,我使用了:

"/$(realpath -m --relative-to / SOME_PATH)"

# example
echo "/$(realpath -m --relative-to / /etc/bogus/..)"

您可以看到更多选项realpath --help

解决方案 24:

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

因此对于像“~/Documents”这样的目录:

你可以运行这个:

stat -f %N ~/Documents

获取完整路径:

/Users/me/Documents

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

stat -f %Y example_symlink

可能会返回如下结果:

/usr/local/sbin/example_symlink

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

解决方案 25:

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

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用