programing

Bash 배열에서 요소 제거

nasanasas 2020. 9. 21. 07:49
반응형

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

다음은 mapfile을 사용한 한 줄 솔루션입니다.

$ 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 명령을 수정 / 교환하여 큰 유연성을 허용하며 배열에 빈 문자열을 남기지 않습니다.


다음은 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*위해 for 전환하십시오 . 사용 ${elmt,,} == ${word,,}대소 문자를 구분하지 일치하는; 등, bash가 [[지원하는 모든 것.

입력 배열의 인덱스를 결정하고 역순으로 반복하여 작동합니다 (따라서 요소를 삭제해도 반복 순서가 망가지지 않습니다). 인덱스를 얻으려면 bash 변수 indirection을 통해 수행 할 수있는 이름으로 입력 배열에 액세스해야합니다 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 간접 지정을 통해 원래 배열을 이름으로 복사하고 복사본에서 색인을 얻습니다. 인덱스를 반대로 반복하기 위해 C 스타일 for 루프를 사용합니다. 또한를 통해 인덱스에 액세스하고을 사용하여 ${!arycopy[@]}반전하여 수행 할 수 있습니다 . 이는 입력 라인 순서를 뒤집는 taca cat입니다.

변수 간접이없는 함수 솔루션은 아마도를 포함해야 할 것입니다 eval. 이는 해당 상황에서 사용하는 것이 안전 할 수도 있고 안전하지 않을 수도 있습니다 (알 수 없습니다).


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

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

그러면 다음을 포함하는 배열이 생성됩니다. (two onetwo three threefour "one six")


사용 unset

특정 인덱스에서 요소를 제거하기 위해 사용 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>also를 사용하여 일부 요소 세트를 제거 할 수 있습니다. 예를 들어 첫 번째 요소를 제거하려면 :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

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

따라서 bash, korn shells 또는 zsh.

따라서 현재 귀하의 질문에 답할 수 없습니다.

아마도 이것이 당신을 위해 일할 것입니다.

unset array[$delete]

실제로 쉘 구문에는 항목이 제거되어야 할 때 배열을 쉽게 재구성 할 수있는 동작이 내장되어 있음을 알았습니다.

# 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+=()구문을 사용하여 배열을 어떻게 구성했는지 주목 하세요?

실제로 한 번에 전체 다른 배열의 내용을 포함하여 둘 이상의 항목을 추가 할 수 있습니다.


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

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

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

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

$ {PARAMETER %% PATTERN} # 욕심 많은 경기 끝에서 제거

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

어레이를 정리하는 몇 가지 다른 방법은 아래 예를 참조하십시오.

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? 

도움이 되었기를 바랍니다.


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

이 구문도 있습니다. 예를 들어 두 번째 요소를 삭제하려는 경우 :

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

이것은 사실 2 개의 탭을 연결 한 것입니다. 첫 번째는 인덱스 0에서 인덱스 1 (배타적)까지, 두 번째는 인덱스 2에서 끝까지입니다.


부분 답변 만

배열의 첫 번째 항목을 삭제하려면

unset 'array[0]'

배열의 마지막 항목을 삭제하려면

unset 'array[-1]'

To avoid conflicts with array index using unset - see https://stackoverflow.com/a/49626928/3223785 and https://stackoverflow.com/a/47798640/3223785 for more information - reassign the array to itself: 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

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


What I do is:

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

BAM, that item is removed.


This is a quick-and-dirty solution that will work in simple cases but will break if (a) there are regex special characters in $delete, or (b) there are any spaces at all in any items. Starting with:

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

Delete all entries exactly matching $delete:

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

resulting in echo $array -> pippo, and making sure it's an array: echo $array[1] -> pippo

fmt is a little obscure: fmt -1 wraps at the first column (to put each item on its own line. That's where the problem arises with items in spaces.) fmt -999999 unwraps it back to one line, putting back the spaces between items. There are other ways to do that, such as xargs.

Addendum: If you want to delete just the first match, use sed, as described here:

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

How about something like:

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

반응형