List only common parent directories for files - linux

I am searching for one file, say "file1.txt", and output of find command is like below.
/home/nicool/Desktop/file1.txt
/home/nicool/Desktop/dir1/file1.txt
/home/nicool/Desktop/dir1/dir2/file1.txt
In above cases I want only common parent directory, which is "/home/nicool/Desktop" in above case. How it can be achieved using bash? Please help to find general solution for such problem.

This script reads lines and stores the common prefix in each iteration:
# read a line into the variable "prefix", split at slashes
IFS=/ read -a prefix
# while there are more lines, one after another read them into "next",
# also split at slashes
while IFS=/ read -a next; do
new_prefix=()
# for all indexes in prefix
for ((i=0; i < "${#prefix[#]}"; ++i)); do
# if the word in the new line matches the old one
if [[ "${prefix[i]}" == "${next[i]}" ]]; then
# then append to the new prefix
new_prefix+=("${prefix[i]}")
else
# otherwise break out of the loop
break
fi
done
prefix=("${new_prefix[#]}")
done
# join an array
function join {
# copied from: http://stackoverflow.com/a/17841619/416224
local IFS="$1"
shift
echo "$*"
}
# join the common prefix array using slashes
join / "${prefix[#]}"
Example:
$ ./x.sh <<eof
/home/nicool/Desktop1/file1.txt
/home/nicool/Desktop2/dir1/file1.txt
/home/nicool/Desktop3/dir1/dir2/file1.txt
eof
/home/nicool

I don't think there's a bash builtin for this, but you can use this script, and pipe your find into it.
read -r FIRSTLINE
DIR=$(dirname "$FIRSTLINE")
while read -r NEXTLINE; do
until [[ "${NEXTLINE:0:${#DIR}}" = "$DIR" || "$DIR" = "/" ]]; do
DIR=$(dirname "$DIR")
done
done
echo $DIR
For added safety, use -print0 on your find, and adjust your read statements to have -d '\0'. This will work with filenames that have newlines.

lcp() {
local prefix path
read prefix
while read path; do
while ! [[ $path =~ ^"$prefix" ]]; do
[[ $prefix == $(dirname "$prefix") ]] && return 1
prefix=$(dirname "$prefix")
done
done
printf '%s\n' "$prefix"
return 0
}
This finds the longest common prefix of all of the lines of standard input.
$ find / -name file1.txt | lcp
/home/nicool/Desktop

Related

Bash Loop with counter gives a count of 1 when no item found. Why?

