Code challenge: Bash prompt path shortener

时光总嘲笑我的痴心妄想 提交于 2019-12-03 01:32:38

问题


I implemented a prompt path shortener for bash to be included in the PS1 environment variable, which shortens the working directory into something more compact but still descriptive. I'm curious what other ideas may exist.

Here's the challenge:

Create a bash function _dir_chomp which can be included into PS1 like this (line breaks inserted for readability):

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

with "20" being the parameter for the maximum length as soft limit. These are the examples:

  1. /usr/portage/media-plugins/banshee-community-extensions/files becomes /u/p/m/b/files
  2. /home/user1/media/video/music/live-sets becomes ~/m/v/m/live-sets (note the ~ character as replacement for $HOME)
  3. /home/user2/media does NOT change (20 char limit not exceeded)
  4. /home/user1/this-is-a-very-long-path-name-with-more-than-20-chars becomes ~/this-is-a-very-long-path-name-with-more-than-20-chars (last component stays unshortened: soft limit)
  5. /home/user1/src becomes ~/src ($HOME always shortened)
  6. /home/user1/.kde4/share/config/kresources becomes ~/.k/s/c/kresources (note the prefixing dot is preserved)

Current user is user1.

It's allowed to call external interpreters like awk, perl, ruby, python but not compiled C programs or similar. In other words: external source files are not allowed, code must be inline. Shortest version wins. The length of the bash function body (and called sub functions) counts, means:

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

回答1:


Pure Bash:

_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[*]}"
}

For purposes of testing, I'm assuming that the question means that the current user is "user1".

Note: Bash 4 has a variable PROMPT_DIRTRIM that shortens the \w escape in PS1 by retaining the number of sub-directories according to its value and replacing the rest with ...

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



回答2:


This one is 20 or so characters shorter than my other answer:

_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
}



回答3:


This was my own solution when I had the idea for this challenge. The inspiration actually came from Jolexa's Blog.

So here it is, the ruby implementation in readable form:

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

And the actual one-line implementation within the bash function:

_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:


Another solution with only bash internals, no use of sed

shortpath()                                                                                                                                                                                                                                                                   
{           
    dir=${1%/*} && last=${1##*/}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                

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

My previous solution, with bash and sed. it cut each dir in 3 first caracters and add '..' like this: /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'
}



回答5:


This is how I shorten my bash prompt w/ full path in titlebar (works since 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} \$ '

This method requires very low CPU overhead.



来源:https://stackoverflow.com/questions/3497885/code-challenge-bash-prompt-path-shortener

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!