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

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


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


当前回答

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

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

你可以运行这个:

stat -f %N ~/文档

获取完整路径:

/用户/我/文档

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

stat -f %Y example_symlink

这可能会返回如下结果:

/usr/local/sbin/example_symlink

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

其他回答

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

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

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

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

在普通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 "

老问题了,但是如果你在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,则无用。

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

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