In the function below my counter works fine as long as an item is found in $DT_FILES. If the find is empty for that folder the counter gives me a count of 1 instead of 0. I am not sure what I am missing.
What the script does here is 1) makes a variable containing all the parent folders. 2) Loop through each folder, cd inside each one and makes a list of all files that contain the string "-DT-". 3) If it finds a file that doesn't not end with ".tif", it then copy the DT files and put a .tif extension to it. Very simple.
I count the number of times the loop did create a new file with the ".tif" extension.
So I am not sure why I am getting a count of 1 at times.
function create_tifs()
{
IFS=$'\n'
# create list of main folders
LIST=$( find . -maxdepth 1 -mindepth 1 -type d )
for f in $LIST
do
echo -e "\n${OG}>>> Folder processed: ${f} ${NONE}"
cd ${f}
DT_FILES=$(find . -type f -name '*-DT-*' | grep -v '.jpg')
if (( ${#DT_FILES} ))
then
count=0
for b in ${DT_FILES}
do
if [[ "${b}" != *".tif" ]]
then
# cp -n "${b}" "${b}.tif"
echo -e "TIF created ${b} as ${b}.tif"
echo
((count++))
else
echo -e "TIF already done ${b}"
fi
done
fi
echo -e "\nCount = ${count}"
}
I can't repro your problem, but your code contains several dubious constructs. Here is a refactoring might coincidentally also remove whatever problem you were experiencing.
#!/bin/bash
# Don't use non-portable function definition syntax
create_tifs() {
# Don't pollute global namespace; don't attempt to parse find output
# See also https://mywiki.wooledge.org/BashFAQ/020
local f
for f in ./*/; do
# prefer printf over echo -e
# print diagnostic messages to standard error >&2
# XXX What are these undeclared global variables?
printf "\n%s>>> Folder processed: %s %s" "$OG" "$f" "$NONE" >&2
# Again, avoid parsing find output
find "$f" -name '*-DT-*' -not -name '*.jpg' -exec sh -c '
for b; do
if [[ "${b}" != *".tif" ]]
then
# cp -n "${b}" "${b}.tif"
printf "TIF created %s as %s.tif\n" "$b" "$b" >&2
# print one line for wc
printf ".\n"
else
# XXX No newline, really??
printf "TIF already done %s" "$b" >&2
fi
done
fi' _ {} +
# Missing done!
done |
# Count lines produced by printf inside tif creation
wc -l |
sed 's/.*/Count = &/'
}
This could be further simplified by using find ./*/ instead of looping over f but then you don't (easily) get to emit a diagnostic message for each folder separately. Similarly, you could add -not -name '*.tif' but then you don't get to print "tif already done" for those.
Tangentially perhaps see also Correct Bash and shell script variable capitalization; use lower case for your private variables.
Printing a newline before your actual message (like in the first printf) is a weird antipattern, especially when you don't do that consequently. The usual arrangement would be to put a newline at the end of each emitted message.
If you've got Bash 4.0 or later you can use globstar instead of (the error-prone) find. Try this Shellcheck-clean code:
#! /bin/bash -p
shopt -s dotglob extglob nullglob globstar
function create_tifs
{
local dir dtfile
local -i count
for dir in */; do
printf '\nFolder processed: %s\n' "$dir" >&2
count=0
for dtfile in "$dir"**/*-DT-!(*.jpg); do
if [[ $dtfile == *.tif ]]; then
printf 'TIF already done %s\n' "$dtfile" >&2
else
cp -v -n -- "$dtfile" "$dtfile".tif
count+=1
fi
done
printf 'Count = %d\n' "$count" >&2
done
return 0
}
shopt -s ... enables some Bash settings that are required by the code:
dotglob enables globs to match files and directories that begin with .. find shows such files by default.
extglob enables "extended globbing" (including patterns like !(*.jpg)). See the extglob section in glob - Greg's Wiki.
nullglob makes globs expand to nothing when nothing matches (otherwise they expand to the glob pattern itself, which is almost never useful in programs).
globstar enables the use of ** to match paths recursively through directory trees.
Note that globstar is potentially dangerous in versions of Bash prior to 4.3 because it follows symlinks, possibly leading to processing the same file or directory multiple times, or getting stuck in a cycle.
The -v option with cp causes it to print details of what it does. You might prefer to drop the option and print a different format of message instead.
See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I used printf instead of echo.
I didn't use cd because it often leads to problems in programs.

Recursive directory listing in shell without using ls

I am looking for a script that recursively lists all files using export and read link and by not using ls options. I have tried the following code, but it does not fulfill the purpose. Please can you help.
My Code-
#!/bin/bash
for i in `find . -print|cut -d"/" -f2`
do
if [ -d $i ]
then
echo "Hello"
else
cd $i
echo *
fi
done
Here's a simple recursive function which does a directory listing:
list_dir() {
local i # do not use a global variable in our for loop
# ...note that 'local' is not POSIX sh, but even ash
# and dash support it.
[[ -n $1 ]] || set -- . # if no parameter is passed, default to '.'
for i in "$1"/*; do # look at directory contents
if [ -d "$i" ]; then # if our content is a directory...
list_dir "$i" # ...then recurse.
else # if our content is not a directory...
echo "Found a file: $i" # ...then list it.
fi
done
}
Alternately, if by "recurse", you just mean that you want the listing to be recursive, and can accept your code not doing any recursion itself:
#!/bin/bash
# ^-- we use non-POSIX features here, so shebang must not be #!/bin/sh
while IFS='' read -r -d '' filename; do
if [ -f "$filename" ]; then
echo "Found a file: $filename"
fi
done < <(find . -print0)
Doing this safely calls for using -print0, so that names are separated by NULs (the only character which cannot exist in a filename; newlines within names are valid.

Shell Script for File name conversion in linux

I am pretty new to Unix and have little exposure to shell script. I need to come up with a script that converts the file names from certain string values to special characters. This needs to be run in such a way all files under sub-directories also gets renamed.
For Example:
From: abc(GE)xyz(PR).txt changes
To: abc>xyz%.txt
I m ok to set if condition for all required special characters, but im not sure what options to pass and how to do it for all sub-directories.
Thanks,
Jeel
Here's one approach:
# given a filename, execute any desired replacements.
update_name() {
local orig_name_var=$1
local dest_name_var=$2
local orig_name=${!orig_name_var}
local new_name="$orig_name"
new_name=${new_name//(GE)/">"}
new_name=${new_name//(PR)/"%"} # repeat for additional substitutions
printf -v "$dest_name_var" "$new_name"
}
while IFS= read -r -d '' orig_name; do
update_name orig_name new_name
[[ $orig_name = $new_name ]] && continue
if ! [[ -e $orig_name ]]; then
orig_dirname=${orig_name%/*}
orig_basename=${orig_name##*/}
update_name orig_dirname new_dirname
if [[ -e $new_dirname/$orig_basename ]]; then
# we already renamed the directory this file is in
orig_name=$new_dirname/$orig_basename
fi
fi
mv -- "$orig_name" "$new_name"
done < <(find . '(' -name '*(GE)*' -o -name '*(PR)*' ')' -print0)

Running diff and have it stop on a difference

I have a script running that is checking multiples directories and comparing them to expanded tarballs of the same directories elsewhere.
I am using diff -r -q and what I would like is that when diff finds any difference in the recursive run it will stop running instead of going through more directories in the same run.
All help appreciated!
Thank you
#bazzargh I did try it like you suggested or like this.
for file in $(find $dir1 -type f);
do if [[ $(diff -q $file ${file/#$dir1/$dir2}) ]];
then echo differs: $file > /tmp/$runid.tmp 2>&1; break;
else echo same: $file > /dev/null; fi; done
But this only works with files that exist in both directories. If one file is missing I won't get information about that. Also the directories I am working with have over 300.000 files so it seems to be a bit of overhead to do a find for each file and then diff.
I would like something like this to work, with and elif statement that checks if $runid.tmp contains data and breaks if it does. I added 2> after the first if statement so stderr is sent to the $runid.tmp file.
for file in $(find $dir1 -type f);
do if [[ $(diff -q $file ${file/#$dir1/$dir2}) ]] 2> /tmp/$runid.tmp;
then echo differs: $file > /tmp/$runid.tmp 2>&1; break;
elif [[ -s /tmp/$runid.tmp ]];
then echo differs: $file >> /tmp/$runid.tmp 2>&1; break;
else echo same: $file > /dev/null; fi; done
Would this work?
You can do the loop over files with 'find' and break when they differ. eg for dirs foo, bar:
for file in $(find foo -type f); do if [[ $(diff -q $file ${file/#foo/bar}) ]]; then echo differs: $file; break; else echo same: $file; fi; done
NB this will not detect if 'bar' has directories that do not exist in 'foo'.
Edited to add: I just realised I overlooked the really obvious solution:
diff -rq foo bar | head -n1
It's not 'diff', but with 'awk' you can compare two files (or more) and then exit when they have a different line.
Try something like this (sorry, it's a little rough)
awk '{ h[$0] = ! h[$0] } END { for (k in h) if (h[k]) exit }' file1 file2
Sources are here and here.
edit: to break out of the loop when two files have the same line, you may have to do the loop in awk. See here.
You can try the following:
#!/usr/bin/env bash
# Determine directories to compare
d1='./someDir1'
d2='./someDir2'
# Loop over the file lists and diff corresponding files
while IFS= read -r line; do
# Split the 3-column `comm` output into indiv. variables.
lineNoTabs=${line//$'\t'}
numTabs=$(( ${#line} - ${#lineNoTabs} ))
d1Only='' d2Only='' common=''
case $numTabs in
0)
d1Only=$lineNoTabs
;;
1)
d2Only=$lineNoTabs
;;
*)
common=$lineNoTabs
;;
esac
# If a file exists in both directories, compare them,
# and exit if they differ, continue otherwise
if [[ -n $common ]]; then
diff -q "$d1/$common" "$d2/$common" || {
echo "EXITING: Diff found: '$common'" 1>&2;
exit 1; }
# Deal with files unique to either directory.
elif [[ -n $d1Only ]]; then # fie
echo "File '$d1Only' only in '$d1'."
else # implies: if [[ -n $d2Only ]]; then
echo "File '$d2Only' only in '$d2."
fi
# Note: The `comm` command below is CASE-SENSITIVE, which means:
# - The input directories must be specified case-exact.
# To change that, add `I` after the last `|` in _both_ `sed commands`.
# - The paths and names of the files diffed must match in case too.
# To change that, insert `| tr '[:upper:]' '[:lower:]' before _both_
# `sort commands.
done < <(comm \
<(find "$d1" -type f | sed 's|'"$d1/"'||' | sort) \
<(find "$d2" -type f | sed 's|'"$d2/"'||' | sort))
The approach is based on building a list of files (using find) containing relative paths (using sed to remove the root path) for each input directory, sorting the lists, and comparing them with comm, which produces 3-column, tab-separated output to indicated which lines (and therefore files) are unique to the first list, which are unique to the second list, and which lines they have in common.
Thus, the values in the 3rd column can be diffed and action taken if they're not identical.
Also, the 1st and 2nd-column values can be used to take action based on unique files.
The somewhat complicated splitting of the 3 column values output by comm into individual variables is necessary, because:
read will treat multiple tabs in sequence as a single separator
comm outputs a variable number of tabs; e.g., if there's only a 1st-column value, no tab is output at all.
I got a solution to this thanks to #bazzargh.
I use this code in my script and now it works perfectly.
for file in $(find ${intfolder} -type f);
do if [[ $(diff -q $file ${file/#${intfolder}/${EXPANDEDROOT}/${runid}/$(basename ${intfolder})}) ]] 2> ${resultfile}.tmp;
then echo differs: $file > ${resultfile}.tmp 2>&1; break;
elif [[ -s ${resultfile}.tmp ]];
then echo differs: $file >> ${resultfile}.tmp 2>&1; break;
else echo same: $file > /dev/null;
fi; done
thanks!

Bash PWD Shortening

I'm looking for a bash function that will shorten long path names to keep my PS1 variable from getting excessively long. Something along the lines of:
/this/is/the/path/to/a/really/long/directory/i/would/like/shortened
might end up as:
/t../i../t../p../to/a/r../l../d../i/w../like/shortened
something that the took the path and a maximum acceptable number of characters to shorten to would be perfect for my .bashrc file.
Doesn't give the same result, but my ~/.bashrc contains
_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)\$ '
which limits the path shown to 20 characters max. If the path is over 20 characters, it will be shown like /...d/like/shortened or ~/.../like/shortened.
Here's a bash-only solution that you might like. This shortens each part of the path down to the shortest prefix that can still be tab-completed, and uses * instead of .. as the filler.
#!/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.
Give it the length as the first argument, and the path as the optional second argument. If no second argument is given, it uses the current working directory.
This will try to shorten to under the length given. If that's not possible, it just gives the shortest path it can give.
Algorithmically speaking, this is probably horrible, but it ends up being pretty fast. (The key to quick shell scripts is avoiding subshells and external commands, especially in inner loops.)
By design, it only shortens by 2 or more characters ('hom*' is just as many characters as 'home').
It's not perfect. There are some situations where it won't shorten as much as is possible, like if there are several files whose filenames share a prefix (If foobar1 and foobar2 exist, foobar3 won't be shortened.)
FYI, there is a built-in \w "shortener" in Bash 4+:
PROMPT_DIRTRIM=3
will shorten /var/lib/whatever/foo/bar/baz to .../foo/bar/baz.
I made some improvements to Evan Krall's code. It now checks to see if your path starts in $HOME and begins the shortened variety with ~/ instead of /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.
Also, here are some functions I put in my .bashrc file to shrink the path shown by the shell. I'm not sure if editing $PWD like this is completely safe as some scripts might depend on a valid $PWD string, but so far I haven't had problems with occasional use. Note that I saved the above script as "shortdir" and put it in my PATH.
function tinypwd(){
PWD=`shortdir`
}
function hugepwd(){
PWD=`pwd`
}
EDIT Oct 19 2010
The proper way to do the aliases in bash is by modifying the $PS1 variable; this is how the prompt is parsed. In MOST cases (99% of the time) the current path is in the prompt string as "\w". We can use sed to replace this with shortdir, like so:
#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') "
}
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
Here's another spin on Evan's answer:
This one uses plus (+) instead of an asterisk (*) for truncated paths. It replaces the HOME path with ~, and it leaves the final directory segment intact. If the final segment is over 20 characters, it shortens it to the tab-completable bit and adds an ellipses (...).
#!/bin/bash
# Modified from http://stackoverflow.com/a/1617048/359287
# 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.
}
Download the script here and include it in your .bashrc:
https://raw.github.com/alanctkc/dotfiles/master/.bash_scripts/pwd-prompt.bash
. ~/.bash_scripts/pwd-prompt.bash
Add the directory to your PS1 like this:
export PS1="[other stuff...] \$(__pwd_ps1)\$ "
Here's a relatively easy perl solution. This is short
enough that you could embed it directly in PS1 rather
than invoking a script. It gives all the characters
of the truncated names rather than replacing with '.'
$ echo '/this/is/a/realy/long/path/id/like/shortened' |
perl -F/ -ane 'print join( "/", map { $i++ &lt #F - 2 ?
substr $_,0,3 : $_ } #F)'
/thi/is/a/rea/lon/pat/id/like/shortened
I'm not immediately seeing a nice way to replace characters with '.',
but here's an ugly way:
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
Try this:
PS1='$(pp="$PWD/" q=${pp/#"$HOME/"/} p=${q%?};((${#p}>19))&&echo "${p::9}…${p:(-9)}"||echo "$p") \$'
It transforms
~/.vim/bundle/ack.vim/plugin
to
.vim/bund…im/plugin
transfrom
/usr/share/doc/xorg-x11-font-utils-7.5/
to
/usr/shar…utils-7.5
And when $PWD same as $HOME, show nothing.
Bonus: you could modify number of length to fit you need.

Resources