I'm trying to make my code more clean. My function seems rather oblique to do a simple find/replace. Is there a cleaner way to do this find/replace array function with awk or something similar?
# Finds first item in array that matches find item, and replaces it with 1 or more array elements.
# Preserves sort order of original array.
find_replace_in_list(){
# $1 = list
# $2 = find item string
# $3 = replace array.
local -n _list=$1
local -n _replace_list=$3
for i in "${!_list[@]}"; do # Iterate over indices.
if [ ${_list[$i]} == "$2" ]; then
# Insert the replace array starting at indice.
local p=$((i 1)) # Get indice just after match.
local array_pre=${_list[@]:0:i}
local array_post=${_list[@]:p}
local new_array=("${array_pre[@]}" "${_replace_list[@]}" "${array_post[@]}")
echo "${new_array[@]}"
return # Break out of loop. Only replace first match.
fi
done
# Nothing found. Return orginal array.
echo "${_list[@]}"
}
: ' Example use ^^^
list=('a' 'b' 'c' 'd')
find='b'
replace_list=('b1' 'b2' 'b3')
test=$(find_replace_in_list list "$find" replace_list)
echo "RESULT:${test[*]}"
RESULT: a b1 b2 b3 c d
'
CodePudding user response:
You can return the array as stdout and readarray the result but it forks a sub-shell:
#!/usr/bin/env bash
# Finds first item in array that matches find item, and replaces it with 1 or more array elements.
# Preserves sort order of original array.
find_replace_in_list() {
# $1 = list
# $2 = find item string
# $3 = replace array.
local -n _list=$1
local -n _replace_list=$3
for i in "${!_list[@]}"; do # Iterate over indices.
if [ "${_list[$i]}" == "$2" ]; then
# Insert the replace array starting at indice.
local p=$((i 1)) # Get indice just after match.
local new_array=("${_list[@]:0:i}" "${_replace_list[@]}" "${_list[@]:p}")
printf '%s\0' "${new_array[@]}"
return # Break out of loop. Only replace first match.
fi
done
# Nothing found. Return orginal array.
echo "${_list[@]}"
}
# shellcheck disable=SC2034 # nameref use
list=('a' 'b' 'c' 'd')
find='b'
# shellcheck disable=SC2034 # nameref use
replace_list=('b1' 'b2' 'b3')
readarray -td '' test < <(find_replace_in_list list "$find" replace_list)
printf 'RESULT: %s\n' "${test[*]}"
Or you can return the new array as reference:
#!/usr/bin/env bash
# Finds first item in array that matches find item, and replaces it with 1 or more array elements.
# Preserves sort order of original array.
find_replace_in_list() {
# $1 = list nameref
# $2 = find item string
# $3 = replace array.nameref
# ?$4 = optional new array nameref
local -n _list=$1
local -n _replace_list=$3
if [ $# -eq 4 ]; then
local -n _new_list=$4
else
local -a _new_list=()
fi
for i in "${!_list[@]}"; do # Iterate over indices.
if [ "${_list[$i]}" == "$2" ]; then
# Insert the replace array starting at indice.
local p=$((i 1)) # Get indice just after match.
_new_list=("${_list[@]:0:i}" "${_replace_list[@]}" "${_list[@]:p}")
# If no new array reference, print null delimited
[ $# -lt 4 ] && printf '%s\0' "${_new_list[@]}"
return # Break out of loop. Only replace first match.
fi
done
# Nothing found. Return orginal array.
echo "${_list[@]}"
}
# shellcheck disable=SC2034 # nameref use
list=('a' 'b' 'c' 'd')
find='b'
# shellcheck disable=SC2034 # nameref use
replace_list=('b1' 'b2' 'b3')
find_replace_in_list list "$find" replace_list test
# shellcheck disable=SC2154 # nameref use
printf 'RESULT: %s\n' "${test[*]}"
CodePudding user response:
Return to another shared global variable:
find_replace_in_list() {
local -n _list=$1 _replace_list=$3
__A0=()
for i in "${!_list[@]}"; do
if [[ ${_list[i]} == "$2" ]]; then
__A0 =("${_replace_list[@]}" "${_list[@]:i 1}")
break
fi
__A0[i]=${_list[i]}
done
}
Modify original list:
find_replace_in_list() {
local -n _list=$1 _replace_list=$3
local counter=0
for i in "${!_list[@]}"; do
if [[ ${_list[i]} == "$2" ]]; then
_list=("${_list[@]:0:counter}" "${_replace_list[@]}" "${_list[@]:i 1}")
break
fi
(( counter ))
done
}
CodePudding user response:
You can use nameref's (declare -n) to pass data to/from the function.
Also, instead of trying to reindex a current array we'll just build a new array (newlist aka _newlist) on-the-fly thus allowing us to streamline the code.
Modifying the current code ...
find_replace_in_list(){
# $1 = list (array) : read from
# $2 = find item (string)
# $3 = replace list (array) : read from
# $4 = newlist (array) : write to
# $5 = number of times to match-n-replace (optional; default=1)
local -n _list=$1
local ptn=$2
local -n _replace_list=$3
local -n _newlist=$4
local match_count=${5:-1} # OP can add more logic to validate $5 is a positive integer
_newlist=()
for item in "${_list[@]}"
do
if [[ "${item}" = "${ptn}" && "${match_count}" -gt 0 ]]
then
_newlist =( "${_replace_list[@]}" )
(( match_count-- ))
else
_newlist =( "${item}" )
fi
done
}
Test #1 (one match & replacement):
list=('a' 'b' 'c' 'd')
find='b'
replace_list=('b1' 'b2' 'b3')
newlist=()
find_replace_in_list list "$find" replace_list newlist
typeset -p newlist
This generates:
declare -a newlist=([0]="a" [1]="b1" [2]="b2" [3]="b3" [4]="c" [5]="d")
Test #2 (no matches found):
list=('a' 'b' 'c' 'd')
find='z'
replace_list=('z1' 'z2' 'z3')
newlist=()
find_replace_in_list list "$find" replace_list newlist
typeset -p newlist
This generates:
declare -a newlist=([0]="a" [1]="b" [2]="c" [3]="d")
Test #3a (one match & replacement):
list=('a' 'b' 'c' 'd' 'c')
find='c'
replace_list=('c7' 'c8' 'c9')
newlist=()
find_replace_in_list list "$find" replace_list newlist
typeset -p newlist
This generates:
declare -a newlist=([0]="a" [1]="b" [2]="c7" [3]="c8" [4]="c9" [5]="d" [6]="c")
Test #3b (allow up to 999 match & replacements):
list=('a' 'b' 'c' 'd' 'c')
find='c'
replace_list=('c7' 'c8' 'c9')
newlist=()
find_replace_in_list list "$find" replace_list newlist 999
typeset -p newlist
This generates:
declare -a newlist=([0]="a" [1]="b" [2]="c7" [3]="c8" [4]="c9" [5]="d" [6]="c7" [7]="c8" [8]="c9")
