我正在寻找一个优雅的单行(例如,awk),它将使用每个父/中间级别的第一个字符缩短 Unix 路径的字符串,但完整的基本名称。通过示例更容易展示:
/path/to/file ? /p/t/file/tmp ? /tmp/foo/bar/.config/wizard_magic ? /f/b/./wizard_magic/foo/bar/.config/wizard_magic? /f/b/.c/wizard_magicGil*_*il' 13
在 sed 中很容易(假设文件名中没有换行符):
sed 's!\([^/]\)[^/]*/!\1/!g'
Run Code Online (Sandbox Code Playgroud)
在 awk 中不太容易,因为它缺少反向引用(Gawk 除外,但语法笨拙):
awk -v FS=/ -v OFS=/ '{for (i=1; i<NF; i++) $i=substr($i,1,1)} 1'
Run Code Online (Sandbox Code Playgroud)
在 zsh 中(路径为$full_path):
echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
Run Code Online (Sandbox Code Playgroud)
你可以这样做:
cd /usr///.//share/../share//man/man1 || exit
IFS=/; set -f
printf %.1s/ ${PWD%/*}
printf %s\\n "${PWD##*/}"
Run Code Online (Sandbox Code Playgroud)
/u/s/m/man1
Run Code Online (Sandbox Code Playgroud)
这是一个sed:
printf %s "$file" |
tr /\\n \\n/ | sed -et$ \
-e '\|^\.\.$|{x;s|\(.*\)\n.*$|\1|;x;}' \
-e 's|^\.\{0,2\}$||;\|.|H;$!d;x' \
-e$ -e '\|\(\.\{0,2\}.\)\(.*\)\(\n\)|!b' \
-e 's||\1\3\2\3|;P;s|\n||;D' |
tr /\\n \\n/
Run Code Online (Sandbox Code Playgroud)
这非常接近于完成函数在下面所做的所有相同的事情。它不像函数那样用波浪号缩写或插入$PWD到头部以获得前导非斜杠(实际上,从不打印前导斜杠),但可以在之后处理。它确实处理了空路径组件和单点,并清除了..案例。
给定与上面相同的man路径cd,它会打印:
u/s/m/man1
Run Code Online (Sandbox Code Playgroud)
它还将为每个以此类开头的路径组件打印一两个额外的前导点,而不仅仅是一两个点。
您询问是否要为以..开头的路径组件执行多个字符。为了做到这一点,我认为每个组件无论如何都需要单独关注,因为我很好奇,我尝试着找出没有更改目录的规范路径。经过一些反复试验,我最终决定做正确的唯一方法是做两次 - 向后和向前:
pathbytes(){
local IFS=/ o="$-" p
set -f${ZSH_VERSION+LFy}
set -- ${1:-$PWD}
for p in /${1:+$PWD} $*
do case $p in (.|"") ;;
(..) ${1+shift} ;;
(/) set -- ;;
(*) set -- $p $*; esac
done
for p in //$* ""
do case ${p:-/$3} in
([!./]*) ;;
(..*) set "..$@" ;;
(.*) set ".$@" ;;
(//*) ! set "" $1 $1 ;;
(~) ! p=\~ ;;
(~/*) p="~/$2";set $HOME
! while "${2+shift}" 2>&3
do p="~/${p#??*/}"
done 3>/dev/null;;
esac&& set "" "${p%"${p#$1?}"}/$2" "$p/$3"
done; printf %s\\n "${p:-$2}"
set +f "-${o:--}"
}
Run Code Online (Sandbox Code Playgroud)
以便永远不会更改目录或尝试确认任何路径组件的存在,但它会压缩重复的/分隔符并/./完全删除单点组件,并处理/../适当地双点组件。
当$IFS设置为某个非空白字符时,两个或多个$IFS字符的序列将导致一个或多个空字段。因此,多个连续的斜杠适用于空值参数。主角也是如此$IFS。因此,当set -- $1拆分时,如果结果$1为空,则它以斜杠开头,否则,${1:+$PWD}如果它不为空,则我插入$PWD. 换句话说,如果第一个参数不以斜杠开头,它将被$PWD预先添加。这与路径验证非常接近。
否则,第一个for循环递归地反转路径组件的顺序,例如:
1 2 3
1 2 3
2 1 3
3 2 1
Run Code Online (Sandbox Code Playgroud)
...这样做时它会忽略任何单点或空组件,并且因为..它确实......
1 .. 3
1 .. 3
3
3
Run Code Online (Sandbox Code Playgroud)
...第二遍逆转了这种效果,在这样做的同时,它将每个组件压缩为2-dots+char,或1-dot+char,或char。
因此,无论是否存在,它都应该朝着规范的路径发展。
我在第二个循环中加/减了一点。它现在set不那么频繁(每个[!./]*组件只有一次),并且case大部分时间都会短路模式评估(感谢上述模式),并且包括针对~. 如果最终规范路径的全部或前导部分(按整个组件划分)可以匹配~,则匹配位将被剥离并~替换为文字。为了做到这一点,我还必须在缩写旁边保留路径的完整副本(因为将缩写路径匹配到~可能不会很有帮助),因此将其保留在. 最后循环分支仅在以下情况下运行$3while~ 作为一个子集匹配 $3。
如果您在set -x启用跟踪的情况下运行它,您可以观察它的工作情况。
$ (set -x;pathbytes ..abc/def/123///././//.././../.xzy/mno)
+ pathbytes ..abc/def/123///././//.././../.xzy/mno
+ local IFS=/ o=xsmi p
+ set -f
+ set -- ..abc def 123 . . .. . .. .xzy mno
+ set --
+ set -- home
+ set -- mikeserv home
+ set -- ..abc mikeserv home
+ set -- def ..abc mikeserv home
+ set -- 123 def ..abc mikeserv home
+ shift
+ shift
+ set -- .xzy ..abc mikeserv home
+ set -- mno .xzy ..abc mikeserv home
+ set mno mno
+ set . mno mno
+ set .x/mno .xzy/mno
+ set .. .x/mno .xzy/mno
+ set ..a/.x/mno ..abc/.xzy/mno
+ set m/..a/.x/mno mikeserv/..abc/.xzy/mno
+ set h/m/..a/.x/mno home/mikeserv/..abc/.xzy/mno
+ p=~/h/m/..a/.x/mno
+ set home mikeserv
+ shift
+ p=~/m/..a/.x/mno
+ shift
+ p=~/..a/.x/mno
+
+ printf %s\n ~/..a/.x/mno
~/..a/.x/mno
+ set +f -xsmi
Run Code Online (Sandbox Code Playgroud)
对于这个测试文件:
$ cat path
/path/to/file
/tmp
/foo/bar/.config/wizard_magic
Run Code Online (Sandbox Code Playgroud)
可以使用以下 awk 代码生成缩写:
$ awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1)} 1' OFS=/ path
/p/t/file
/tmp
/f/b/./wizard_magic
Run Code Online (Sandbox Code Playgroud)
此版本将目录名称缩写为一个字符,但以.缩写为两个字符开头的名称除外:
$ awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))} 1' OFS=/ path
/p/t/file
/tmp
/f/b/.c/wizard_magic
Run Code Online (Sandbox Code Playgroud)
-F/
这告诉 awk 在输入时使用斜杠作为字段分隔符。
for (i=1;i<NF;i++) $i=substr($i,1,1)
这会遍历除最后一个字段之外的每个字段,并仅用其第一个字符替换它。
EDIT1:在修订版中,当字段以..
1
这告诉 awk 打印修改后的行。
OFS=/
这告诉 awk 在输出时使用斜杠作为字段分隔符。