patternbashMinor
Pathmunge function in zsh
Viewed 0 times
functionpathmungezsh
Problem
pathmunge is an often-seen function in shell initialization files, used to add entries to the PATH environment variable without duplication. Examples:- Fedora
/etc/profile'spathmunge.
- How can I cleanly add to $PATH? - Unix & Linux
- Unix shell function for adding directories to PATH
I wrote this
pathmunge variation in zsh:pathmunge() {
local path_array before
# The default is to prepend the argument to PATH
before=1
case $1 in
after)
# Append to PATH
before=
;&
before)
shift
;;
esac
# Split PATH on : into an array
path_array=(${(As.:.)PATH})
# Then remove the arguments from it, since they are to be added in the
# order specified
path_array=(${path_array:|argv})
if [[ -n $before ]]
then
# Reverse the order of the arguments, so that:
# for f in ...; pathmunge f
# and
# pathmunge ...
# have the same effect.
path_array=(${(Oa)argv} $path_array)
else
path_array=($path_array $argv)
fi
# Concatenate the array back to PATH
PATH="${(j.:.)path_array}"
}
Note on implementation: Usually
pathmunge implementations don't add to PATH if the entry already exists. I took a slightly different path - I'll remove the old instance of entry, and then add the entry as specified. The purpose is to ensure that entries are available in the expected order, since the order matters.For example, if an entry
/foo has commands shadowing another entry /bar, and I do pathmunge before /foo or pathmunge after /foo with PATH already containing /foo and /bar in some order, I can reasonably expect that now PATH will have the expected relative order between /foo and /bar.Compatibility with other shells is not a concern. I am fairly competent when it comes to shell scripts. However, I have never made much use of zsh's extensive parameter expansion features.
Solution
- Use parameter expansion for great effect
You can replace most of the
case construct with a two ${name:#pattern} expansions. If pattern matches the value of name, the empty string is substituted, otherwise the value of name. If name is an array, matching elements will be removed from the array. So instead of this:local before
before=1
# [...]
case $1 in
after)
# Append to PATH
before=
;&
before)
shift
;;
esac
#[...]
if [[ -n $before ]]
then
#[...]
fiit would look like this:
local before=${1:#after}
argv=(${argv:#[^/]*})
if [[ -n $before ]]
then
#[...]
fiWith
before=${1:#after} before will be empty if the first parameter is "after", non-zero otherwise. argv=(${argv:#[^/]*}) will remove any element from argv that does not start with an /, including "before" and "after"One difference would be, that
before contains the value of the first parameter instead of 1 unless said parameter was "after". But this does not matter as long as you check only if before is non-zero.The second difference would be, that in addition to removing
"before" and "after" from argv, any relative paths (or anything that does not start with an /) will be removed, too.- Make use of
pathinstead ofPATH
There is no need to split
PATH into an array on your own because zsh already makes the value of PATH available as an array with the array parameter path. Any change to PATH is automatically mirrored in path and vice versa. So you can manipulate path directly without the need for the local path_array parameter.Instead of
path_array=(${(As.:.)PATH})
path_array=(${path_array:|argv})
if [[ -n $before ]]
then
path_array=(${(Oa)argv} $path_array)
else
path_array=($path_array $argv)
fi
PATH="${(j.:.)path_array}"You need only
path=(${path:|argv})
if [[ -n $before ]]
then
path=(${(Oa)argv} $path)
else
path=($path $argv)
fiCode Snippets
local before
before=1
# [...]
case $1 in
after)
# Append to PATH
before=
;&
before)
shift
;;
esac
#[...]
if [[ -n $before ]]
then
#[...]
filocal before=${1:#after}
argv=(${argv:#[^/]*})
if [[ -n $before ]]
then
#[...]
fipath_array=(${(As.:.)PATH})
path_array=(${path_array:|argv})
if [[ -n $before ]]
then
path_array=(${(Oa)argv} $path_array)
else
path_array=($path_array $argv)
fi
PATH="${(j.:.)path_array}"path=(${path:|argv})
if [[ -n $before ]]
then
path=(${(Oa)argv} $path)
else
path=($path $argv)
fiContext
StackExchange Code Review Q#154038, answer score: 4
Revisions (0)
No revisions yet.