There are several ways to handle this problem.
You can prepend or append the new directory, using PATH=
"newdir
:$PATH"
or PATH="$PATH
:newdir"
,
though you should make sure the directory isn’t already in the
$PATH
.
If you need to edit something in the middle of the path, you can echo the path to the screen, then use your terminal’s kill and yank (copy and paste) facility to duplicate it on a new line and edit it. Or, you can add the “Macros that are convenient for shell interaction” from the readline documentation at http://tiswww.tis.case.edu/php/chet/readline/readline.html#SEC12, specifically:
# edit the path "C-xp": "PATH=${PATH}eC-eC-aefC-f" # [...] # Edit variable on current line. "M-C-v": "C-aC-k$C-yM-C-eC-aC-y="
Then pressing Ctrl-X P will display the $PATH
on the current line for you to edit,
while typing any variable name and pressing Meta Ctrl-V will display that variable for editing. Very
handy.
For simple cases you can use this quick function (adapted slightly from Red Hat Linux’s /etc/profile):
# cookbook filename: func_pathmunge # Adapted from Red Hat Linux function pathmunge { if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then if [ "$2" = "after" ] ; then PATH="$PATH:$1" else PATH="$1:$PATH" fi fi }
The egrep pattern looks for the value in $1
between
two : or (|) at the beginning (^) or end ($) of the $PATH
string. We chose to use a case
statement in our function, and to force a
leading and trailing : to do the same thing. Ours is theoretically
faster since it uses a shell built-in, but the Red Hat version is more
concise. Our version is also an excellent illustration of the fact that
the if
command works
on exit codes, so the first if
works by using the exit code set by
grep, while the second requires the use of the test
operator ( [ ] ).
For more complicated cases when you’d like a lot of error checking you can source and then use the following more generic functions:
# cookbook filename: func_tweak_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Add a directory to the beginning or end of your path as long as it's not # already present. Does not take into account symbolic links! # Returns: 1 or sets the new $PATH # Called like: add_to_path <directory> (pre|post) function add_to_path { local location=$1 local directory=$2 # Make sure we have something to work with if [ -z "$location" -o -z "$directory" ]; then echo "$0:$FUNCNAME: requires a location and a directory to add" >&2 echo "e.g. add_to_path pre /bin" >&2 return 1 fi # Make sure the directory is not relative if [ $(echo $directory | grep '^/') ]; then :echo "$0:$FUNCNAME: '$directory' is absolute" >&2 else echo "$0:$FUNCNAME: can't add relative directory '$directory' to the $PATH" >&2 return 1 fi # Make sure the directory to add actually exists if [ -d "$directory" ]; then : echo "$0:$FUNCNAME: directory exists" >&2 else echo "$0:$FUNCNAME: '$directory' does not exist--aborting" >&2 return 1 fi # Make sure it's not already in the PATH if [ $(contains "$PATH" "$directory") ]; then echo "$0:$FUNCNAME: '$directory' already in $PATH--aborting" >&2 else :echo "$0:$FUNCNAME: adding directory to $PATH" >&2 fi # Figure out what to do case $location in pre* ) PATH="$directory:$PATH" ;; post* ) PATH="$PATH:$directory" ;; * ) PATH="$PATH:$directory" ;; esac # Clean up the new path, then set it PATH=$(clean_path $PATH) } # end of function add_to_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Remove a directory from your path, if present. # Returns: sets the new $PATH # Called like: rm_from_path <directory> function rm_from_path { local directory=$1 # Remove all instances of $directory from $PATH PATH=${PATH//$directory/} # Clean up the new path, then set it PATH=$(clean_path $PATH) } # end of function rm_from_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Remove leading/trailing or duplicate ':', remove duplicate entries # Returns: echos the "cleaned up" path # Called like: cleaned_path=$(clean_path $PATH) function clean_path { local path=$1 local newpath local directory # Make sure we have something to work with [ -z "$path" ] && return 1 # Remove duplicate directories, if any for directory in ${path//:/ }; do contains "$newpath" "$directory" && newpath="${newpath}:${directory}" done # Remove any leading ':' separators # Remove any trailing ':' separators # Remove any duplicate ':' separators newpath=$(echo $newpath | sed 's/^:*//; s/:*$//; s/::/:/g') # Return the new path echo $newpath } # end of function clean_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Determine if the path contains a given directory # Return 1 if target is contained within pattern, 0 otherwise # Called like: contains $PATH $dir function contains { local pattern=":$1:" local target=$2 # This will be a case-sensitive comparison unless nocasematch is set case $pattern in *:$target:* ) return 1;; * ) return 0;; esac } # end of function contains
Use as follows:
$ source chpath $ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/bin/X11:/usr/X11R6/bin:/home/jp/bin $ add_to_path pre foo -bash:add_to_path: can't add relative directory 'foo' to the $PATH $ add_to_path post ~/foo -bash:add_to_path: '/home/jp/foo' does not exist--aborting $ add_to_path post '~/foo' -bash:add_to_path: can't add relative directory '~/foo' to the $PATH $ rm_from_path /home/jp/bin $ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/bin/X11:/usr/X11R6/bin $ add_to_path /home/jp/bin -bash:add_to_path: requires a location and a directory to add e.g. add_to_path pre /bin $ add_to_path post /home/jp/bin $ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/bin/X11:/usr/X11R6/bin:/home/jp/bin $ rm_from_path /home/jp/bin $ add_to_path pre /home/jp/bin $ echo $PATH /home/jp/bin:/bin:/usr/bin:/usr/local/bin:/usr/bin/X11:/usr/X11R6/bin
There are four interesting things about this problem and the functions presented in func_tweak_path in the Solution.
First, if you try to modify your path or other environment variables in a shell script, it won’t work because scripts run in subshells that go away when the script terminates, taking any modified environment variables with them. So instead, we source the functions into the current shell and run them from there.
Second, you may notice that add_to_path
post ~/foo
returns “does not exist” while add_to_path post'~/foo'
returns “can’t add
relative directory.” That’s because ~/foo is
expanded by the shell to /home/jp/foo before the
function ever sees it. Not accounting for shell expansion is a common
mistake. Use the echo command to see what the shell
will actually pass to your scripts and functions.
Next, you may note the use of lines such as echo"$0:$FUNCNAME:requires a directory toadd">&2.
$0:$FUNCNAME
is a handy way to identify exactly where an error message is coming from.
$0
is always the name of the current
program (-bash
in the solution’s
example, and the name of your script or program in other cases). Adding
the function name makes it easier to track down problems when debugging.
Echoing to >&2
sends the
output to STDERR, where runtime user feedback, especially including
warnings or errors, should go.
Finally, you can argue that the functions have inconsistent
interfaces, since add_to_path
and
remove_from_path
actually set
$PATH
, while clean_path
displays the cleaned up path and
contains
returns true or false. We
might not do it that way in production either, but it makes this example
more interesting and shows different ways to do things. And we might
argue that the interfaces make sense given what the functions do.
18.119.130.24