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

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


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


当前回答

如果你只想规范化一个路径,不管是否存在,不涉及文件系统,不解析任何链接,也不使用外部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...

其他回答

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

如果你真的想遵循符号链接,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

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

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

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

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

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

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

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

你可以运行这个:

stat -f %N ~/文档

获取完整路径:

/用户/我/文档

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

stat -f %Y example_symlink

这可能会返回如下结果:

/usr/local/sbin/example_symlink

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