Bash PWD Shortening
How about a Python script? This shortens the longest directory names first, one character at a time until it meets its length goal or cannot get the path any shorter. It does not shorten the last directory in the path.
(I started writing this in plain shell script but man, bash stinks at string manipulation.)
#!/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
Example output:
$ 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
How to shorten path parts between // in bash
If you want to unconditionally shorten all path components, you can do it quite easily with sed
:
sed 's:\([^/]\)[^/]*/:\1/:g'
If you want to also insert ~
at the beginning of paths which start with $HOME
, you can add that to the sed command (although this naive version assumes that $HOME
does not include a colon).
sed 's:^'"$HOME"':~:/;s:\([^/]\)[^/]*/:\1/:g'
A better solution is to use bash substitution:
short_pwd() {
local pwd=$(pwd)
pwd=${pwd/#$HOME/\~}
sed 's:\([^/]\)[^/]*/:\1/:g' <<<"$pwd"
}
With that bash function, you can then "call" it from your PS1 string:
$ PS1='$(short_pwd)\$ '
~/s/tmp$ PS1='\$ '
$
Bash prompt shortening
You could try this:
if ((start > 1)); then
name=$(IFS=/; echo .../"${elts[*]:start}")
# If your terminal is correctly set up for unicode, you can save two character positions:
# name=$(IFS=/; echo …/"${elts[*]:start}")
fi
Note that in bash, in an arithmetic context, which includes the inside of ((...))
and array subscripts, you can write just the name of the variable; no need for sigils.
Another way to do it would be
if ((start > 1)); then
printf -v name "/%s" "${elts[@]:start}"
name=...$name
fi
Yet another solution, using regex captures in the BASH_REMATCH
array rather than splitting and rejoining the string:
dirtrim () {
local path="$1";
[[ $path =~ ^"$HOME"(/.*)? ]] && path=~${BASH_REMATCH[1]};
((PROMPT_DIRTRIM)) &&
[[ $path =~ ...*((/[^/]*){$PROMPT_DIRTRIM}) ]] &&
path=…${BASH_REMATCH[1]};
echo "$path"
}
The ((PROMPT_DIRTRIM))
test is not completely robust because of the peculiarities of bash evaluation in an arithmetic context. For distribution you might prefer something like [[ $PROMPT_DIRTRIM =~ ^[1-9][0-9]*$ ]]
How can I shortern my command line prompt's current directory?
Consider this script using awk instead of sed for your case:
pwd_length=14
pwd_symbol="..."
newPWD="${PWD/#$HOME/~}"
if [ $(echo -n $newPWD | wc -c | tr -d " ") -gt $pwd_length ]
then
newPWD=$(echo -n $newPWD | awk -F '/' '{
print $1 "/" $2 "/.../" $(NF-1) "/" $(NF)}')
fi
PS1='${newPWD}$ '
For your example of directory ~/workspace/projects/project1/folder1/test
it makes PS1 as: ~/workspace/.../folder1/test
UPDATE
Above solution will set your prompt but as you noted in your comment that it will NOT change PS1 dynamically when you change directory. So here is the solution that will dynamically set PS1 when you change directories around.
Put these 2 lines in your .bashrc file:
export MYPS='$(echo -n "${PWD/#$HOME/~}" | awk -F "/" '"'"'{
if (length($0) > 14) { if (NF>4) print $1 "/" $2 "/.../" $(NF-1) "/" $NF;
else if (NF>3) print $1 "/" $2 "/.../" $NF;
else print $1 "/.../" $NF; }
else print $0;}'"'"')'
PS1='$(eval "echo ${MYPS}")$ '
if (NF > 4 && length($0) > 14)
condition in awk will only apply special handling when your current directory is more than 3 directories deep AND if length of $PWD
is more than 14 characters otherwise and it will keep PS1 as $PWD
.
eg: if current directory is ~/workspace/projects/project1$
then PS1 will be ~/workspace/projects/project1$
Effect of above in .bashrc will be as follows on your PS1:
~$ cd ~/workspace/projects/project1/folder1/test
~/workspace/.../folder1/test$ cd ..
~/workspace/.../project1/folder1$ cd ..
~/workspace/.../project1$ cd ..
~/.../projects$ cd ..
~/workspace$ cd ..
~$
Notice how prompt is changing when I change directories. Let me know if this is not what you wanted.
Get current directory or folder name (without the full path)
No need for basename, and especially no need for a subshell running pwd (which adds an extra, and expensive, fork operation); the shell can do this internally using parameter expansion:
result=${PWD##*/} # to assign to a variable
result=${result:-/} # to correct for the case where PWD=/
printf '%s\n' "${PWD##*/}" # to print to stdout
# ...more robust than echo for unusual names
# (consider a directory named -e or -n)
printf '%q\n' "${PWD##*/}" # to print to stdout, quoted for use as shell input
# ...useful to make hidden characters readable.
Note that if you're applying this technique in other circumstances (not PWD
, but some other variable holding a directory name), you might need to trim any trailing slashes. The below uses bash's extglob support to work even with multiple trailing slashes:
dirname=/path/to/somewhere//
shopt -s extglob # enable +(...) glob syntax
result=${dirname%%+(/)} # trim however many trailing slashes exist
result=${result##*/} # remove everything before the last / that still remains
result=${result:-/} # correct for dirname=/ case
printf '%s\n' "$result"
Alternatively, without extglob
:
dirname="/path/to/somewhere//"
result="${dirname%"${dirname##*[!/]}"}" # extglob-free multi-trailing-/ trim
result="${result##*/}" # remove everything before the last /
result=${result:-/} # correct for dirname=/ case
Bash: How to shorten long lines of a log file whilst keeping a fixed number of characters from beginning and end of each line?
You can do this with awk
awk '
(length > 100) {
l=length
$0 = substr($0,0,40) "..."l-80" Characters Removed..." substr($0,l-39)
}1' ./infile
Proof of Concept
$ cat ./infile
|- These are the first 40 characters --|0123456789012345678901234567890|-- These are the last 40 characters --|
12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
9012345678901234567890
2345678901234567890
12345678901234567asdfasd9as98jf-a9jfa9uhf0sd9uhfas0dfadfa890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
123456789012345678901234567890123456789012345678901234567aisfjds9dafa908sfj9asdjf9asdf89012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234567890123456asf9jasf-asjf0as8789012345678901234567890
$ awk '
(length > 100) {
l=length
$0 = substr($0,0,40) "..."l-80" Characters Removed..." substr($0,l-39)
}1' ./infile
|- These are the first 40 characters --|...31 Characters Removed...|-- These are the last 40 characters --|
1234567890123456789012345678901234567890...30 Characters Removed...1234567890123456789012345678901234567890
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
9012345678901234567890
2345678901234567890
12345678901234567asdfasd9as98jf-a9jfa9uh...70 Characters Removed...1234567890123456789012345678901234567890
1234567890123456789012345678901234567890...59 Characters Removed...1234567890123456789012345678901234567890
1234567890123456789012345678901234567890...47 Characters Removed...sf9jasf-asjf0as8789012345678901234567890
Setting value of command prompt ( PS1) based on present directory string length
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.
How to get a vim-tab-like short path in bash script?
Using sed
is easiest because of regex backreference support, but for fun and profit a pure bash
solution:
path="$(while read -rd/; do echo -n ${REPLY::1}/; done <<< "$PWD"; echo "${PWD##*/}")"
The value of $PWD
is fed into the while
loop via the herestring syntax <<<
, then split on slashes by read -rd/
. Conveniently, the last component is ignored because it doesn't end in a slash, so read
exits with a nonzero status and terminates the loop.
Inside the loop, ${REPLY::1}
takes only the first character of the path component, and echo -n
prints it without a newline.
Finally, we print the last pathname component in full using ${PWD##*/}
, which strips the longest prefix that matches */
.
Shortening my prompt in Zsh
Old question, I know, but as an alternative solution I just discovered powerlevel9k, an extension of agnoster (they appear practically identical bar a fair few tweaks), which has this functionality built in.
Just set it as your zsh theme, then in .zshrc set
POWERLEVEL9K_SHORTEN_DIR_LENGTH=2
which ensures that only two directories are listed.
Alternate options are outlined in the readme.
Related Topics
Passing Main Script Variables into Perl Modules
Linux - Replacing Spaces in the File Names
How to Enable Scrolling in Tmux Panels with Mouse Wheel
Linux: Which Process Is Causing "Device Busy" When Doing Umount
How to Schedule Tcpdump to Run for a Specific Period of Time
When Grep "\\" Xxfile I Got "Trailing Backslash"
Id_Rsa.Pub File Ssh Error: Invalid Format
"Git Add" Returning "Fatal: Outside Repository" Error
Openssh Client Hangs on Logout When Forwarding X Connections
How to Develop Linux Screen Saver
How to Configure Curl to Only Show Percentage
Lowering Linux Kernel Timer Frequency
What Is the Best Emacs Workspaces Plugin
Open Vim from Within a Bash Shell Script
Git Forces Refresh Index After Switching Between Windows and Linux