Solution of the proposed solution from dmckee:
- While some versions of Bash may hyphenate function names, others (MacOS X) do not.
- I do not see the need to use return just before the function ends.
- I do not see the need for all half-columns.
- I do not understand why you are exporting the value of path-element-by-pattern. Think of
export as the equivalent of setting (or even creating) a global variable - something should be avoided when possible. - I'm not sure what you expect from "
replace-path PATH $PATH /usr ", but it does not do what I expect.
Consider the PATH value that begins with:
. /Users/jleffler/bin /usr/local/postgresql/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /usr/bin /bin /sw/bin /usr/sbin /sbin
The result I got (from ' replace-path PATH $PATH /usr '):
. /Users/jleffler/bin /local/postgresql/bin /local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /local/bin /bin /bin /sw/bin /sbin /sbin
I would expect to get my original path back, since / usr is not displayed as an element (full), only as part of a path element.
This can be fixed in replace-path by changing one of the sed commands:
export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" | tr "\n" ":" | sed "s|::|:|g")
I used ':' instead of '|' to separate replacement parts since '|' can (theoretically) appear in a path component, whereas by definition by PATH, the colon cannot. I noticed that the second sed can remove the current directory from the middle of PATH. That is, a valid (albeit perverse) PATH value can be:
PATH=/bin::/usr/local/bin
After processing, the current directory will no longer be in PATH.
A similar change in the matching binding is appropriate for path-element-by-pattern :
export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\$")
I note along the way that grep -m 1 not standard (this is the GNU extension, also available on MacOS X). Indeed, the -n option for echo also non-standard; you would be better off simply removing the trailing colon added by converting the newline from echo to colon. Since path-element-by-pattern is used only once, it has unwanted side effects (it compresses any pre-existing exported variable called $removestr ), it can reasonably be replaced by its body. This, along with a more liberal use of quotes to avoid space problems or an undesirable file name extension, leads to:
# path_tools.bash # # A set of tools for manipulating ":" separated lists like the # canonical $PATH variable. # # /bin/sh compatibility can probably be regained by replacing $( ) # style command expansion with ` ` style ############################################################################### # Usage: # # To remove a path: # replace_path PATH $PATH /exact/path/to/remove # replace_path_pattern PATH $PATH <grep pattern for target path> # # To replace a path: # replace_path PATH $PATH /exact/path/to/remove /replacement/path # replace_path_pattern PATH $PATH <target pattern> /replacement/path # ############################################################################### # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 a ":" delimited list to work from (eg $PATH) # $3 the precise string to be removed/replaced # $4 the replacement string (use "" for removal) function replace_path () { path=$1 list=$2 remove=$3 replace=$4 # Allowed to be empty or unset export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" | tr "\n" ":" | sed 's|:$||') } # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 a ":" delimited list to work from (eg $PATH) # $3 a grep pattern identifying the element to be removed/replaced # $4 the replacement string (use "" for removal) function replace_path_pattern () { path=$1 list=$2 removepat=$3 replacestr=$4 # Allowed to be empty or unset removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$") replace_path "$path" "$list" "$removestr" "$replacestr" }
I have a Perl script called echopath , which I find useful in debugging problems with PATH-like variables:
When I run the modified solution on the test code below:
echo xpath=$PATH replace_path xpath $xpath /usr echopath $xpath echo xpath=$PATH replace_path_pattern xpath $xpath /usr/bin /work/bin echopath xpath echo xpath=$PATH replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin echopath xpath
Output:
. /Users/jleffler/bin /usr/local/postgresql/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /usr/bin /bin /sw/bin /usr/sbin /sbin . /Users/jleffler/bin /usr/local/postgresql/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /work/bin /bin /sw/bin /usr/sbin /sbin . /Users/jleffler/bin /work/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /usr/bin /bin /sw/bin /usr/sbin /sbin
This looks right to me - at least for my definition of the problem.
Note that echopath LD_LIBRARY_PATH evaluates $LD_LIBRARY_PATH . It would be nice if your functions could do this, so the user could enter:
replace_path PATH /usr/bin /work/bin
This can be done using:
list=$(eval echo '$'$path)
This leads to code revision:
# path_tools.bash # # A set of tools for manipulating ":" separated lists like the # canonical $PATH variable. # # /bin/sh compatibility can probably be regained by replacing $( ) # style command expansion with ` ` style ############################################################################### # Usage: # # To remove a path: # replace_path PATH /exact/path/to/remove # replace_path_pattern PATH <grep pattern for target path> # # To replace a path: # replace_path PATH /exact/path/to/remove /replacement/path # replace_path_pattern PATH <target pattern> /replacement/path # ############################################################################### # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 the precise string to be removed/replaced # $3 the replacement string (use "" for removal) function replace_path () { path=$1 list=$(eval echo '$'$path) remove=$2 replace=$3 # Allowed to be empty or unset export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" | tr "\n" ":" | sed 's|:$||') } # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 a grep pattern identifying the element to be removed/replaced # $3 the replacement string (use "" for removal) function replace_path_pattern () { path=$1 list=$(eval echo '$'$path) removepat=$2 replacestr=$3 # Allowed to be empty or unset removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$") replace_path "$path" "$removestr" "$replacestr" }
The following revised test is also currently running:
echo xpath=$PATH replace_path xpath /usr echopath xpath echo xpath=$PATH replace_path_pattern xpath /usr/bin /work/bin echopath xpath echo xpath=$PATH replace_path_pattern xpath "/usr/.*/bin" /work/bin echopath xpath
It produces the same result as before.