Thursday Sep 06, 2007
Thursday Sep 06, 2007
How many people out there have huge PATHs partly because your work requires various tools from different installations, and partly because you end up with multiple instances of the same directory within your PATH?
I suspect more than a few. I just recently added this to the end of my .bashrc, but I welcome more efficient ways of accomplishing the same thing:
PATH="$(echo "$PATH" | tr : '\012' |
perl -n -e 'chomp; if (!defined($h{$_})) { print ":" if $. > 1; print "$_"; $h{$_} = 1; }')"
Invoking perl every time I start a shell is, shall we say, less than satisfying.
Update: From Nevin comes this bash function, which couldn't be formatted properly in the comments section, so I add it here between a couple of pretty <pre> tags:
strippath()
{
local ifs="$IFS"
IFS=:
local path
path=(${1:-${PATH}})
local p=${#path[*]}
until ((0 == p))
do
((--p))
if [ -d ${path[$p]} ]
then
local e;
for ((e = 0; e != p; ++e))
do
if [ "${path[$p]}" == "${path[$e]}" ]
then
unset path[$p]
break;
fi
done
else
unset path[$p]
fi
done
echo "${path[*]}"
IFS="$ifs"
}
PATH="$(strippath)"
Here is the script I use under bash, which also eliminates non-existent directories from the path. I don't remember if it handles leading, trailing, or double colons directly.
strippath()
{
local ifs="$IFS"
IFS=:
local path
path=(${1:-${PATH}})
local p=${#path[*]}
until ((0 == p))
do
((--p))
if [ -d ${path[$p]} ]
then
local e;
for ((e = 0; e != p; ++e))
do
if [ "${path[$p]}" == "${path[$e]}" ]
then
unset path[$p]
break;
fi
done
else
unset path[$p]
fi
done
echo "${path[*]}"
IFS="$ifs"
}
PATH="$(strippath)"
Posted by Nevin ":-)" on September 06, 2007 at 05:48 PM BST #
Sorry about the formatting, but apparently I'm not allowed to use HTML Syntax. :-(
One other note: I have this in my .bash_profile, not my .bashrc, as .bashrc doesn't usually muck around with paths.
Posted by Nevin ":-)" on September 06, 2007 at 05:50 PM BST #
Posted by The dot in ... --- ... on September 07, 2007 at 09:27 AM BST #
Nevin: Thanks! I'll add it as an update to the blog entry, and format it appropriately.
Posted by Mark J Musante on September 07, 2007 at 10:54 AM BST #
Below is a ksh version of the stuff above which converts the value of a PATH, FPATH or MANPATH variable to an array which is then manipulated (filter, remove, append etc.) and finally written back into a *PATH-like format (the advantage is that this is faster for larger number of elements (e.g. FPATH may contain a few dozend elements if various packages need to be combined)):
-- snip --
typeset -a x # needs ksh93q or higher
integer i
integer numelements
# fill array from a path-like variable (PATH, FPATH, MANPATH)
function pathtopatharray
{
typeset p="$1"
nameref ar=$2
ar+=( ${p//:/$'\n'} )
}
# convert path array back to path-like variable representation
function patharraytopath
{
nameref ar=$1
(IFS=':' ; print "${ar[*]}")
}
# print a path array with label and index
function printpatharray
{
nameref ar=$1
typeset label="$2"
integer numelements=${#ar}
integer i
for((i=0 ; i < numelements ; i++ )) ; do
printf "%s: element %d =\"%s\"\n" "${label}" i "${ar[i]}"
done
}
# filter path array via shell pattern
function filterpatharraybypattern
{
nameref ar=$1
typeset pattern="$2"
integer numelements=${#ar}
integer i
for((i=0 ; i < numelements ; i++ )) ; do
if [[ "${ar[i]}" = ${pattern} ]] ; then
printf "unsetting element %d =\"%s\"\n" i "${ar[i]}"
unset "ar[$i]"
fi
done
}
function appendtopatharrayifnotpresent
{
nameref ar=$1
typeset -A set_paths # paths which are set
integer numelements=${#ar}
integer i
typeset p
# use the assiciative array "set_paths" to remeber
# which paths are set - we use the path name as
# index to use it for lookup below
for((i=0 ; i < numelements ; i++ )) ; do
set_paths["${ar[i]}"]="${ar[i]}"
done
shift
while (( $# > 0 )) ; do
p="$1"
if [[ "${set_paths["$p"]}" = "" ]] ; then
set_paths["$p"]="$p"
ar+=( "$p" )
fi
shift
done
}
# main
pathtopatharray "${PATH}" x
# print array content
printpatharray x "loop 1"
# remove elements by fiter
# (extended regular expression to filter "games" and "mit" kerb stuff)
filterpatharraybypattern x '~(E)(mit|game)'
appendtopatharrayifnotpresent x "/bin" "/bin" "/bix" "/bax" "/bix"
# print new view of array
printpatharray x "loop 2"
# print new PATH variable
print "PATH=$(patharraytopath x)"
# EOF.
-- snip --
Output looks like this:
-- snip --
$ ksh x.sh
loop 1: element 0 ="/usr/local/bin"
loop 1: element 1 ="/usr/bin"
loop 1: element 2 ="/usr/X11R6/bin"
loop 1: element 3 ="/bin"
loop 1: element 4 ="/usr/games"
loop 1: element 5 ="/opt/gnome/bin"
loop 1: element 6 ="/opt/kde3/bin"
loop 1: element 7 ="/usr/lib/mit/bin"
loop 1: element 8 ="/usr/lib/mit/sbin"
loop 1: element 9 =""
loop 1: element 10 =""
loop 1: element 11 =""
loop 1: element 12 =""
loop 1: element 13 =""
unsetting element 4 ="/usr/games"
unsetting element 7 ="/usr/lib/mit/bin"
unsetting element 8 ="/usr/lib/mit/sbin"
loop 2: element 0 ="/usr/local/bin"
loop 2: element 1 ="/usr/bin"
loop 2: element 2 ="/usr/X11R6/bin"
loop 2: element 3 ="/bin"
loop 2: element 4 =""
loop 2: element 5 ="/opt/gnome/bin"
loop 2: element 6 ="/opt/kde3/bin"
loop 2: element 7 ="/bix"
loop 2: element 8 ="/bax"
loop 2: element 9 =""
loop 2: element 10 =""
loop 2: element 11 =""
loop 2: element 12 =""
loop 2: element 13 =""
PATH=/usr/local/bin:/usr/bin:/usr/X11R6/bin:/bin:/opt/gnome/bin:/opt/kde3/bin:/bix:/bax
-- snip --
Version with "insert_before", "insert_after" and more filter operations on demand.
Posted by Roland Mainz on September 07, 2007 at 11:51 PM BST #