Skip to content

PATH manipulation…

September 7, 2007

I just read this blog entry about reducing your path and realised there were two reasons I would not be doing that:

  1. Calling perl from a dot file would be one step to far.

  2. I have some korn shell functions for manipulating PATH variables that I have been using for years that amongst other things prevent duplicate entries in your path.

I realise I should share these with the world, however since the SCCS is dated 1996 I will use the defence that they work and are very useful when you all tell me that the scripts are not nice.

Shell function

Purpose

apath [ -p PATH_VARIABLE][-d|-f|-n] path …

Append the paths to end of the PATH_VARIABLE listed. If the path is already in the PATH_VARIABLE then the path is moved to the end of the variable.

ipath [ -p PATH_VARIABLE][-d|-f|-n] path …

Insert the paths at the beginning of the PATH_VARIABLE listed. If the path is already in the PATH_VARIABLE then the path is moved to the beginning of the variable.

rpath [ -p PATH_VARIABLE][-d|-f|-n] path …

Delete the path from the PATH_VARIABLE .

tpath [ -p PATH_VARIABLE] path ….

Test if the path is in the PATH_VARIABLE



All the scripts, except tpath take the same arguments. -p lets you select the variable you are working on. -d, which is the default says that this is a path of directories, -f says it is a path of files and -n that it is a path of objects that don’t exist in the file system (useful for NIS_PATH when I was supporting NIS+).

To use them you simply save the script in a directory that is part of your FPATH. Then make hard links to it for all the names:


$ for i in rpath tpath ipath > do > ln apath $i > done

and now you can use them. Here is a typical use from my .profile:


ipath /opt/SUNWspro/bin ipath -p MANPATH /opt/SUNWspro/man 


This adds /opt/SUNWspro/bin to my PATH and /opt/SUNWspro/man to my MANPATH. (Actually this is a fabrication as I have a shell function like this to cover the common case:

function addpath {      typeset i      for i in $@      do            if apath $i            then                  apath -p MANPATH ${i%/*}/man            fi     done }  

so my MANPATH remains close to my PATH but lets keep things simple.)


Not rocket science but really useful, even more so for interactive sessions when you want to manipulate your path.

Advertisements

From → Solaris

9 Comments
  1. Roland Mainz permalink

    Erm, what about rewriting the script to use arrays (e.g. convert PATH to path_array, add/remove/modify items from the array and convert the array back to a PATH variable (and maybe apply the ruleset from http://www.opensolaris.org/os/project/shell/shellstyle/ , too)) ? IMO that may be more efficient…

  2. Roland Mainz permalink

    I forgot to provide a small example for ksh93:
    — snip —
    typeset -a x
    integer i
    integer numelements
    # fill array
    IFS=$’\n’
    x+=( ${PATH//:/$’\n’} )
    numelements=${#x}
    # print array content
    for((i=0 ; i < numelements ; i++ )) ; do
    printf "loop 1: element %d =\"%s\"\n" i "${x[i]}"
    done
    # remove elements by fiter
    for((i=0 ; i < numelements ; i++ )) ; do
    if [[ "${x[i]}" = *mit* ]] ; then
    printf "unsetting element %d =\"%s\"\n" i "${x[i]}"
    unset "x[$i]"
    fi
    done
    # print new view of array
    numelements=${#x}
    for((i=0 ; i < numelements ; i++ )) ; do
    printf "loop 2: element %d =\"%s\"\n" i "${x[i]}"
    done
    # print new PATH variable
    (IFS=’:’ ; print "PATH=${x[*]}")
    # EOF.
    — snip —
    The 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 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 ="/usr/games"
    loop 2: element 5 ="/opt/gnome/bin"
    loop 2: element 6 ="/opt/kde3/bin"
    loop 2: element 7 =""
    loop 2: element 8 =""
    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:/usr/games:/opt/gnome/bin:/opt/kde3/bin
    — snip —

  3. Roland Mainz permalink

    Improved version, now uses functions, arrays and nameref’s and supports (efficient) append_if_not_present operation:
    — snip —
    typeset -a x
    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" and "insert_after" operation on demand.

  4. I’m sure they could be rewritten to be better and faster but since they have worked for over 10 years for me I can’t justify the effort.
    If anyone wants to contribute a better set of path manipulation routines I would welcome them.

  5. Just wanted to say thanks, having used these scripts for over 10 years myself. Roland’s suggestion is interesting; alas array support is not available in our current implementation of ksh (/usr/bin/ksh).

  6. Marc permalink

    With zsh, you don’t even have to convert PATH to an array, the shell does it for you, it is called path, and when you update either path or PATH, the other one gets updated as well (same for manpath). But then using zsh is cheating, everything is already implemented in it…

  7. Roland Mainz permalink

    Stacey Marshall wrote:
    > Roland’s suggestion is interesting; alas array
    > support is not available in our current
    > implementation of ksh (/usr/bin/ksh).
    Erm, Solaris 11/B72 now contains ksh93 as /usr/bin/ksh93.
    And even if you use another shell you could temporarity invoke ksh93 to filter PATH, e.g.
    $ PATH="$(ksh93 -c ‘do_some_stuff_with_PATH’)" # …

  8. Roland Mainz permalink

    Marc wrote:
    > With zsh, you don’t even have to convert PATH to
    > an array, the shell does it for you, it is called
    > path, and when you update either path or PATH, the
    > other one gets updated as well (same for manpath).
    > But then using zsh is cheating, everything is
    > already implemented in it…
    Erm, zsh gets away with this because it doesn’t care about conforming to the POSIX shell standard (which ksh93 implements).
    However for ksh93 there is a easy solution to get the same behaviour using "discipline functions", e.g. functions which track the "get"/"set"/"unset" of any shell variable.
    Basically it looks like this:
    — snip —
    # discipline function called when someone
    # reads the variable PATH – we return
    # the value calculated from the array
    function PATH.get
    {
    .sh.value="$(convert_array_to_path)"
    }
    # discipline function called when someone
    # writes to the variable PATH – we store
    # the new value in the array.
    function PATH.set
    {
    path_to_array "${.sh.value}"
    }
    — snip —

  9. While ksh93 is in build 72 there are a number of problems with just calling ksh93 every time you want to add an entry to the path. Firstly not every system runs build 72 or higher. These shell functions work on all Solaris release based on SunOS 5.x and there are plenty of systems out there that are not going to be running an OpenSolaris based build for many many years.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: