【问题标题】:Setting value of command prompt ( PS1) based on present directory string length根据当前目录字符串长度设置命令提示符(PS1)的值
【发布时间】:2026-01-04 08:55:01
【问题描述】:

我知道我可以这样做以仅反映 PS1 值中的最后 2 个目录。

PS1=${PWD#"${PWD%/*/*}/"}#

但是假设我们有一个非常混乱的目录名称,并且会减少我的工作空间,比如

T-Mob/2021-07-23--07-48-49_xperia-build-20191119010027#

2021-07-23--07-48-49_nokia-build-20191119010027/T-Mob#

那些是prompt之前的最后两个目录

我想设置一个条件 如果最后 2 个目录中的任何一个的目录长度超过阈值,例如10 个字符,长度超过 10 的目录的第一个 3 和最后 3 个字符缩短名称 例如

    2021-07-23--07-48-49_xperia-build-20191119010027  & 

2021-07-23--07-48-49_nokia-build-20191119010027 

gt 10 将分别缩短为 202*027 和 PS1

T-Mob/202*027/# for T-Mob/2021-07-23--07-48-49_xperia-build-20191119010027#202*027/T-Mob# for 2021-07-23--07-48-49_nokia-build-20191119010027/T-Mob#

一个快速的 1 Liner 来完成这项工作? 我不能在 cmets 中发布这个,所以在这里更新。参考华金回答 (thx J)

PS1=''`echo ${PWD#"${PWD%/*/*}/"} | awk -v RS='/' 'length()  <=10{printf $0"/"}; length()>10{printf "%s*%s/", substr($0,1,3),  substr($0,length()-2,3)};'| tr -d "\n"; echo "#"`''


见下文 o/p's

/root/my-applications/bin # it shortened as expected
my-*ons/bin/#cd -  # going back to prev.
/root
my-*ons/bin/#    #value of prompt is the same but I am in /root

【问题讨论】:

  • 你为什么要一个班轮?可读性(以及扩展性,可维护性)不是比简洁更重要吗?
  • 另外,不要用awksed 标记您的问题,除非您的问题是关于 awksed。如果您只是认为这些是某人可能用于构建答案的工具,那么您的问题不是关于这些工具——如果您想要使用标准 UNIX 工具的答案,请标记unix。就是说,对于诸如打印提示之类的事情——在调用之间的时间很短的情况下一遍又一遍地发生——你不想纯粹出于性能原因使用外部命令(awk 和 sed 都是);生成子进程是 shell 可以做的最慢的事情之一。
  • @user1874594:我建议您编写一个生成目录字符串的函数,并在您的PS1 中使用此函数。这更容易调试和维护。
  • @user1934428,通过命令替换在 PS1 中使用函数需要分叉子外壳。它比在PROMPT_COMMAND 中执行分配要慢(当然,假设您的PROMPT_COMMAND 代码是为了避免使用子shell 而编写的)。
  • @CharlesDuffy:正确,但由于此功能仅在打印提示时执行(即在用户交互的时间范围内),至少子进程的创建不是我担心的事情。

标签: linux bash shell unix ps1


【解决方案1】:

单线基本上总是错误的选择。编写健壮、可读和可维护的代码(并且,对于经常调用或在紧密循环中调用的东西,要高效)——不要简洁。

假设 bash 4.3 或更新版本可用:

# Given a string, a separator, and a max length, shorten any segments that are
# longer than the max length.
shortenLongSegments() {
  local -n destVar=$1; shift  # arg1: where do we write our result?
  local maxLength=$1; shift   # arg2: what's the maximum length?
  local IFS=$1; shift         # arg3: what character do we split into segments on?
  read -r -a allSegments <<<"$1"; shift        # arg4: break into an array

  for segmentIdx in "${!allSegments[@]}"; do   # iterate over array indices
    segment=${allSegments[$segmentIdx]}        # look up value for index
    if (( ${#segment} > maxLength )); then     # value over maxLength chars?
      segment="${segment:0:3}*${segment:${#segment}-3:3}" # build a short version
      allSegments[$segmentIdx]=$segment        # store shortened version in array
    fi
  done
  printf -v destVar '%s\n' "${allSegments[*]}" # build result string from array
}

# function to call from PROMPT_COMMAND to actually build a new PS1
buildNewPs1() {
  # declare our locals to avoid polluting global namespace
  local shorterPath
  # but to cache where we last ran, we need a global; be explicit.
  declare -g buildNewPs1_lastDir
  # do nothing if the directory hasn't changed
  [[ $PWD = "$buildNewPs1_lastDir" ]] && return 0
  shortenLongSegments shorterPath 10 / "$PWD"
  PS1="${shorterPath}\$"
  # update the global tracking where we last ran this code
  buildNewPs1_lastDir=$PWD
}

PROMPT_COMMAND=buildNewPs1 # call buildNewPs1 before rendering the prompt

注意printf -v destVar %s "valueToStore" 用于就地写入变量,以避免var=$(someFunction) 的性能开销。同样,我们使用 bash 4.3 功能名称变量(可通过 local -ndeclare -n 访问)以允许对目标变量名称进行参数化,而不会带来 eval 的安全风险。


如果你真的想让这个逻辑只适用于最后两个目录名(虽然我不明白为什么这比把它应用到所有其中),你可以很容易地做到这一点:

buildNewPs1() {
  local pathPrefix pathFinalSegments
  pathPrefix=${PWD%/*/*}           # everything but the last 2 segments
  pathSuffix=${PWD#"$pathPrefix"}  # only the last 2 segments

  # shorten the last 2 segments, store in a separate variable
  shortenLongSegments pathSuffixShortened 10 / "$pathSuffix"

  # combine the unshortened prefix with the shortened suffix
  PS1="${pathPrefix}${pathSuffixShortened}\$"
}

...添加仅在目录更改为此版本时重建 PS1 的性能优化留给读者作为练习。

【讨论】:

  • 谢谢查尔斯。你在编码实践方面是绝对正确的——如果你需要被理解不能写像狮身人面像的墙壁这样的东西,让人们永远猜测。这太棒了,就像从瓶子里流出的珍珠一样。但是对于PS1 在工作时的任何修饰,我只是想看看是否可以在我的~/.profile 中添加一些额外的行
  • 您应该可以将上述内容放入~/.bashrc(出于以下几个原因,我不会将其放入.profile.profile 也被非 bash shell 使用,而以上是特定于 bash 的代码,它仅在登录 shell 中调用,而不是作为这些登录 shell 的子级运行的交互式 shell)。
  • 是的,我时不时地“炮轰”,但很少为性能付出代价,除非您处理数以百万计的记录,其中节省的毫秒时间在真正意义上加起来,这是一个很小的成本,但即使作为未来shell liners 的方法或模板,您的“编程方式”也值得关注
【解决方案2】:

可能不是最好的解决方案,而是使用 awk 的快速解决方案:

PS1=`echo ${PWD#"${PWD%/*/*}/"} | awk -v RS='/' 'length()<=10{printf $0"/"}; length()>10{printf "%s*%s/", substr($0,1,3), substr($0,length()-2,3)};'| tr -d "\n"; echo "#"`

我用你的例子得到了这个结果:

T-Mob/202*027/#
202*027/T-Mob/#

【讨论】:

  • ideone.com/3ykAWR查看我们两个答案的性能对比
  • ...当然,这有点不公平,因为我的“仅在 PWD 更改时重新运行”优化使大部分代码短路。 ideone.com/TNc2Pr 是取消优化的重新运行(由于在线沙盒的模糊性 both 答案的运行性能较差,但它们之间关系的一般性质是成立的——命令替换、管道,并且开始新的awk 副本都很昂贵)。
  • @Joaquin,当你 cd 到其他目录时它会动态变化吗?
  • @pynexj,将赋值的右侧用单引号括起来,以防止在赋值时只调用一次命令替换,然后在呈现提示时对其进行评估,而不是当上面的代码运行时。
  • 不是动态的......需要每次都运行。这是我根据上面在Q中更新的fb使用的