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

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


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


当前回答

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

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

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

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

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

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