it-source

Bash 배열에서 요소 제거

criticalcode 2023. 4. 26. 23:26
반응형

Bash 배열에서 요소 제거

bash 셸의 배열에서 요소를 제거해야 합니다.일반적으로 다음과 같은 작업을 수행합니다.

array=("${(@)array:#<element to remove>}")

유감스럽게도 제거하려는 요소가 변수이므로 이전 명령을 사용할 수 없습니다.다음은 예입니다.

array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}

감 잡히는 게 없어요?

이 원하는 대로 작동합니다.bash그리고.zsh:

$ array=(pluto pippo)
$ delete=pluto
$ echo ${array[@]/$delete}
pippo
$ array=( "${array[@]/$delete}" ) #Quotes when working with strings

둘 이상의 요소를 삭제해야 하는 경우:

...
$ delete=(pluto pippo)
for del in ${delete[@]}
do
   array=("${array[@]/$del}") #Quotes when working with strings
done

주의사항

이기은실일제접제두거다니합를사치는과 일치하는 를 제거합니다.$delete요소로부터, 반드시 전체 요소는 아닙니다.

갱신하다

정확한 항목을 제거하려면 배열을 살펴보고 대상을 각 요소와 비교한 다음unset정확한 일치 항목을 삭제합니다.

array=(pluto pippo bob)
delete=(pippo)
for target in "${delete[@]}"; do
  for i in "${!array[@]}"; do
    if [[ ${array[i]} = $target ]]; then
      unset 'array[i]'
    fi
  done
done

이 작업을 수행하고 하나 이상의 요소가 제거되면 인덱스는 더 이상 연속적인 정수 시퀀스가 아닙니다.

$ declare -p array
declare -a array=([0]="pluto" [2]="bob")

단순한 사실은 어레이가 가변 데이터 구조로 사용되도록 설계되지 않았다는 것입니다.기본적으로 구분 기호로 문자를 낭비할 필요 없이 단일 변수에 항목 목록을 저장하는 데 사용됩니다(예: 공백을 포함할 수 있는 문자열 목록 저장).

공백이 문제인 경우 공백을 메우기 위해 어레이를 다시 구성해야 합니다.

for i in "${!array[@]}"; do
    new_array+=( "${array[i]}" )
done
array=("${new_array[@]}")
unset new_array

원하지 않는 요소 없이 새 배열을 작성한 다음 이전 배열에 다시 할당할 수 있습니다.이 기능은 다음에서 작동합니다.bash:

array=(pluto pippo)
new_array=()
for value in "${array[@]}"
do
    [[ $value != pluto ]] && new_array+=($value)
done
array=("${new_array[@]}")
unset new_array

이는 다음과 같습니다.

echo "${array[@]}"
pippo

이 방법은 위치를 알고 있는 경우 값을 설정 해제하는 가장 직접적인 방법입니다.

