关于linux:如何在Bash中规范化文件路径?

您所在的位置:网站首页 文件路径标准化处理java 关于linux:如何在Bash中规范化文件路径?

关于linux:如何在Bash中规范化文件路径?

2024-03-04 11:43| 来源: 网络整理| 查看: 265

我想把/foo/bar/..改成/foo

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

编辑:在我的实际案例中,目录确实存在。

相关讨论 如果/foo/bar甚至/foo确实存在,或者您只对根据路径名规则进行字符串操作感兴趣,这有关系吗? @特瓦尔伯格…这有点做作… @卡米洛马丁根本没有做作——它完全按照问题的要求——把/foo/bar/..转换成/foo,并使用bash命令。如果还有其他没有说明的要求,那么也许它们应该…… @特瓦尔伯格,你做了太多的TDD-?

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

12345678910dirname /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,您可以尝试

1readlink -f /path/here/..

阿尔索

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

工作原理与

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

在这一点上,路径不需要存在来进行规范化。

相关讨论 对于那些需要OS X解决方案的人,请查看下面的AdamLiss的答案。 stackoverflow.com/a/17744637/999943这是一个强烈相关的答案!我在一天之内遇到了这两个QA职位,我想把它们联系在一起。 realpath似乎已于2012年添加到coreutils中。请参阅github.com/coreutils/coreutils/commits/master/src/realpath.c上的文件历史记录。 readlink也不是posix标准。

我不知道是否有一个直接的bash命令来执行这个操作,但是我通常会这样做

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

而且效果很好。

相关讨论 这将规范化但不会解析软链接。这可能是一个bug,也可能是一个特性。-) @亚当看到人的真实路径:真实路径-s做的一样:) 如果定义了$cdpath,也会有问题;因为"cd foo"将切换到$cdpath的子目录中的任何"foo"目录,而不仅仅是当前目录中的"foo"。我认为您需要执行以下操作:cdpath="cd"$dirtonormalize";;pwd-p。 我不知道CDPATH——看起来很整洁!你的观点绝对是需要记住的——不过,我可以想象一些情况,能够引用cdpath中的目录正是你想要的。难道不应该使用一个领先的./来修复它吗? 蒂姆的答案绝对是最简单和最便携的。cdpath很容易处理:dir="$(unset cdpath;cd"$dir";pwd)" 如果不存在直接化,这可能非常危险(rm -rf $normalDir)。 是的,根据@davidlevins的评论,最好使用&&。 如果您执行pwd -P,它将解析符号链接。 这对我来说是最好的答案,因为我正在编写一个需要在OSX和Linux上使用的脚本。

试试realpath。以下是其全部来源,特此捐赠给公共领域。

123456789101112131415161718192021222324252627282930313233343536// 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 #include #include #include   #include 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 realpath.c(3)构建它:gcc-o realpath realpath.c(4)运行它:./realpath path/to/make/canonical 感谢您花时间发布非Debian/Ubuntu发行版要使用的源代码。奇妙的是,在发明9年后,一个Q&A数据库可以直接从作者的键盘上找到它。 不客气!很高兴这仍然是相关和有用的。有趣的是,既然我们更加警惕缓冲区溢出攻击,那么代码是如何演变的。如果我今天写这篇文章,我会仔细检查并限制args的长度。 多糟糕的执照啊。即使是开源项目也不能使用这个代码,以防以后有人为了利润而使用它。与所有开放源代码许可证不兼容。

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

1MY_PATH=$(readlink -f"$0") 相关讨论 BSD没有-f标志,这意味着即使在最新的MacOS Mojave和许多其他系统上,也会失败。忘记使用-f如果你想要可移植性,很多操作系统都会受到影响。 索林。问题不在于Mac,而在于Linux。

