![如何将 /path/to/file 缩写为 /p/t/file](https://linux22.com/image/78730/%E5%A6%82%E4%BD%95%E5%B0%86%20%2Fpath%2Fto%2Ffile%20%E7%BC%A9%E5%86%99%E4%B8%BA%20%2Fp%2Ft%2Ffile.png)
我正在寻找一个优雅的单行(例如,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_magic
鉴于下面@MichaelKjörling 和@ChrisH 的优点,此示例展示了当第一个字符是点时我们如何显示前两个字符。
答案1
在 sed 中非常简单(假设文件名中没有换行符):
sed 's!\([^/]\)[^/]*/!\1/!g'
在 awk 中不太容易,因为它缺乏反向引用(Gawk 除外,但语法很笨拙):
awk -v FS=/ -v OFS=/ '{for (i=1; i<NF; i++) $i=substr($i,1,1)} 1'
在 zsh 中(路径为$full_path
):
echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
答案2
你可以这样做:
cd /usr///.//share/../share//man/man1 || exit
IFS=/; set -f
printf %.1s/ ${PWD%/*}
printf %s\\n "${PWD##*/}"
/u/s/m/man1
这是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/
这非常接近于执行下面函数所做的所有相同的事情。它不会像函数那样用波形符缩写或将 插入$PWD
到头部作为前导非斜杠(事实上,永远不会打印前导斜杠)但这可以稍后处理。它确实处理空路径组件和单点,并清除..
案例。
给出man
与cd
上面相同的路径,它会打印:
u/s/m/man1
它还将为以此开头的每个路径组件打印一两个额外的前导点,而不仅仅是一两个点。
您询问是否要为以 . 开头的路径组件执行多个字符.
。为此,我认为每个组件都需要单独关注,并且因为我很好奇,所以我尝试在不更改目录的情况下找出规范路径。经过一番尝试和错误后,我最终决定正确执行此操作的唯一方法是前后执行两次:
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:--}"
}
这样就不会更改目录或尝试确认任何路径组件的存在,但它会挤压重复的/
分隔符并/./
完全删除单点组件,并/../
适当地处理双点组件。
当$IFS
设置为某些非空白字符,两个或多个字符的序列$IFS
将导致一个或多个空字段。因此多个连续的斜杠可以计算出空值参数。对于主角来说也是如此$IFS
。因此,当set -- $1
分割时,如果结果$1
为空,那么它以斜杠开头,否则,${1:+$PWD}
如果它不为空,那么我插入$PWD
.换句话说,如果第一个参数不以斜杠开头,它将被添加到$PWD
前面。这与这条路径一样接近验证。
否则,第一个for
循环递归地反转路径组件的顺序,例如:
1 2 3
1 2 3
2 1 3
3 2 1
...这样做时,它会忽略任何单点或空组件,并且确实..
如此...
1 .. 3
1 .. 3
3
3
...第二遍逆转了这种效果,并且在这样做时它将每个组件挤压到任一2点+字符, 或者1点+字符, 或者字符。
因此,无论是否存在,它都应该遵循规范路径。
我在第二个循环中添加/减去了一点。现在set
越来越少了(每个组件只能使用一次[!./]*
)case
,并且大多数时候短路模式评估(感谢上述模式),并包括针对 的尾调用匹配评估~
。如果全部或主要部分(按整个组件划分)最终规范路径的 可以匹配~
,匹配位将被剥离并~
替换为文字。为了做到这一点,我还必须保留路径的完整副本以及缩写(因为将缩写路径匹配到~
可能不会很有帮助),所以这被保留在$3
.最后一个循环分支仅在匹配为 的子集while
时运行。~
$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
答案3
对于这个测试文件:
$ cat path
/path/to/file
/tmp
/foo/bar/.config/wizard_magic
可以使用以下 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
Edit1:使用两个字符作为点名
此版本将目录名称缩写为一个字符,但以 开头的名称.
缩写为两个字符:
$ 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
怎么运行的
-F/
这告诉 awk 使用斜杠作为输入的字段分隔符。
for (i=1;i<NF;i++) $i=substr($i,1,1)
这将循环遍历每个字段(最后一个字段除外),并仅用其第一个字符替换它。
EDIT1:在修订版本中,当字段以 开头时,我们将子字符串的长度设置为 2
.
。1
这告诉 awk 打印修改后的行。
OFS=/
这告诉 awk 使用斜杠作为输出中的字段分隔符。
答案4
您想要使用短名称还是将其用于命令行?
对于命令行,我有以下建议:
shell 中的文件完成功能对您没有帮助吗?
有时你很幸运,不需要做特别的事情:
# /path/to/file -> /p/t/file
ls -l /*/*/file
# /tmp -> /tmp
cd /tmp
# /foo/bar/.config/wizard_magic -> /f/b/./wizard_magic
ls -l /*/*/*/wizard_magic -> /f/b/./wizard_magic
当您只有一些感兴趣的目录时,可以使用别名:
alias cdto="cd /path/to"
alias cdtmp="cd /tmp"
alias cdcfg="cd /foo/bar/.config"
alias cddeep="cd /home/john/workdir/project1/version3/maven/x/y/z/and/more"
或者您可以为您最喜欢的目录设置变量
export p="/path/to"
export f="/foo/bar/.config"
ls -l $p/file
ls -l $f/wizard_magic
我认为这些选项比尝试使用 .bashrc (或 .profile)中定义的函数来解决这个问题更有意义,例如
function x {
xxpath=""
while [ $# -ne 0 ]; do
xxpath+="${1}*/"
shift
done
cd $(echo "${xxpath}")
}
并在字母之间使用空格调用此函数 x:
# cd /path/to
x /p t
# cd /tmp
x /t
# cd /foo/bar/.config
x /f b