$ array=(one two three)
$ echo ${#array[@]}
3
$ unset 'array[1]'
$ echo ${array[@]}
one three
$ echo ${#array[@]}
2

이 답변은 성능이 중요한 대규모 어레이에서 여러 값을 삭제하는 경우에만 적용됩니다.

가장 많이 사용되는 솔루션은 (1) 배열에 대한 패턴 대체 또는 (2) 배열 요소에 대한 반복입니다.첫 번째는 빠르지만 접두사가 다른 요소만 처리할 수 있고, 두 번째는 O(n*k), n=array size, k=제거할 요소를 가집니다.연관 배열은 상대적으로 새로운 기능이며, 질문이 처음 게시되었을 때 일반적이지 않았을 수 있습니다.

n과 k가 큰 정확한 일치 사례의 경우 O(nk)에서 O(n+klog(k))로 성능을 향상시킬 수 있습니다.실제로는 O(n)가 n보다 k를 훨씬 더 낮게 가정합니다.대부분의 속도 향상은 연관 배열을 사용하여 제거할 항목을 식별하는 것을 기준으로 합니다.

성능(n-배열 크기, 삭제할 k-값).사용자 시간의 성능 측정(초)

   N     K     New(seconds) Current(seconds)  Speedup
 1000   10     0.005        0.033             6X
10000   10     0.070        0.348             5X
10000   20     0.070        0.656             9X
10000    1     0.043        0.050             -7%

시역.current은 N이며, 솔션은 N*K에선며,fast솔루션은 훨씬 낮은 상수로 K에 실질적으로 선형입니다.fast솔루션은 솔루션에 비해 약간 느립니다.currentk=1일 때 솔루션, 추가 설정으로 인해.

빠른' 솔루션: array=입력 목록, delete=제거할 값 목록.

        declare -A delk
        for del in "${delete[@]}" ; do delk[$del]=1 ; done
                # Tag items to remove, based on
        for k in "${!array[@]}" ; do
                [ "${delk[${array[$k]}]-}" ] && unset 'array[k]'
        done
                # Compaction
        array=("${array[@]}")

벤마크에 대한 current해결책, 가장 중요한 답변으로부터.

    for target in "${delete[@]}"; do
        for i in "${!array[@]}"; do
            if [[ ${array[i]} = $target ]]; then
                unset 'array[i]'
            fi
        done
    done
    array=("${array[@]}")

맵 파일이 포함된 한 줄 솔루션은 다음과 같습니다.

$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[@]}" | grep -Pzv "<regexp>")

예:

$ arr=("Adam" "Bob" "Claire"$'\n'"Smith" "David" "Eve" "Fred")

$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"

Size: 6 Contents: Adam Bob Claire
Smith David Eve Fred

$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[@]}" | grep -Pzv "^Claire\nSmith$")

$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"

Size: 5 Contents: Adam Bob David Eve Fred

이 방법을 사용하면 grep 명령을 수정/교환하여 유연성을 높일 수 있으며 배열에 빈 문자열을 남기지 않습니다.

부분적인 답변만

배열의 첫 번째 항목을 삭제하려면 다음과 같이 하십시오.

unset 'array[0]'

배열의 마지막 항목을 삭제하려면 다음과 같이 하십시오.

unset 'array[-1]'

위의 답변을 확장하려면 다음을 사용하여 부분 일치 없이 배열에서 여러 요소를 제거할 수 있습니다.

ARRAY=(one two onetwo three four threefour "one six")
TO_REMOVE=(one four)

TEMP_ARRAY=()
for pkg in "${ARRAY[@]}"; do
    for remove in "${TO_REMOVE[@]}"; do
        KEEP=true
        if [[ ${pkg} == ${remove} ]]; then
            KEEP=false
            break
        fi
    done
    if ${KEEP}; then
        TEMP_ARRAY+=(${pkg})
    fi
done
ARRAY=("${TEMP_ARRAY[@]}")
unset TEMP_ARRAY

이것은 다음을 포함하는 배열을 생성합니다: (2 1 2 3 4 "1 6")

