1

I know I can do this to reflect just last 2 directories in the PS1 value.

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

but lets say we have a directory name that's really messy and will reduce my working space , like

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

OR

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

those are the last 2 directories before the prompt

I want to set a condition if directory length of either of the last 2 directories is more than a threshold e.g. 10 chars , shorten the name with 1st 3 and last 3 chars of the directory (s) whose length exceeds 10 e.g.

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

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

both gt 10 will be shortened to 202*027 & PS1 will be respectively

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

A quick 1 Liner to get this done ? I cant post this in comments so Updating here. Ref to Joaquins Answer ( 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 "#"`''


see below 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
user1874594
  • 2,277
  • 1
  • 25
  • 49
  • 2
    Why do you want a one liner? Isn't readability (and by extension, maintainability) more important than terseness? – Charles Duffy Aug 17 '21 at 01:49
  • 2
    Also, don't tag your questions with `awk` or `sed` unless your question is _about_ `awk` or `sed`. If you just think those are tools someone might use in building an answer, your question isn't _about_ those tools -- tag `unix` if you want answers that use standard UNIX tools. That said, for something like printing a prompt -- that happens over and over with very little time between calls -- you don't want to use external commands (which both awk and sed are) purely for performance reasons; spawning off subprocesses is one of the slowest things the shell can do. – Charles Duffy Aug 17 '21 at 02:05
  • 1
    @user1874594: I suggest that you write a function which produces the directory string, and use this function in your `PS1`. This is easier to debug and to maintain. – user1934428 Aug 17 '21 at 06:33
  • 1
    @user1934428, using a function in your PS1 via command substitution requires forking a subshell. It's slower than performing an assignment in `PROMPT_COMMAND` (assuming, of course, that your `PROMPT_COMMAND` code is written to avoid subshells). – Charles Duffy Aug 17 '21 at 13:06
  • 1
    @CharlesDuffy: Correct, but since this function is executed only when the prompt is printed (i.e. at the timescale of user interaction), at least the creation of the child process is not something I would worry. – user1934428 Aug 17 '21 at 13:22
  • 1
    I suppose it depends on whether we're running on real, modern hardware or an embedded system. Twentysomething years ago, lag from starting a subshell during prompt rendering was noticeable. *Shakes cane at kids on lawn* – Charles Duffy Aug 17 '21 at 15:30

2 Answers2

2

A one-liner is basically always the wrong choice. Write code to be robust, readable and maintainable (and, for something that's called frequently or in a tight loop, to be efficient) -- not to be terse.

Assuming availability of bash 4.3 or newer:

# 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

Note that printf -v destVar %s "valueToStore" is used to write to variables in-place, to avoid the performance overhead of var=$(someFunction). Similarly, we're using the bash 4.3 feature namevars -- accessible with local -n or declare -n -- to allow destination variable names to be parameterized without the security risk of eval.


If you really want to make this logic only apply to the last two directory names (though I don't see why that would be better than applying it to all of them), you can do that easily enough:

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}\$"
}

...adding the performance optimization that only rebuilds PS1 when the directory changed to this version is left as an exercise to the reader.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thanks Charles. You are abs correct in terms of coding practice - if you need to be understood cant be writing stuff like the walls of Sphinx & leave ppl guessing forever. This is wonderful & flows like pearls off a bottle. But for whatever grooming that `PS1` while working was worth , I just wanted to see if It could be pulled away putting a few extra lines in my `~/.profile` – user1874594 Aug 17 '21 at 05:18
  • 1
    You should be able to put the above in `~/.bashrc` (I wouldn't put it in `.profile` for a few reasons: `.profile` is also used by non-bash shells while the above is bash-specific code, and it's only called for login shells, not interactive shells run as children of those login shells). – Charles Duffy Aug 17 '21 at 13:32
  • yes I had been 'shelling' every now and often but seldom paid imp to performance 'cos unless your dealing with millions to records where ms of time saved adds up in real sense , its a small cost but your "programming manners" that maketh the programming (super) man are noteworthy even as an approach aka template for future `shell` liners & will be kept in mind – user1874594 Aug 18 '21 at 12:25
1

Probably not the best solution, but a quick solution using 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 "#"`

I got this results with your examples:

T-Mob/202*027/#
202*027/T-Mob/#
Joaquin
  • 2,013
  • 3
  • 14
  • 26
  • See performance of our two answers compared at https://ideone.com/3ykAWR – Charles Duffy Aug 17 '21 at 02:44
  • 1
    ...granted, that's a little unfair because of the "only rerun if PWD changes" optimization in mine that short-circuits most of the code. https://ideone.com/TNc2Pr is a rerun with that optimization taken out (thanks to the vagueries of online sandboxes _both_ answers have worse performance that run, but the general nature of the relationship between them holds -- command substitutions, pipelines, and starting new copies of `awk` are all expensive). – Charles Duffy Aug 17 '21 at 02:46
  • @Joaquin, does it dynamically change when you cd to other dirs? – pynexj Aug 17 '21 at 03:21
  • @pynexj, wrap the right-hand side of the assignment in single quotes to prevent the command substitution from being called just once at assignment time, and then it should be evaluated when the prompt is rendered instead of when the above code is run. – Charles Duffy Aug 17 '21 at 13:30
  • not dynamic ....needs to be run everytime. This is what I used based on above fb upated in the Q – user1874594 Aug 18 '21 at 13:19