Shell functions to manipulate the PATH

From: Enrique Perez-Terron (enrio_at_online.no)
Date: 09/03/05

  • Next message: Timothy Murphy: "Re: Installing Dual Boot : Fedora Core 4 and Debian on the same HD"
    Date: Sat, 03 Sep 2005 12:00:16 +0200
    
    

    I would like to suggest to distribution maintainers that they include
    functions similar to the ones below in some file under /etc/profile.d.

    The reason is mainly to present a friendlier interface to newbies that
    often have to fiddle with the PATH variable before they get everything
    properly set up.

    Yes, quite often when the newbies want to change the PATH it is misguided,
    because they are trying to run a /sbin command as ordinary users. When
    they try to figure out why it does not work, they learn about the PATH...
    But you can't improve this by making it hard to change the PATH. Instead,
    let the users get the PATH set as they wanted it with less effort, and
    they will have more energy left for understanding what happens next. If
    they need privileges for what they try next, the error message will
    hopefully hint about that. "bash: lsof: no such command" is not very
    explicit here.

    On today's computers, the PATH tends to be quite long. Having to type it
    in from scratch is quite "unfriendly" because no newbie remembers the
    exact spelling of all those variables, and, if he has hosed the PATH
    variable, commands like ls may just not be available.

    Quite certainly, as soon as you get your ears a little dryer, you might
    not need this much. But when you try to explain things to newbies, and
    want to show them how it works, you are essentially showing them that
    "Linux/Unix is hard, error-prone, and unfriendly".

    Even the simplest of tasks, determining what is in your PATH, typing "echo
    $PATH" shows you a string that most often breaks across several lines in
    the command window, breaking in the middle of a word, and with all the
    directories run together with no air between to visually separate them. It
    looks more like the maze of wires under your car's hood than like the
    dashboard in front of the driver's seat.

    There is one good reason writing such scripts have not been very
    successfull in the past, and that is that if you hose you PATH, and sed,
    cut, and all the others become unavailable, then your script will have to
    have its own PATH setting, or use fixed paths. Then the script is less
    poratble and does not work when you need it most, as when you created a
    chroot jail and placed some commands in the wrong directory or nowhere at
    all.

    The script below needs bash to run, but no other tools. If the file
    containing the functions has been sourced when you started the shell, the
    functions are there quite independent of the PATH and quite independent of
    any other commands.

    I bet quite a few people have written similar scripts with similar, yet
    different names and options. The sad thing is that those capable of
    writing such things are not newbies, they seldom need such tools any more.
    For these functions to be usefull they must be standardized and
    distributed with most distributions, so they are found by the newbies when
    told by their peers to try them. And their peers need to know they are
    there and work the same on most computers before they bother to tell the
    newbies to try. When dealing with unexperienced users you don't want to
    qualify every statement you make with things like "if that command is in
    that directory, or you can see if you have this other command..."

    I therefore urge people to try the commands below, install them in a file
    in /etc/profiles.d, and report bugs and critisize all aspects. Then, when
    they are as good as they can be, let's try to persuade the maintainers of
    the various distributions to include them. When one day they are
    everywhere, the old way will look rather backward.

    The only thing I have not included here is a function to save the modified
    path in the user's .bashrc. That step requires quite a bit of thinking
    before we get it right. We need to have modalities for saving PATHs that
    will be used by the system on the next boot, e.g, in /etc/rc on Fedora
    systems, or in a file in /etc/sysconfig, where it can be sourced in by the
    scripts that need it. On many systems, the display manager is started
    directly from init. There needs to be a place to set a PATH so it will be
    seen by the window manager. On some system this is simply the user's
    .bashrc, because the display manager runs a bash script after setting its
    user id to the new user, and everything else starts from there. But the
    hardest part is, if setting the PATH systemwide becomes easier, it also
    means more people will hose their compupters to the point where they
    believe they can only get over it by reinstalling the whole system. That
    too is little friendly. So, what can be done to make the whole thing
    sufficiently intuitive?

    Anyway, here comes the script:

    ------8<-----------------
    #!/bin/bash

    # Routines to manipulate the PATH:
    #
    # `path' prints one item per line.
    # `path args' prepends args, then prints
    # `path --quiet args' prepends args, doesn't print (for .profile)
    # `path --append args' appends args, then prints
    # `path --delete args' deletes args, then prints
    #
    # All the above clean up PATH removing duplicates and adding implicit dots.
    # If you appended, but had it before, the earlier occurrence stays.
    # Of course you can combine --quiet with --append or --delete.
    #
    # `path --show' prints one item per line with no cleanup

    # These routines rely solely on bash built-in functionality.
    # No external tools like sed, awk, expr, etc are called.
    # They do not even depend on the PATH! (Good when the path has been hosed.)

    # The routines are safe against spaces in the directory names.
    # You can for instance have "/home/robert/My Programs" in your path
    # You must, however, be careful to quote spaces on the command line.

    # The only "problem" I know about is with directories whose names end
    # in newlines :)

    # More goodies:
    # All options have long and short forms. See the code below for the
    # complete table.
    #
    # `path -t args' Tests if the named args are all in the path.
    # No output, only exit status
    # `path -l args' Appends args _after_ having deleted them from the
    # path i.e, moves them last or appends.
    # 'path -L args' Moves last as above, but only args that are already
    # in the path. I.e., moves last if present, no
    appending
    # new dirs.
    ####

    # Auxiliary functions:

    # Convert eg., "foo::bar:" to ":foo:.:bar:.:". Notice extra leading
    # and trailing ':'.
    __path_colon_dotify() {
         local pth;
         pth=:$1${1:+:} # If $1 is empty, we want ":", not "::".
         pth=${pth//::/:.:} # Repeat becuse "::::" yields ":.::.:"
         echo "${pth//::/:.:}"
    }

    # Convert eg., ":foo:bar:" to "foo:bar"
    __path_uncolonify() {
         local pth;
         pth=${1#:}
         echo "${pth%:}"
    }

    # Remove all but first occurrence of each element. Assume colonified
    __path_simplify() {
         local array pth dir n
         IFS=: eval "array=(\$1)" # Yields an empty first element
         pth=: # Make sure there is a trailing ':'.
         n=${#array[*]} # Work backwards from the last element
         while [ $n -gt 1 ]; do # Exclude empty leading element.
            dir="${array[--n]}"
            pth=":$dir${pth//:$dir:/:}"
         done
         echo "$pth"
    }

    # Tattarata, now comes...

    # Add or remove args to/from PATH. Cleanup repetitions.
    path() {
         local quiet mode pth dir newline
         mode=prepend
         while [ $# -gt 0 ]; do
            case $1 in
                (- | --) shift; break;;
                (-a | --append) mode=append;;
                (-p | --prepend) mode=prepend;;
                (-d | --delete) mode=delete;;
                (-l | --last) mode=last;;
                (-L | --last-if-present) mode=last-if-present;;
                (-t | --test) mode=test;;
                (-s | --show) mode=show;;
                (-q | --quiet | --silent) quiet=true;;
                (*) break;;
            esac
            shift
         done
         pth=$(__path_colon_dotify "$PATH")
         case $mode in
            prepend)
                [ -n "$*" ] && IFS=: eval "pth=:\$*\$pth"
                pth=$(__path_simplify "$pth")
                PATH=$(__path_uncolonify "$pth");;
            append)
                [ -n "$*" ] && IFS=: eval "pth=\$pth\$*:"
                pth=$(__path_simplify "$pth")
                PATH=$(__path_uncolonify "$pth");;
            delete)
                pth=$(__path_simplify "$pth")
                for dir; do
                    pth=${pth//:$dir:/:}
                done
                PATH=$(__path_uncolonify "$pth");;
            last)
                path -q -d "$@"
                path -q -a "$@";;
            last-if-present)
                for dir; do
                    path -t "$dir" && path -q -l "$dir"
                done;;
            test)
                for dir; do
                    case $pth in
                        (*:$dir:*) continue ;;
                        (*) return 1;;
                    esac
                done
                return 0;;
         esac
         if [ -z "$quiet" ]; then
            newline=$'\n' # Using $'\n' directly in the substit. does not work
            echo "${PATH//:/$newline}"
         fi
    }


  • Next message: Timothy Murphy: "Re: Installing Dual Boot : Fedora Core 4 and Debian on the same HD"

    Relevant Pages

    • Newbie discovers two useful apps...
      ... You pros can flip to the next post, there's nothing here for you, but my fellow newbies may find this interesting... ... A typical example is someone who wants some promotion to end at 7:30 am. Accomplishing this is pretty simple, but has required me to log into the server to manually execute some command, or write some tiny script and have it execute by cron in some tortured way. ... The standard way to install tinydns has you install another DJB product called daemontools. ...
      (freebsd-questions)
    • Re: Finding name of a file in a directory
      ... You just ensure that it accepts command line args. ... to the script, but I usually just give them an hta and have them browse ... without even the implied warranty of merchantability ...
      (microsoft.public.scripting.vbscript)
    • Re: Finding name of a file in a directory
      ... You just ensure that it accepts command line args. ... file to the script and it acts as a command line arg, ... to the script, but I usually just give them an hta and have them browse ...
      (microsoft.public.scripting.vbscript)
    • Re: WHILE and IF
      ... command that possibly take no arguments: ... wrong # args: no script following "else" argument ... If this weren't so already for a looong time, ...
      (comp.lang.tcl)
    • Re: Problems trying to configure Linux laptop to print to Windows XP shared printer
      ... map to guest = Never ... check password script = ... enumports command = ... ldap delete dn = No ...
      (comp.os.linux.setup)