과 bash (아마도 bash에 bash와 입니다.unset텍스트 대체나 빈 요소 폐기가 필요 없는 일반적인 솔루션으로, 인용/오류 공간 등에 문제가 없습니다.

delete_ary_elmt() {
  local word=$1      # the element to search for & delete
  local aryref="$2[@]" # a necessary step since '${!$2[@]}' is a syntax error
  local arycopy=("${!aryref}") # create a copy of the input array
  local status=1
  for (( i = ${#arycopy[@]} - 1; i >= 0; i-- )); do # iterate over indices backwards
    elmt=${arycopy[$i]}
    [[ $elmt == $word ]] && unset "$2[$i]" && status=0 # unset matching elmts in orig. ary
  done
  return $status # return 0 if something was deleted; 1 if not
}

array=(a 0 0 b 0 0 0 c 0 d e 0 0 0)
delete_ary_elmt 0 array
for e in "${array[@]}"; do
  echo "$e"
done

# prints "a" "b" "c" "d" in lines

럼사용처럼 합니다.delete_ary_elmt ELEMENT ARRAYNAME 것도 없이$ . 스위치를 켭니다.== $word위해서== $word*일치의 ; 접사일치경우의; 사용두를 합니다.${elmt,,} == ${word,,}/소문자를 하지 않는 일치 항목 bash "/" "/" "" "" "" "" "" " " ""[[지원합니다.

이는 입력 배열의 인덱스를 결정하고 요소를 삭제해도 반복 순서가 흐트러지지 않도록 역방향으로 반복하는 방식으로 작동합니다. 하며, variable direction을 수 . bash는 bash의 인덱스입니다.x=1; varname=x; echo ${!varname} # prints "1".

다음과 같은 이름으로 어레이에 액세스할 수 없습니다.aryname=a; echo "${$aryname[@]}이것은 당신에게 오류를 줍니다.은 할 수 요.aryname=a; echo "${!aryname[@]}" 수 있습니다.aryname(어레이는 아니지만).가 있는 은 작하는것입니다.aryref="a[@]"; echo "${!aryref}"가 인쇄됩니다.a하는 것이 좋습니다.echo "${a[@]}"됩니다.aryref="!a[@]"또는aryref="#a[@]"또는"${!!aryref}"또는"${#!aryref}"모두 실패합니다.

그래서 저는 bash indirection을 통해 원래 배열을 이름으로 복사하고 복사본에서 인덱스를 가져옵니다.역방향 인덱스를 반복하기 위해 루프에 C 스타일을 사용합니다.또한 다음을 통해 인덱스에 액세스할 수 있습니다.${!arycopy[@]}로 뒤집어서 리고그역전것는키시을들그▁and▁them것▁reversing.tac은 즉, 즉입니다.cat입력 라인 순서를 돌립니다.

방향성에 대한 변수가 없는 함수 솔루션은 아마도 다음과 같이 포함되어야 할 것입니다.eval그 상황에서 사용하는 것이 안전할 수도 있고 안전하지 않을 수도 있습니다(알 수 없습니다.

용사를 합니다.unset

하려면 특인덱에요제소면다사용다니합음을려거하정서를스다▁at▁to니를 하면 됩니다.unset그런 다음 다른 배열로 복사합니다. ㅠㅠunset이 경우에는 필요하지 않습니다. ㅠㅠunset배열의 특정 인덱스에 null 문자열만 설정하는 요소는 제거하지 않습니다.

declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
unset 'arr[1]'
declare -a arr2=()
i=0
for element in "${arr[@]}"
do
    arr2[$i]=$element
    ((++i))
done
echo "${arr[@]}"
echo "1st val is ${arr[1]}, 2nd val is ${arr[2]}"
echo "${arr2[@]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"

출력은

aa cc dd ee
1st val is , 2nd val is cc
aa cc dd ee
1st val is cc, 2nd val is dd

용사를 합니다.:<idx>

다음을 사용하여 요소 집합을 제거할 수 있습니다.:<idx> 첫 요소를 하려면 한또를 . 예를 들어 첫 번째 요소를 제거하려는 경우 사용할 수 있습니다.:1하기와 같이

declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
arr2=("${arr[@]:1}")
echo "${arr2[@]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"

출력은

bb cc dd ee
1st val is cc, 2nd val is dd

http://wiki.bash-hackers.org/syntax/pe#substring_removal

${PARAMETER#PATTERN}# 처음부터 제거

${PARAMETER##PATTERN}# 처음부터 제거, 탐욕스러운 일치

${PARAMETER%PATTERN}# 끝에서 제거

${PARAMETER%%PATTERNAL} 끝에서 제거, 탐욕스러운 일치

전체 제거 요소를 수행하려면 if 문을 사용하여 설정 해제 명령을 수행해야 합니다.다른 변수에서 접두사를 제거하거나 배열에서 공백을 지원하는 데 관심이 없으면 따옴표를 삭제하고 루프를 잊어버릴 수 있습니다.

배열을 정리하는 몇 가지 다른 방법은 아래 예제를 참조하십시오.

options=("foo" "bar" "foo" "foobar" "foo bar" "bars" "bar")

# remove bar from the start of each element
options=("${options[@]/#"bar"}")
# options=("foo" "" "foo" "foobar" "foo bar" "s" "")

# remove the complete string "foo" in a for loop
count=${#options[@]}
for ((i = 0; i < count; i++)); do
   if [ "${options[i]}" = "foo" ] ; then
      unset 'options[i]'
   fi
done
# options=(  ""   "foobar" "foo bar" "s" "")

# remove empty options
# note the count variable can't be recalculated easily on a sparse array
for ((i = 0; i < count; i++)); do
   # echo "Element $i: '${options[i]}'"
   if [ -z "${options[i]}" ] ; then
      unset 'options[i]'
   fi
done
# options=("foobar" "foo bar" "s")

# list them with select
echo "Choose an option:"
PS3='Option? '
select i in "${options[@]}" Quit
 do
    case $i in 
       Quit) break ;;
       *) echo "You selected \"$i\"" ;;
    esac
 done

산출량

Choose an option:
1) foobar
2) foo bar
3) s
4) Quit
Option? 

도움이 되길 바랍니다.

두 번째 요소를 삭제하려는 경우와 같은 구문도 있습니다.

array=("${array[@]:0:1}" "${array[@]:2}")

두 개의 탭이 연결되어 있습니다.첫 번째는 인덱스 0에서 인덱스 1(전용)까지, 두 번째는 인덱스 2에서 끝까지입니다.

POSIX 셸 스크립트에 배열이 없습니다.

그래서 아마도 당신은 다음과 같은 특정한 방언을 사용하고 있을 것입니다.bash 또는 콘개또는조▁or.zsh.

따라서 현재 당신의 질문에 답변할 수 없습니다.

이 방법이 도움이 될 수도 있습니다.

unset array[$delete]

제가 하는 일은:

array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"

BAM, 해당 항목이 제거되었습니다.

이는 간단한 경우에는 작동하지만 (a) 정규식 특수 문자가 있는 경우에는 작동하지 않는 빠르고 더러운 솔루션입니다.$delete또는 (b) 모든 항목에 공백이 있습니다.시작:

array+=(pluto)
array+=(pippo)
delete=(pluto)

하게 일치하는 모든 $delete:

array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)

으로 결적으로과.echo $array하기: -> 피포, 배열지확니다인합인다▁-확.echo $array[1]-> 삐뽀

fmt애매해요: 약간모니다합호다.fmt -1첫 번째 열에서 랩합니다(각 항목을 고유한 줄에 배치).여기서 공간에 있는 항목에 문제가 발생합니다.) fmt -999999한 줄로 다시 포장을 풀고 항목 사이의 공백을 뒤로 놓습니다.이를 위한 다른 방법은 다음과 같습니다.xargs.

부록:첫 번째 일치 항목만 삭제하려면 다음 설명에 따라 sed를 사용합니다.

array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)

사실 방금 전에 셸 구문에는 질문에서 제안한 것처럼 항목을 제거해야 할 때 배열을 쉽게 재구성할 수 있는 동작이 내장되어 있다는 것을 알게 되었습니다.

# let's set up an array of items to consume:
x=()
for (( i=0; i<10; i++ )); do
    x+=("$i")
done

# here, we consume that array:
while (( ${#x[@]} )); do
    i=$(( $RANDOM % ${#x[@]} ))
    echo "${x[i]} / ${x[@]}"
    x=("${x[@]:0:i}" "${x[@]:i+1}")
done

bash를 하십시오.x+=()구문?

한 번에 두 개 이상의 항목을 추가할 수 있습니다. 즉, 전체 배열의 내용을 추가할 수 있습니다.

ZSH에서 이것은 매우 쉽습니다(이것은 이해하기 쉽도록 가능한 한 필요 이상의 bash 호환 구문을 사용합니다).

# I always include an edge case to make sure each element
# is not being word split.
start=(one two three 'four 4' five)
work=(${(@)start})

idx=2
val=${work[idx]}

# How to remove a single element easily.
# Also works for associative arrays (at least in zsh)
work[$idx]=()

echo "Array size went down by one: "
[[ $#work -eq $(($#start - 1)) ]] && echo "OK"

echo "Array item "$val" is now gone: "
[[ -z ${work[(r)$val]} ]] && echo OK

echo "Array contents are as expected: "
wanted=("${start[@]:0:1}" "${start[@]:2}")
[[ "${(j.:.)wanted[@]}" == "${(j.:.)work[@]}" ]] && echo "OK"

echo "-- array contents: start --"
print -l -r -- "-- $#start elements" ${(@)start}
echo "-- array contents: work --"
print -l -r -- "-- $#work elements" "${work[@]}"

결과:

Array size went down by one:
OK
Array item two is now gone:
OK
Array contents are as expected:
OK
-- array contents: start --
-- 5 elements
one
two
three
four 4
five
-- array contents: work --
-- 4 elements
one
three
four 4
five

어레이 인덱스와의 충돌을 방지하려면 다음을 사용합니다.unset자세한 내용은 https://stackoverflow.com/a/49626928/3223785 및 https://stackoverflow.com/a/47798640/3223785 을 참조하십시오. 어레이를 자체적으로 재할당합니다.ARRAY_VAR=(${ARRAY_VAR[@]}).

#!/bin/bash

ARRAY_VAR=(0 1 2 3 4 5 6 7 8 9)
unset ARRAY_VAR[5]
unset ARRAY_VAR[4]
ARRAY_VAR=(${ARRAY_VAR[@]})
echo ${ARRAY_VAR[@]}
A_LENGTH=${#ARRAY_VAR[*]}
for (( i=0; i<=$(( $A_LENGTH -1 )); i++ )) ; do
    echo ""
    echo "INDEX - $i"
    echo "VALUE - ${ARRAY_VAR[$i]}"
done

exit 0

[참조: https://tecadmin.net/working-with-array-bash-script/ ]

다음과 같은 것은 어떻습니까?

array=(one two three)
array_t=" ${array[@]} "
delete=one
array=(${array_t// $delete / })
unset array_t
#/bin/bash

echo "# define array with six elements"
arr=(zero one two three 'four 4' five)

echo "# unset by index: 0"
unset -v 'arr[0]'
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

arr_delete_by_content() { # value to delete
        for i in ${!arr[*]}; do
                [ "${arr[$i]}" = "$1" ] && unset -v 'arr[$i]'
        done
        }

echo "# unset in global variable where value: three"
arr_delete_by_content three
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

echo "# rearrange indices"
arr=( "${arr[@]}" )
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

delete_value() { # value arrayelements..., returns array decl.
        local e val=$1; new=(); shift
        for e in "${@}"; do [ "$val" != "$e" ] && new+=("$e"); done
        declare -p new|sed 's,^[^=]*=,,'
        }

echo "# new array without value: two"
declare -a arr="$(delete_value two "${arr[@]}")"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

delete_values() { # arraydecl values..., returns array decl. (keeps indices)
        declare -a arr="$1"; local i v; shift
        for v in "${@}"; do 
                for i in ${!arr[*]}; do
                        [ "$v" = "${arr[$i]}" ] && unset -v 'arr[$i]'
                done
        done
        declare -p arr|sed 's,^[^=]*=,,'
        }
echo "# new array without values: one five (keep indices)"
declare -a arr="$(delete_values "$(declare -p arr|sed 's,^[^=]*=,,')" one five)"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done

# new array without multiple values and rearranged indices is left to the reader

언급URL : https://stackoverflow.com/questions/16860877/remove-an-element-from-a-bash-array

반응형