Home > Enterprise >  Find Item in Array / Replace With Array Using Awk?
Find Item in Array / Replace With Array Using Awk?

Time:01-22

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")
  •  Tags:  
  • Related