一个可移植和可靠的解决方案是使用python,它在几乎所有地方(包括达尔文)都预先安装了。您有两种选择:

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在OSX下不可用。python应该在大多数平台上。 为了澄清这一点,可以在OSX上使用readlink,而不使用-f选项。这里讨论了便携式解决方案。 如果你不想跟踪链接,这是唯一明智的解决方案,这真的让我很困惑。Unix就像我的脚。

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

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

1abspath=$(readlink -f $path)

要获取必须与所有父目录一起存在的目录的绝对路径,请执行以下操作:

1abspath=$(readlink -e $path)

要规范化给定的路径,并遵循符号链接(如果它们恰好存在),但如果不存在,则忽略缺少的目录并返回路径,它是:

1abspath=$(readlink -m $path)

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

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

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

相关讨论 如果可以的话,readlink是一个很好的选择。OS X版本不支持-e或-f选项。在前三个示例中,您应该在$path周围使用双引号来处理文件名中的空格或通配符。+1表示参数扩展,但这存在安全漏洞。如果path是空的,这将cd转到您的主目录。你需要双引号。abspath=$(cd"${path%/*}" && echo"$PWD/${path##*/}") 这只是一个例子。如果您非常注重安全性,那么您实际上不应该使用bash或任何其他shell变体。此外,在跨平台兼容性方面,bash也有自己的问题,并且在主要版本之间的功能更改方面也有问题。OSX只是许多与shell脚本相关的平台之一,更不用说它是基于BSD的。当您必须是真正的多平台时,您需要符合POSIX,所以参数扩展确实超出了窗口。看一下Solaris或HP-UX。 这里没有任何冒犯的意思,但是指出诸如这类模糊的问题是很重要的。我只是想快速回答这个小问题,如果不是上面的评论,我会相信任何/所有输入的代码。在这些bash讨论中支持OS-X也很重要。不幸的是,在OS-X上有很多命令不受支持,许多论坛在讨论bash时都认为这是理所当然的,这意味着我们将继续得到许多跨平台的问题,除非它早晚得到处理。

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

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

当CD发生在子shell中时,它不会影响主脚本。

假设您的shell内置命令接受-l和-p,有两种变体:

12   abspath="$( cd -P"$path" && pwd -P )"    #physical path with resolved symlinks    abspath="$( cd -L"$path" && pwd -L )"    #logical path preserving symlinks

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

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

12name0="$(basename"$0")";                  #base name of script dir0="$( cd"$( dirname"$0" )" && pwd )"; #absolute starting dir

使用cd可以确保您始终拥有绝对目录,即使脚本是由诸如./script.sh之类的命令运行的,而没有cd/pwd,这些命令通常只给出……如果脚本稍后执行CD,则无效。

正如AdamLiss所指出的,realpath并不是与每个发行版捆绑在一起的。很遗憾,因为这是最好的解决办法。提供的源代码非常好,我现在可能会开始使用它。以下是我到目前为止一直在使用的内容,我在这里只是为了完整性而分享:

1234567get_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只解析路径的目录部分中的符号链接,而不解析basename部分。

我最近的解决方案是:

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

根据蒂姆·惠特科姆的回答。

相关讨论 如果参数不是目录,我怀疑这会失败。假设我想知道哪里/UR/BI/Java导致? 如果你知道这是一个文件,你可以给pushd $(dirname /usr/bin/java)一次尝试。

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

如果你真的想使用symlinks,那么readlink就可以了。但是也有一个仅仅规范化./和../和//序列的用例,这些序列完全可以在语法上完成,而无需规范化符号链接。readlink对这个没有好处,realpath也没有好处。

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

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

