如何将/ path / to / file缩写为/ p / t / file


9

我正在寻找一种优雅的单行代码(例如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的优点,本示例说明了当第一个字符为点时我们如何显示前两个字符。

一条建议(我不知道您的用例):改为/f/b/.c/wizard_magic。该点在特定目录中通常很常见,以至于无法找到您应该去的地方。
克里斯·H

除了@ChrisH所说的以外,.通常只表示“当前目录”。所以/f/b/./wizard_magic是一样的/f/b/wizard_magic,因为路径元素./压缩到一个空的路径元素。
CVn 2015年

你为什么需要那个?你不能在你的交互shell使用一些巧妙的自动完成功能(可能改变你的shell的东西足够)
巴西莱Starynkevitch

Answers:


7

对于此测试文件:

$ 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在输出上使用斜杠作为字段分隔符。


出色的答案,对使用分隔符的较小改动:awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))(i==1||length($i)<2?"":"‥")} 1' OFS=/ <<<$PWD给:/foo/bar/.config/wizard_magic/f‥/b‥/.c‥/wizard_magic
ideaman42

12

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
在IIRC中,“后向引用”是对在模式中而不是替换字符串中出现的捕获组的引用。
Rhymoid

\1替换字符串中的 @Rhymoid 确实表示对模式中的捕获组的引用。无论在何处使用,反向引用都是反向引用。
吉尔(Gilles)'所以

8

您可以这样做:

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

它还会为每个以此开头的路径分量打印一个或两个额外的前导点,而不仅仅是一个或两个点。

您问过,对于以a开头的路径组件,您需要做的不只是一个字符.。为此,我认为每个组件无论如何都需要个人的关注,并且由于我很好奇,因此我尝试着制定出没有更改目录的规范路径。经过反复试验,我最终决定做对的唯一方法是做两次-来回移动:

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为null,则以斜杠开头;否则,${1:+$PWD}如果非null,则我插入$PWD。换句话说,如果第一个参数不以斜杠开头,它将被$PWD放在前面。这与路径验证差不多

否则,第一个for循环递归地反转路径组件的顺序,例如:

      1 2 3
1     2 3
2 1   3
3 2 1

...这样做时它会忽略任何单点或null分量,并且..确实如此...

      1 .. 3
1     .. 3
      3
3

...第二遍使该效果相反,同时这样做将每个组件压缩为2-dot + char1-dot + charchar

因此,无论存在与否,它都应该走一条规范的道路。

我增加/减去了第二个循环。现在,它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

4
酷,但我的眼睛受伤了。
glenn jackman 2015年

1
@don_crissti-是的!
mikeserv

2

“腥”岩组主题从噢,我的岩组包含一个Perl代码片段来做到这一点有Unicode支持:

perl -pe '
   BEGIN {
      binmode STDIN,  ":encoding(UTF-8)";
      binmode STDOUT, ":encoding(UTF-8)";
   }; s|^$HOME|~|g; s|/([^/.])[^/]*(?=/)|/$1|g; s|/\.([^/])[^/]*(?=/)|/.$1|g;
'

1

您是否要使用短名称或将其用于命令行?
对于命令行,我有以下建议:
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 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.