How do you normalize a file path in Bash?
我想把
是否有一个bash命令执行此操作?
编辑:在我的实际案例中,目录确实存在。
如果你想从路径中选择部分文件名,"dirname"和"basename"是你的朋友,"realpath"也很方便。
1 2 3 4 5 6 7 8 9 10 | 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 |
如果shell不支持
1 | readlink -f /path/here/.. |
阿尔索
1 | readlink -m /path/there/../../ |
工作原理与
1 | realpath -s /path/here/../../ |
在这一点上,路径不需要存在来进行规范化。
我不知道是否有一个直接的bash命令来执行这个操作,但是我通常会这样做
1 2 | normalDir="`cd"${dirToNormalize}";pwd`" echo"${normalDir}" |
而且效果很好。
试试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 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); } |
使用coreutils包中的readlink实用程序。
1 | MY_PATH=$(readlink -f"$0") |
一个可移植和可靠的解决方案是使用python,它在几乎所有地方(包括达尔文)都预先安装了。您有两种选择:
在每种情况下,
要获取某个目录的绝对路径,该目录可能存在,也可能不存在,但其父目录确实存在,请使用:
1 | abspath=$(readlink -f $path) |
要获取必须与所有父目录一起存在的目录的绝对路径,请执行以下操作:
1 | abspath=$(readlink -e $path) |
要规范化给定的路径,并遵循符号链接(如果它们恰好存在),但如果不存在,则忽略缺少的目录并返回路径,它是:
1 | abspath=$(readlink -m $path) |
唯一的缺点是readlink会跟随链接。如果不想使用链接,可以使用此替代约定:
1 | abspath=$(cd ${path%/*} && echo $PWD/${path##*/}) |
这将chdir指向$path的目录部分,并打印当前目录以及$path的文件部分。如果chdir失败,您将得到一个空字符串和stderr上的一个错误。
旧问题,但如果您在shell级别处理完整路径名,则有更简单的方法:
1 | abspath="$( cd"$path" && pwd )" |
当CD发生在子shell中时,它不会影响主脚本。
假设您的shell内置命令接受-l和-p,有两种变体:
1 2 | abspath="$( cd -P"$path" && pwd -P )" #physical path with resolved symlinks abspath="$( cd -L"$path" && pwd -L )" #logical path preserving symlinks |
就个人而言,我很少需要这种后期的方法,除非我出于某种原因对符号链接着迷。
仅供参考:获取脚本的起始目录时的变化,即使脚本稍后更改了当前目录,也可以正常工作。
1 2 | name0="$(basename"$0")"; #base name of script dir0="$( cd"$( dirname"$0" )" && pwd )"; #absolute starting dir |
使用cd可以确保您始终拥有绝对目录,即使脚本是由诸如./script.sh之类的命令运行的,而没有cd/pwd,这些命令通常只给出……如果脚本稍后执行CD,则无效。
正如AdamLiss所指出的,
1 2 3 4 5 6 7 | get_abs_path() { local PARENT_DIR=$(dirname"$1") cd"$PARENT_DIR" local ABS_PATH="$(pwd)"/"$(basename"$1")" cd - >/dev/null echo"$ABS_PATH" } |
如果您希望它解析符号链接,只需将
我最近的解决方案是:
1 2 3 | pushd foo/bar/.. dir=`pwd` popd |
根据蒂姆·惠特科姆的回答。
不完全是答案,但可能是后续问题(原始问题不明确):
如果你真的想使用symlinks,那么
1 | for f in $paths; do (cd $f; pwd); done |
适用于现有路径,但适用于其他路径。
一个
FWWW,使用Java(JDK 6 +)的一个内衬:
1 | jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths |
健谈,回答有点晚。我需要写一封信,因为我困在旧的瑞尔4/5上。我处理绝对链接和相对链接,并简化//、//和somedir/./条目。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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` |
我参加派对迟到了,但这是我在阅读了一堆这样的线索后制定的解决方案:
1 2 3 | resolve_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+之外没有任何依赖关系;因此它应该可以在任何地方工作。它是免费的,干净的,简单的和有教育意义的。
你可以做到:
1 | get_realpath |
此功能是库的核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 } |
它还包含获取目录名、获取文件名、获取词干名和验证路径的函数。跨平台尝试,并帮助改进它。
根据@andre的回答,我可能会有一个稍微好一点的版本,以防有人想要一个完全基于字符串操作的无循环解决方案。对于那些不想取消引用任何符号链接的人也很有用,这是使用
它适用于bash 3.2.25及更高版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
1 2 3 4 5 6 7 8 9 10 | function 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以节省其他任何进一步的牦牛剃须。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/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")" |
下面是一个简短的测试用例,在路径中有一些扭曲的空间来充分地执行报价
1 2 3 4 5 6 7 8 | 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" |
基于Loveborg优秀的python代码片段,我写了以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/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 |
1 2 | FILEPATH="file.txt" echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH) |
即使文件不存在,也可以这样做。它确实要求包含该文件的目录存在。
我知道这是个古老的问题。我仍在提供另一种选择。最近我遇到了同样的问题,没有发现任何现有的和可移植的命令可以做到这一点。所以我写了下面的shell脚本,其中包含一个可以实现这个技巧的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #! /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
今天我发现您可以使用
所以对于像"~/documents"这样的目录:
您可以运行此:
要获得完整路径:
对于symlinks,可以使用%y格式选项:
可能会返回如下结果:
其他版本的*nix的格式选项可能有所不同,但在OSX上这些选项对我很有用。
使用
1 2 | #!/usr/bin/env node process.stdout.write(require('path').resolve(process.argv[2])); |