Bash PWD缩写

23

我正在寻找一个Bash函数,可以缩短较长的路径名,以避免我的PS1变量过长。类似这样的函数:

/this/is/the/path/to/a/really/long/directory/i/would/like/shortened

可能会最终变成:

/t../i../t../p../to/a/r../l../d../i/w../like/shortened

我希望在我的 .bashrc 文件中使用一个能够接受路径和最大可接受字符数的缩短方法。


3
就我个人而言,我的提示符只显示前两个目录,因此上面的内容会变成 like/shortened。我使用ZSH,但是我不知道如何在bash中实现这一点。 - pavpanchekha
1
@pavpanchekha pwd | sed -e "s|.*/\(.*/.*\)|\1|" - polypus74
1
而对于在PS1中使用,可以这样做:function pwd_depth_limit_2 { if [ "$PWD" = "$HOME" ] then echo -n "~" else pwd | sed -e "s|.*/\(.*/.*\)|\1|" fi } - polypus74
8个回答

34

虽然结果不完全相同,但是我的~/.bashrc包含以下内容

_PS1 ()
{
    local PRE= NAME="$1" LENGTH="$2";
    [[ "$NAME" != "${NAME#$HOME/}" || -z "${NAME#$HOME}" ]] &&
        PRE+='~' NAME="${NAME#$HOME}" LENGTH=$[LENGTH-1];
    ((${#NAME}>$LENGTH)) && NAME="/...${NAME:$[${#NAME}-LENGTH+4]}";
    echo "$PRE$NAME"
}
PS1='\u@\h:$(_PS1 "$PWD" 20)\$ '

该路径最多显示20个字符。如果路径超过20个字符,它将显示为/...d/like/shortened~/.../like/shortened


1
这很棒。简单而有效。 - Somebody still uses you MS-DOS

18

这里有一个仅使用bash的解决方案,您可能会喜欢。它将路径的每个部分缩短到最短的前缀,以便仍然可以通过tab键自动补全,并使用*代替..作为填充。

#!/bin/bash

begin="" # The unshortened beginning of the path.
shortbegin="" # The shortened beginning of the path.
current="" # The section of the path we're currently working on.
end="${2:-$(pwd)}/" # The unmodified rest of the path.
end="${end#/}" # Strip the first /
shortenedpath="$end" # The whole path, to check the length.
maxlength="${1:-0}"

shopt -q nullglob && NGV="-s" || NGV="-u" # Store the value for later.
shopt -s nullglob    # Without this, anything that doesn't exist in the filesystem turns into */*/*/...

while [[ "$end" ]] && (( ${#shortenedpath} > maxlength ))
do
  current="${end%%/*}" # everything before the first /
  end="${end#*/}"    # everything after the first /

  shortcur="$current"
  shortcurstar="$current" # No star if we don't shorten it.

  for ((i=${#current}-2; i>=0; i--))
  do
    subcurrent="${current:0:i}"
    matching=("$begin/$subcurrent"*) # Array of all files that start with $subcurrent. 
    (( ${#matching[*]} != 1 )) && break # Stop shortening if more than one file matches.
    shortcur="$subcurrent"
    shortcurstar="$subcurrent*"
  done

  begin="$begin/$current"
  shortbegin="$shortbegin/$shortcurstar"
  shortenedpath="$shortbegin/$end"
done

shortenedpath="${shortenedpath%/}" # strip trailing /
shortenedpath="${shortenedpath#/}" # strip leading /

echo "/$shortenedpath" # Make sure it starts with /

shopt "$NGV" nullglob # Reset nullglob in case this is being used as a function.

将长度作为第一个参数,并将路径作为可选的第二个参数。如果没有给出第二个参数,则使用当前工作目录。
这将尝试缩短到给定的长度以下。如果不可能,它只会给出它能够给出的最短路径。
从算法上讲,这可能很糟糕,但最终变得相当快速。(快速shell脚本的关键是避免子shell和外部命令,特别是在内部循环中。)
按设计,它仅缩短2个或更多字符('hom *'与'home'一样多的字符)。
它并不完美。有一些情况下它不会缩短得尽可能多,比如如果有几个文件名共享前缀的文件存在(如果foobar1和foobar2存在,则foobar3不会被缩短)。

7
我喜欢展示可通过Tab键自动补全的唯一前缀的想法。 - Barry Brown

15

提醒一下,Bash 4+内置了一个\w“缩短器”:

PROMPT_DIRTRIM=3

/var/lib/whatever/foo/bar/baz缩短为.../foo/bar/baz


2
很好,只是需要澄清一下:需要bash v4+(例如,OS X 10.8带有bash 3.2.48)。 - mklement0
只需在Mac上运行“brew install bash”即可获取最新版本。 - neurotronix
并将新的bash设置为默认shell: sudo -s; echo /usr/local/bin/bash >> /etc/shells; chsh -s /usr/local/bin/bash - neurotronix

11

我对Evan Krall的代码进行了一些改进。现在它会检查你的路径是否以$HOME开头,并且用~/代替/h*/u*,使路径更加简短。

#!/bin/bash

begin="" # The unshortened beginning of the path.
shortbegin="" # The shortened beginning of the path.
current="" # The section of the path we're currently working on.
end="${2:-$(pwd)}/" # The unmodified rest of the path.

if [[ "$end" =~ "$HOME" ]]; then
    INHOME=1
    end="${end#$HOME}" #strip /home/username from start of string
    begin="$HOME"      #start expansion from the right spot
else
    INHOME=0
fi

end="${end#/}" # Strip the first /
shortenedpath="$end" # The whole path, to check the length.
maxlength="${1:-0}"

shopt -q nullglob && NGV="-s" || NGV="-u" # Store the value for later.
shopt -s nullglob    # Without this, anything that doesn't exist in the filesystem turns into */*/*/...

while [[ "$end" ]] && (( ${#shortenedpath} > maxlength ))
do
  current="${end%%/*}" # everything before the first /
  end="${end#*/}"    # everything after the first /

  shortcur="$current"
  shortcurstar="$current" # No star if we don't shorten it.

  for ((i=${#current}-2; i>=0; i--)); do
    subcurrent="${current:0:i}"
    matching=("$begin/$subcurrent"*) # Array of all files that start with $subcurrent. 
    (( ${#matching[*]} != 1 )) && break # Stop shortening if more than one file matches.
    shortcur="$subcurrent"
    shortcurstar="$subcurrent*"
  done

  #advance
  begin="$begin/$current"
  shortbegin="$shortbegin/$shortcurstar"
  shortenedpath="$shortbegin/$end"
done

shortenedpath="${shortenedpath%/}" # strip trailing /
shortenedpath="${shortenedpath#/}" # strip leading /

if [ $INHOME -eq 1 ]; then
  echo "~/$shortenedpath" #make sure it starts with ~/
else
  echo "/$shortenedpath" # Make sure it starts with /
fi

shopt "$NGV" nullglob # Reset nullglob in case this is being used as a function.

此外,这是我在我的.bashrc文件中添加的一些函数,用于缩短shell显示的路径。我不确定像这样编辑$PWD是否完全安全,因为某些脚本可能依赖于有效的$PWD字符串,但到目前为止,我偶尔使用并没有出现问题。请注意,我将上面的脚本保存为“shortdir”并将其放在了我的PATH中。

function tinypwd(){
    PWD=`shortdir`
}

function hugepwd(){
    PWD=`pwd`
}

编辑于2010年10月19日

在bash中进行别名的正确方法是修改变量$PS1,这是提示符的解析方式。在绝大多数情况下(99%的情况下),当前路径在提示字符串中表示为"\w"。我们可以使用sed将其替换为shortdir,如下所示:

#NOTE: trailing space before the closing double-quote (") is a must!!
function tinypwd(){                                                             
    PS1="$(echo $PS1 | sed 's/\\w/\`shortdir\`/g') "
}                                                                               

function hugepwd(){                                                             
    PS1="$(echo $PS1 | sed 's/[`]shortdir[`]/\\w/g') "                            
} 

5
一个Python脚本怎么样?它会首先缩短最长的目录名称,每次缩短一个字符,直到达到其长度目标或不能再缩短路径为止。它不会缩短路径中的最后一个目录。
(我开始用纯shell脚本编写,但是,哇,bash在字符串操作方面很糟糕。)
#!/usr/bin/env python
import sys

try:
    path   = sys.argv[1]
    length = int(sys.argv[2])
except:
    print >>sys.stderr, "Usage: $0 <path> <length>"
    sys.exit(1)

while len(path) > length:
    dirs = path.split("/");

    # Find the longest directory in the path.
    max_index  = -1
    max_length = 3

    for i in range(len(dirs) - 1):
        if len(dirs[i]) > max_length:
            max_index  = i
            max_length = len(dirs[i])

    # Shorten it by one character.    
    if max_index >= 0:
        dirs[max_index] = dirs[max_index][:max_length-3] + ".."
        path = "/".join(dirs)

    # Didn't find anything to shorten. This is as good as it gets.
    else:
        break

print path

示例输出:

$ echo $DIR
/this/is/the/path/to/a/really/long/directory/i/would/like/shortened
$ ./shorten.py $DIR 70
/this/is/the/path/to/a/really/long/directory/i/would/like/shortened 
$ ./shorten.py $DIR 65
/this/is/the/path/to/a/really/long/direc../i/would/like/shortened
$ ./shorten.py $DIR 60
/this/is/the/path/to/a/re../long/di../i/would/like/shortened
$ ./shorten.py $DIR 55
/t../is/the/p../to/a/r../l../di../i/wo../like/shortened
$ ./shorten.py $DIR 50
/t../is/the/p../to/a/r../l../d../i/w../l../shortened

我刚刚写了一个相当类似的 Python 脚本。我的脚本做了更多递归,效率稍微更高,但是在达到所需长度时完全没有停止截取的功能。因此,除非有人关心,否则我不会再继续完善和发布它了。:-/ - Benson
不错。我唯一的担忧是在每次 shell 执行时执行 Python 脚本的成本。我会尝试一下并让你知道。 - user195709
如果速度太慢,请告诉我,如果需要的话,肯定可以加快速度。 - John Kugelman
1
非常好用。这是我在.bashrc中的整个PS1定义表单:PS1="\[\e[31m\]\H:\[\e[32m\]"'`path-shorten $PWD 50`'"\[\e[31m\] -->\[\e[0m\] " - user195709
1
当我改变当前路径(cd somepath)时,它不会更新。有什么建议吗? - Jeremias Erbs

3
这里是 Evan 答案的另一种方法:
这个方法使用加号 (+) 代替星号 (*) 来缩短路径。它将 HOME 路径替换为 ~,并保留最后一个目录段。如果最后一个段超过 20 个字符,则将其缩短为可制表位,并添加省略号 (...)。
#!/bin/bash
# Modified from https://dev59.com/WnI-5IYBdhLWcg3w6dC1#1617048
# By Alan Christopher Thomas (http://alanct.com)

__pwd_ps1 ()
{
    begin=""
    homebegin=""
    shortbegin=""
    current=""
    end="${2:-$(pwd)}/" # The unmodified rest of the path.
    end="${end#/}" # Strip the first /
    shortenedpath="$end"

    shopt -q nullglob && NGV="-s" || NGV="-u"
    shopt -s nullglob

    while [[ "$end" ]]
    do
      current="${end%%/*}" # Everything before the first /
      end="${end#*/}" # Everything after the first /

      shortcur="$current"
      for ((i=${#current}-2; i>=0; i--))
      do
        [[ ${#current} -le 20 ]] && [[ -z "$end" ]] && break
        subcurrent="${current:0:i}"
        matching=("$begin/$subcurrent"*) # Array of all files that start with $subcurrent
        (( ${#matching[*]} != 1 )) && break # Stop shortening if more than one file matches
        [[ -z "$end" ]] && shortcur="$subcurrent..." # Add character filler at the end of this string
        [[ -n "$end" ]] && shortcur="$subcurrent+" # Add character filler at the end of this string
      done

      begin="$begin/$current"
      homebegin="$homebegin/$current"
      [[ "$homebegin" =~ ^"$HOME"(/|$) ]] && homebegin="~${homebegin#$HOME}" # Convert HOME to ~
      shortbegin="$shortbegin/$shortcur"
      [[ "$homebegin" == "~" ]] && shortbegin="~" # Use ~ for home
      shortenedpath="$shortbegin/$end"
    done

    shortenedpath="${shortenedpath%/}" # Strip trailing /
    shortenedpath="${shortenedpath#/}" # Strip leading /

    [[ ! "$shortenedpath" =~ ^"~" ]] && printf "/$shortenedpath" # Make sure it starts with /
    [[ "$shortenedpath" =~ ^"~" ]] && printf "$shortenedpath" # Don't use / for home dir

    shopt "$NGV" nullglob # Reset nullglob in case this is being used as a function.
}

在此处下载脚本并将其包含在你的 .bashrc 文件中:

https://raw.github.com/alanctkc/dotfiles/master/.bash_scripts/pwd-prompt.bash

. ~/.bash_scripts/pwd-prompt.bash

将目录添加到您的PS1中,方法如下:

export PS1="[other stuff...] \$(__pwd_ps1)\$ "

1

这里有一个相对简单的Perl解决方案。它足够短,可以直接嵌入PS1而不是调用脚本。它会给出所有被截断名称的字符,而不是用“.”替换。

$ echo '/this/is/a/realy/long/path/id/like/shortened' |
 perl -F/ -ane 'print join( "/", map { $i++ < @F - 2 ?
 substr $_,0,3 : $_ } @F)'
/thi/is/a/rea/lon/pat/id/like/shortened

我没有立即看到一种好的方法来用“.”替换字符,但这里有一个丑陋的方法:

echo '/this/is/a/realy/long/path/id/like/shortened' |
 perl -F/ -ane 'print join( "/", map { m/(.)(.*)/;
 $_ = $1 . "." x (length $2 > 2 ? 2 : length $2 ) if $i++ < @F - 2; $_ } @F)'
/t../i./a/r../l../p../i./like/shortened

谢谢。我借用了它来建议在Super User上回答(几乎)相同的问题。http://superuser.com/questions/180257/bash-prompt-how-to-have-the-initials-of-directory-path - Telemachus

0

试试这个:

PS1='$(pp="$PWD/" q=${pp/#"$HOME/"/} p=${q%?};((${#p}>19))&&echo "${p::9}…${p:(-9)}"||echo "$p") \$'

它转换了

~/.vim/bundle/ack.vim/plugin

.vim/bund…im/plugin

转换

/usr/share/doc/xorg-x11-font-utils-7.5/

/usr/shar…utils-7.5

$PWD$HOME相同时,不显示任何内容。

奖励:您可以修改长度的数量以适应您的需求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接