【问题标题】:Code challenge: Bash prompt path shortener代码挑战:Bash 提示路径缩短器
【发布时间】:2011-03-30 16:47:01
【问题描述】:

我实现了一个提示路径缩短器,以便将 bash 包含在 PS1 环境变量中,它将工作目录缩短为更紧凑但仍具有描述性的内容。我很好奇可能还有什么其他想法。

挑战如下:

创建一个 bash 函数_dir_chomp,它可以像这样包含在 PS1 中(插入换行符以提高可读性):

PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] $(
  _dir_chomp "$(pwd)" 20
)\[\033[01;37m\]$(parse_git_branch)\[\033[01;34m\] \$\[\033[00m\] '

“20”是作为软限制的最大长度参数。这些是例子:

  1. /usr/portage/media-plugins/banshee-community-extensions/files 变为 /u/p/m/b/files
  2. /home/user1/media/video/music/live-sets 变为 ~/m/v/m/live-sets(注意 ~ 字符作为 $HOME 的替换)
  3. /home/user2/media 不变(不超过 20 个字符的限制)
  4. /home/user1/this-is-a-very-long-path-name-with-more-than-20-chars 变为 ~/this-is-a-very-long-path-name-with-more-than-20-chars(最后一个组件保持未缩短:软限制)
  5. /home/user1/src 变为 ~/src($HOME 始终缩短)
  6. /home/user1/.kde4/share/config/kresources 变为 ~/.k/s/c/kresources(注意保留前缀点)

当前用户是user1。

允许调用外部解释器,如awkperlrubypython,但不能调用已编译的 C 程序或类似程序。换句话说:不允许外部源文件,代码必须是内联的。最短的版本获胜。 bash 函数体(以及被称为子函数)的长度很重要,意思是:

_sub_helper() {
  # this counts
}
_dir_chomp() {
  # these characters count (between { and })
  _sub_helper "foobar" # _sub_helper body counts, too
}

【问题讨论】:

  • 我注意到你有parse_git_branch - 你更喜欢__git_ps1吗?
  • 真的,只要有人花时间去做,我怀疑任何东西都比不上不可读但简短的 perl 解决方案。
  • @Jefromi:这只是一个班轮:git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ [git:\1]/'。顺便说一句:我的解决方案是 ruby​​,而不是 perl。虽然是单行的,但也很可读。
  • 查看此解决方案:unix.stackexchange.com/a/26860/6128 不完全符合要求,但很容易修改。
  • 允许 awk、perl、ruby 和 python 不是这个挑战的好主意:提示不能只是代码短,它应该在加载的外部程序(fork+exec)中保持轻量级快。

标签: bash path prompt


【解决方案1】:

纯猛击:

_dir_chomp () {
    local IFS=/ c=1 n d
    local p=(${1/#$HOME/\~}) r=${p[*]}
    local s=${#r}
    while ((s>$2&&c<${#p[*]}-1))
    do
        d=${p[c]}
        n=1;[[ $d = .* ]]&&n=2
        ((s-=${#d}-n))
        p[c++]=${d:0:n}
    done
    echo "${p[*]}"
}

出于测试目的,我假设问题意味着当前用户是“user1”。

注意:Bash 4 有一个变量PROMPT_DIRTRIM,它通过根据其值保留子目录的数量并将其余的替换为... 来缩短PS1 中的\w 转义

/$ PROMPT_DIRTRIM=2
/$ echo $PS1
\w\$
/$ pwd
/
/$ cd /usr/share/doc/bash
.../doc/bash$

【讨论】:

  • 好吧,PROMPT_DIRTRIM 对我来说太简洁了,所以我创建了自己的解决方案。
【解决方案2】:

这个比我的另一个答案短 20 个字符左右:

_dir_chomp () {
    local p=${1/#$HOME/\~} b s
    s=${#p}
    while [[ $p != "${p//\/}" ]]&&(($s>$2))
    do
        p=${p#/}
        [[ $p =~ \.?. ]]
        b=$b/${BASH_REMATCH[0]}
        p=${p#*/}
        ((s=${#b}+${#p}))
    done
    echo ${b/\/~/\~}${b+/}$p
}

【讨论】:

  • 我喜欢纯 bash 方法。
  • 顺便说一句,我的两个答案都是纯 Bash。
  • 我的意思是与我的红宝石解决方案相反。
  • 注意,在使用它来设置 PS1 时,请务必引用 $PWD — 对其他人来说可能很明显,但在锻炼前几个月我一直在忍受 bash: ... division by 0 的痛苦。 //即// PS1='$(_dir_chomp "$PWD" 20)'
【解决方案3】:

当我有这个挑战的想法时,这是我自己的解决方案。灵感其实来自Jolexa's Blog

这就是可读形式的 ruby​​ 实现:

a = ARGV[1].gsub(%r{^#{ENV['HOME']}}, "~")
b, a = a, a.gsub(%r{/(\.?[^/.])[^/]+(/.*)}, '/\1\2') while
  (a.length > ARGV[2].to_i) && (b != a)
print a

以及 bash 函数中的实际单行实现:

_dir_chomp() {
  ruby -e'a="'$1'".gsub(%r{^'$HOME'},"~");b,a=a,a.gsub(%r{/(\.?[^/.])[^/]+(/.*)},"/\\1\\2")while(a.length>'$2')&&(b!=a);print a'
}

【讨论】:

    【解决方案4】:

    这就是我在标题栏中使用完整路径缩短我的 bash 提示符的方法(从 3.0 开始工作):

    _PS1P=('' '..')
    PROMPT_COMMAND='_PS1L=${#DIRSTACK[0]} _PS1D=${DIRSTACK[0]}'
    PS1='\[\e]2;\h:\w\a\]\h ${_PS1P[$_PS1L>36]}${_PS1D:$_PS1L>36?-34:0} \$ '
    

    这种方法需要非常低的 CPU 开销。

    【讨论】:

      【解决方案5】:

      另一个只有 bash 内部的解决方案,不使用 sed

      shortpath()                                                                                                                                                                                                                                                                   
      {           
          dir=${1%/*} && last=${1##*/}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
      
          res=$(for i in ${dir//\// } ; do echo -n "${i:0:3}../" ; done)                                                                                                                                                                                                            
          echo "/$res$last"                                                                                                                                                                                                                                                         
      } 
      

      我以前的解决方案,使用 bash 和 sed。 它将每个目录切成 3 个第一个字符并添加“..”,如下所示: /hom../obo../tmp../exa../bas../

      shortpath()
      {
              dir=$(dirname $1)
              last=$(basename $1)
      
              v=${dir//\// } # replace / by <space> in path
              t=$(printf "echo %s | sed -e 's/^\(...\).*$/\\\1../' ; " $v) 
                  # prepare command line, cut names to 3 char and add '..'
              a=$(eval $t) 
                  # a will contain list of 3 char words ended with '..' ans separated with ' '
      
              echo " "$a"/"$last | sed -e 's/ /\//g'
      }
      

      【讨论】:

        最近更新 更多