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

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


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


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);
}

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

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

而且效果很好。


如果您想要从路径中删除部分文件名,“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/.. 

Also

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

工作原理与

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

在这种情况下,路径不需要存在就可以被规范化。


话多,回答有点晚。我需要写一个,因为我卡住了旧的RHEL4/5。 I处理绝对和相对链接,并简化//,/。/和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`

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

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

一种可移植且可靠的解决方案是使用python,几乎所有地方都预装了python(包括Darwin)。你有两个选择:

Abspath返回一个绝对路径,但不解析符号链接: import os,sys;打印(os.path.abspath (sys.argv[1]))“路径/ /文件 Realpath返回一个绝对路径并解析符号链接,生成一个规范路径: import os,sys;打印(os.path.realpath (sys.argv[1]))“路径/ /文件

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


正如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。


我最近的解决方案是:

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

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


不完全是一个答案,但可能是一个后续问题(最初的问题不明确):

如果你真的想遵循符号链接,Readlink是很好的。但也有一个用例仅仅是正常化。/和../和//序列,这可以纯语法地完成,而不需要规范化符号链接。Readlink做不到这一点,realpath也不行。

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

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

sed脚本似乎是一个不错的选择,除了不能迭代地替换序列(/foo/bar/baz/../..)- > / foo / bar / . .-> /foo)没有使用像Perl这样的东西,这在所有系统上都是不安全的,或者使用一些丑陋的循环来比较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

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

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

这将解析$1的绝对路径,很好地处理~,将符号链接保留在它们所在的路径中,并且不会打乱目录堆栈。它返回完整的路径,如果不存在则不返回。它期望$1是一个目录,如果不是的话可能会失败,但这是一个很容易自己做的检查。


试试我们新的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函数。在不同平台上尝试它,并帮助改进它。


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上报错。


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

对于"~/Documents"这样的目录:

你可以运行这个:

stat -f %N ~/文档

获取完整路径:

/用户/我/文档

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

stat -f %Y example_symlink

这可能会返回如下结果:

/usr/local/sbin/example_symlink

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


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
}

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


基于@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

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

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

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

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

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

假设你的shell内置命令接受-L和-P,有两种变化:

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

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

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

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

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


基于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

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

即使文件不存在,这也可以工作。它需要包含该文件的目录存在。


我知道这是一个古老的问题。我仍在提供另一种选择。最近我遇到了同样的问题,并且发现没有现有的可移植命令来执行此操作。因此,我编写了下面的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


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

在普通mac上工作。realpath和readlink -f是插件 解决符号链接 有错误处理

没有一个人的答案同时包含了第一条和第二条。我加了第三条是为了让其他人不用再剃牦牛了。

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

我做了一个内置函数来处理这个问题,并专注于最高的性能(为了好玩)。它不解析符号链接,因此基本上与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时,在我的机器上,abspath的运行速度比realpath -sm快110倍,这主要是因为不需要每次都执行新程序。


如果你只想规范化一个路径,不管是否存在,不涉及文件系统,不解析任何链接,也不使用外部utils,这里有一个从Python的posixpath.normpath转换而来的纯Bash函数。

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

Personally, I cannot understand why Shell, a language often used for manipulating files, doesn't offer basic functions to deal with paths. In python, we have nice libraries like os.path or pathlib, which offers a whole bunch of tools to extract filename, extension, basename, path segments, split or join paths, to get absolute or normalized paths, to determine relations between paths, to do everything without much brain. And they take care of edge cases, and they're reliable. In Shell, to do any of these, either we call external executables, or we have to reinvent wheels with these extremely rudimentary and arcane syntaxes...


由于所介绍的解决方案都不适合我,所以在文件不存在的情况下,我实现了我的想法。 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
}