一个sed脚本似乎是一个很好的选择,除非你不能在不使用诸如perl之类的东西的情况下迭代替换序列(/foo/bar/baz/../..->/foo/bar/..->/foo,这在所有系统上都是不安全的,或者使用一些丑陋的循环将sed的输出与输入进行比较。

FWWW,使用Java(JDK 6 +)的一个内衬:

1jrunscript -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规范化类似。在这些情况下,如果在稍后应用结果的目录附近碰巧有符号链接,那么结果通常是不正确的,这是一个固有的限制。

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748test -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`

我参加派对迟到了,但这是我在阅读了一堆这样的线索后制定的解决方案:

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

这将解析$1的绝对路径,使用~,保持符号链接在它们所在的路径中,并且不会干扰您的目录堆栈。它返回完整的路径,如果不存在则不返回任何内容。它期望$1是一个目录,如果不是,它可能会失败,但这是一个很容易检查自己。

尝试我们在Github上放置的新bash库产品realpath lib,以供免费和无障碍使用。它被完整地记录下来,是一个很好的学习工具。

它解析局部、相对和绝对路径,除了bash 4+之外没有任何依赖关系;因此它应该可以在任何地方工作。它是免费的,干净的,简单的和有教育意义的。

你可以做到:

1get_realpath

此功能是库的核心:

1234567891011121314151617181920212223242526function 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 }

它还包含获取目录名、获取文件名、获取词干名和验证路径的函数。跨平台尝试,并帮助改进它。

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

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

1234567891011121314shopt -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功能时,必须同时指定所需的版本和非默认选项,因为这些细节在OSS之间可能有所不同。例如,MacOSX(约塞米蒂)的最新版本只附带了过时的bash(3.2)版本。 抱歉,@ricovox;我已经更新了。我很想知道你那里的bash的确切版本。以上公式(更新)适用于Centos 5.8,它与bash 3.2.25一起提供 不好意思弄混了。当我打开extglob后,这段代码在我的MacOSXbash(3.2.57)版本上确实有效。我关于bash版本的注释是一个更一般的注释(实际上它更适用于这里关于bash中regex的另一个答案)。 不过,我很感激你的回答。我把它作为自己的基地。顺便说一句,我注意到你的几个失败的例子:(1)相对路径hello/../world(2)文件名为/hello/..world的点(3)双斜线/hello//../world的点(4)双斜线/hello//./world或/hello/.//world的点(5)当前的父项:/hello/./../world/的(6)父项后的父项:/hello/../../world等——其中一些可以e通过使用循环进行修正,直到路径停止改变。(同时从末端取下dir/../,而不是/dir/..,但从末端取下dir/..。

realpath的问题在于它在BSD(或OSX)上不可用。下面是从Linux期刊上一篇相当古老的(2009年)文章中提取的一个简单配方,它非常可移植:

12345678910function normpath() {   # Remove all /./ sequences.   local path=${1//\/.\//\/}   # Remove dir/.. sequences.   while [[ $path =~ ([^/][^/]*/\.\./) ]]; do     path=${path/${BASH_REMATCH[0]}/}   done   echo $path }

注意,这个变量也不需要存在路径。

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

在股票市场工作。realpath和readlink -f是附加项。 解析符号链接 有错误处理

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

1234567891011121314#!/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")"

下面是一个简短的测试用例,在路径中有一些扭曲的空间来充分地执行报价

12345678mkdir -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"

基于Loveborg优秀的python代码片段,我写了以下内容:

123456789101112131415#!/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 12FILEPATH="file.txt" echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

即使文件不存在,也可以这样做。它确实要求包含该文件的目录存在。

我知道这是个古老的问题。我仍在提供另一种选择。最近我遇到了同样的问题,没有发现任何现有的和可移植的命令可以做到这一点。所以我写了下面的shell脚本,其中包含一个可以实现这个技巧的函数。

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

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

所以对于像"~/documents"这样的目录:

您可以运行此:

stat -f %N ~/Documents

要获得完整路径:

/Users/me/Documents

对于symlinks,可以使用%y格式选项:

stat -f %Y example_symlink

可能会返回如下结果:

/usr/local/sbin/example_symlink

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

相关讨论 stat -f %N ~/Documents线是一条红鲱鱼……你的外壳正在用/Users/me/Documents替换~/Documents,stat只是逐字打印它的论点。

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

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3