Created
June 3, 2024 21:18
-
-
Save ardnew/f64649bb626cc433f89b678760abded3 to your computer and use it in GitHub Desktop.
prepend or remove elements in a delimited string list with zsh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/zsh | |
# ------------------------------------------------------------------------------ | |
# | |
# prepath: prepend or remove elements in a delimited string list | |
# | |
# examples: | |
# | |
# #| this example demonstrates: | |
# #| - the default list delimiter is ":" | |
# #| - the variable to modify is given by name, not value | |
# #| - elements are prepended in the order given | |
# #| - existing elements are removed before prepending | |
# #| | |
# #| PATH="/usr/bin:/bin" | |
# #| is changed to: | |
# #| PATH="/foo:/usr/bin:/usr/local/bin:/bin" | |
# #| | |
# > prepath PATH /usr/local/bin /usr/bin /foo | |
# | |
# #| this example demonstrates: | |
# #| - override the list delimiter using PREPATH_DELIM | |
# #| - extraneous delimiters are removed | |
# #| - arguments do not match substrings | |
# #| | |
# #| LIST="bar//baz/food/" | |
# #| is changed to: | |
# #| LIST=foo/bar/baz/food" | |
# #| | |
# > PREPATH_DELIM=/ prepath LIST foo | |
# | |
# #| this example demonstrates: | |
# #| - override the list delimiter using flag "-s" | |
# #| - remove elements using flag "-d" | |
# #| - multiple elements can be separate args or delimited | |
# #| - remove and prepend in the same call | |
# #| - order of delimited args is maintained as expressed | |
# #| | |
# #| LIST="foo/zzz/bar/fa/do" | |
# #| is changed to: | |
# #| LIST=do/re/mi/fa" | |
# #| | |
# > prepath -s / -d "bar/foo" -d zzz LIST re/mi do | |
# | |
# ------------------------------------------------------------------------------ | |
prepath() { | |
local delim=${PREPATH_DELIM:-':'} | |
local -a drop | |
while getopts ":d:s:" opt; do | |
case "${opt}" in | |
d) drop+=( "${OPTARG}" ) ;; | |
s) delim=${OPTARG} ;; | |
:) echo "error: option requires argument: ${OPTARG}"; return 2 ;; | |
?) echo "error: invalid option: ${OPTARG}"; return 1 ;; | |
esac | |
done | |
shift $(( OPTIND - 1 )) | |
delim=${delim:0:1} # ensure delimiter is a single char | |
if [[ ${#} -lt 1 ]]; then | |
echo "invalid arguments"; return 255 | |
fi | |
# separate args to add from args to drop | |
[[ ${#drop[@]} -eq 0 ]] || drop=( -- "${drop[@]}" ) | |
# expand arguments containing delimiter(s) into multiple arguments, but | |
# reverse the order so that the output is ordered as expressed. | |
local -a expand reverse | |
for each in "${@:2}" "${drop[@]}"; do | |
reverse=() | |
while read -s -- elem; do | |
reverse+=( "${elem}" ) | |
done < <( tr -s "${delim}" '\n' <<< "${each}" ) | |
for (( i = ${#reverse[@]}; i > 0; --i )); do | |
expand+=( "${reverse[${i}]}" ) | |
done | |
done | |
set -- "${1}" "${expand[@]}" | |
# first argument is a variable reference (n.b., NOT value): | |
# | prepath PATH foo # <- prepends "foo" to $PATH | |
# | prepath $PATH foo # <- ERROR! | |
local var=${1} | |
unset -v drop | |
# check for and prepend all given paths. processes paths in-order. | |
# thus, the last (right-most) argument given will take the highest- | |
# precedence in the given var: | |
# | prepath PATH foo bar # <- PATH will be "bar:foo:$PATH" | |
while [[ ${#} -gt 1 ]]; do | |
shift | |
[[ ${1} != -- ]] || drop=true | |
# surround value with colons so that we can match entire | |
# paths (and not subpaths) even at the head/tail of list. | |
local val="${delim}${(P)var}${delim}" | |
# replace all pre-existing occurrences with a delimiter. | |
# this causes our given argument to be moved to the front | |
# of the list if it is already present. | |
val=${val//${delim}${1}${delim}/${delim}} | |
# strip any leading, trailing, and runs of delimiters | |
val=$( tr -s "${delim}" <<< "${val}" ) | |
val=${val%${delim}} | |
val=${val#${delim}} | |
if [[ x${drop} == x ]]; then | |
export ${var}="${1}${val:+${delim}${val}}" | |
else | |
export ${var}=${val} | |
fi | |
done | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment