[arrays] 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 명령을 수정 / 교환하여 큰 유연성을 허용하며 배열에 빈 문자열을 남기지 않습니다.
답변
이 대답은 성능이 중요한 대형 배열에서 여러 값을 삭제하는 경우에만 해당됩니다.
가장 많이 투표 한 솔루션은 (1) 배열의 패턴 대체 또는 (2) 배열 요소에 대한 반복입니다. 첫 번째는 빠르지 만 고유 한 접두사가있는 요소 만 처리 할 수 있고, 두 번째는 O (n * k), n = 배열 크기, k = 제거 할 요소가 있습니다. 연관 배열은 상대적으로 새로운 기능이며 질문이 처음 게시되었을 때 일반적이지 않았을 수 있습니다.
정확한 일치 케이스의 경우 큰 n과 k를 사용하면 O (n k)에서 O (n + k log (k))로 성능을 향상시킬 수 있습니다 . 실제로 O (n)는 k가 n보다 훨씬 낮다고 가정합니다. 대부분의 속도 향상은 제거 할 항목을 식별하기 위해 연관 배열을 사용하는 것입니다.
성능 (n-array 크기, 삭제할 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 * K에 선형이고, fast
해는 훨씬 더 낮은 상수를 사용하여 사실상 K에 선형입니다. fast
용액은 약간 느린 VS이다 current
용액 때 추가 설정 때문에 K = 1.
‘빠른’솔루션 : 배열 = 입력 목록, 삭제 = 제거 할 값 목록.
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[@]}")
답변
다음은 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[@]}
반전하여 수행 할 수 있습니다 . 이는 입력 라인 순서를 뒤집는 tac
a 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”)