Bash 要解析的字符串中的可移植递归正则表达式替换。。在小路上

Bash 要解析的字符串中的可移植递归正则表达式替换。。在小路上,bash,shell,awk,sed,Bash,Shell,Awk,Sed,在Bash中,解决路径字符串中出现的所有。的最优雅和可移植的方法是什么 示例:路径/aa/bb/cc/./dd/ee/../ff/gg/hh/ii/jj/../kk变成/aa/bb/ff/gg/kk 我编写了两个函数,它们基本上实现了这个目标。(实际上是一个,而且更长,但我在写这个问题时有一些想法…) 一种使用重复: 解析父项(){ 本地上一个结果=$1 local re=“\/$re |$re\/”re='[^\/]{1,}\/.\.” 而[[$result!=$previous]];执行 上

在Bash中,解决路径字符串中出现的所有
的最优雅和可移植的方法是什么

示例:路径
/aa/bb/cc/./dd/ee/../ff/gg/hh/ii/jj/../kk
变成
/aa/bb/ff/gg/kk

我编写了两个函数,它们基本上实现了这个目标。(实际上是一个,而且更长,但我在写这个问题时有一些想法…)

一种使用重复:

解析父项(){
本地上一个结果=$1
local re=“\/$re |$re\/”re='[^\/]{1,}\/.\.”
而[[$result!=$previous]];执行
上一个=$result
结果=$(回显“$result”| awk'{sub(/\/'$re'/,“”)}1')
完成
回显“$result”
}
解决家长的/aa/bb/cc/./dd/ee/../ff/gg/hh/ii/jj/../kk'
其中一个使用递归:

resolve\u parents\r(){
local re=“\/$re |$re\/”re='[^\/]{1,}\/.\.”
本地结果=$(回显“$1”| awk'{sub(/\/'$re'/,“”)}1')
[[$1=$result]]&&echo“$1”| |解析_家长_r”$result”
}
它们不会删除前导的
,这将需要确定基本目录以获取其父目录。我同意这个限制。此外,它不会将
aa/.
完全减少为空字符串。我想我要么需要在末尾单独应用一个正则表达式,而不使用前导或尾随斜杠,要么在字符串的开头添加一个斜杠,然后在末尾删除它

背景

我有一个运行
ln-s“$source”“$target”
的函数。如果
$source
不存在,则需要抛出错误。如果是绝对路径,则确认
$source
存在很简单,但必须规范化相对路径。我通过以下方式实现这一目标:

[[-d$target]]&&target=$target/$(basename“$source”)
本地abs_source=$source
[[$source=/*]| | abs|u source=$(cd“$target/./$source”&&pwd-P)
第一行是确保
$target
以目标名称结尾所必需的。否则,如果
$target
是一个目录,则需要省略
。/
,但如果
$target
已以预期的链接名称结尾,则需要使用该目录
cd
仅在目录存在时才适用于解析包含双点父引用的路径。由于测试是在创建链接之前进行的,
$target
很遗憾,但不一定存在

因此,我需要通过删除正则表达式
[^/]+/\.\.
前面或后面有斜杠的匹配项来确定
$source
是否存在,但不能同时删除这两个匹配项。(我考虑过删除匹配项,然后删除
/
序列,但这可能会导致相对路径变为绝对路径,或者导致以目标名称结尾的路径变为目录,从而附加另一个目标名称。Yikes!)我无法使用全局正则表达式替换该模式,因为这样可以删除像
。/…
这样的序列。我不知道有哪个命令会通过在每次匹配时将搜索光标重置到字符串的开头来进行全局替换,因此我将其写入
while
循环中

为了简单起见,我希望使用存储在
$re
中的模式,但当我尝试用
sed
替换时,这很快变得相当复杂,我发现
+
需要扩展正则表达式,有时用
sed-E
(在OSX上)打开,有时用
sed-r
打开。在将
+
替换为
{1,}
时,我发现
sed
需要转义大括号,但是转义它们导致Bash regex匹配失败。我迁移到使用
awk
及其
sub
命令,这显然要求分隔符为
/
,因此我还需要对模式中的分隔符进行转义

为了防止由于Bash和
awk
中的正则表达式之间的解释不匹配而导致无限循环,我首先添加了一个具有任意限制的计数器变量。我将其改进为差异测试,因此
而[[$result=~$re]]&((count<1000))
变成
而[[$result=~$re&$result!=$previous]
。然后,在尝试创建一个实际的递归函数之后,我意识到
$result=~$re&
不是必需的


此时,我开始怀疑我是否真的在乎我的
$source
是否不存在

看起来您可以在这里使用
readlink-m

p='/aa/bb/cc/../dd/ee/../../ff/gg/hh/ii/jj/../../../kk'

readlink -m "$p"
/aa/bb/ff/gg/kk
根据
readlink--help

-m,--canonicalize missing
通过跟踪中的每个符号链接来规范化 给定名称的每个组件都递归, 不存在对组件的要求


更新:这里有一个标准的堆栈方法,可以使用
awk
解决此问题。考虑这个AWK脚本:

BEGIN {
   FS = OFS = "/"
}
/^\.\.\// {
   $0 = pwd OFS $0  # prefix with pwd if stats with ../
}
{
   for (i=1; i<=NF; i++) {
      if ($i == "..")
         delete p[k--]  # pop an element from stack
      else
         p[++k] = $i    # push an element into stack
   }
   # print final stack content
   for (i=1; i <= k; i++)
      printf "%s%s", (i>1 ? OFS : ""), p[i]
   print ""
}

在“自动工具”生成的“配置”文件中,使用了非常简单的方法,看起来非常便于移植:)


我惊讶于这是多么简单,但遗憾的是macOS上的readlink没有这样的选项,这是针对Mac/Linux安装程序的。我试图没有必要的软件包,因为这个脚本将负责安装自制软件本身。。。但这也许是最好的方法。我也试着用sed解决这个问题:
sed:z;s、 [^/]*/\.\./*,,;tz'
1<代码>:z是带有自定义名称z的标签。我们循环的开始。2.
s,[^/]*/\.\./*,
尝试删除
something/.
的第一个外观,并在第3个结尾处添加可选的“/”
tz
是“如果prevision substitute命令替换某些内容,请跳到标签
z
p='/aa/bb/cc/../dd/ee/../../ff/gg/hh/ii/jj/../../../kk'
awk -v pwd="$PWD" -f realpath.awk <<< "$p"

/aa/bb/ff/gg/kk
# expand $ac_aux_dir to an absolute path
am_aux_dir=`cd $ac_aux_dir && pwd`