从数组shell中移除元素
我需要从bash shell中的数组中删除一个元素。 一般我只是做:
array=("${(@)array:#<element to remove>}")
不幸的是我想删除的元素是一个variables,所以我不能使用以前的命令。 在这里举一个例子:
array+=(pluto) array+=(pippo) delete=(pluto) array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}
任何想法?
在bash
和zsh
,以下方法是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
了匹配$delete
前缀,而不一定是整个元素。
更新
要真正删除确切的项目,您需要遍历数组,将目标与每个元素进行比较,并使用unset
删除完全匹配。
array=(pluto pippo bob) delete=(pippo) for target in "${delete[@]}"; do for i in "${!array[@]}"; do if [[ ${array[i]} = "${delete[0]}" ]]; then unset 'array[i]' fi done done
请注意,如果您这样做,并且删除了一个或多个元素,则索引将不再是连续的整数序列。
$ declare -p array declare -a array=([0]="pluto" [2]="bob")
简单的事实是,数组并不是devise用作可变数据结构的。 它们主要用于将项目列表存储在单个variables中,而不需要浪费字符作为分隔符(例如,存储可包含空格的string列表)。
如果差距是一个问题,那么你需要重build数组来弥补差距:
for i in "${!array[@]}"; do new_array+=( "${array[i]} ) done array=("${new_array[@]}") unset new_array
您可以build立一个没有不需要的元素的新数组,然后将其分配回旧数组。 这在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
您可以将要销毁的元素设置为空string,将被视为已删除。
以下示例显示如何删除abcd
数组中的b
string:
$ array=(abcd) $ echo ${array[@]} abcd $ for i in ${!array[@]} ; do # iterate over array indexes if [ ${array[$i]} = 'b' ] ; then # if it's the value you want to delete array[$i]='' # set the string as empty at this specific index fi done $ echo ${array[@]} acd
注意,这个解决scheme即使在像sh这样的POSIX shell中也能工作。
POSIX shell脚本没有数组。
所以很可能你正在使用特定的方言,如bash
,korn shell或zsh
。
所以,你现在的问题是不能回答的。
也许这对你有用:
unset array[$delete]
为了扩展上面的答案,可以使用以下内容从数组中删除多个元素,而不进行部分匹配:
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
这将导致一个数组包含:(两个三三四四六)
在ZSH中这很容易:
# 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
实际上,我刚刚注意到,shell语法有一些内置的行为,如在问题中提出的,应该删除一个项目,这样可以方便地重build数组。
# 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+=()
语法构造数组?
你实际上可以添加多个项目,即一个整个其他数组的内容。
这里是一个(可能是非常bash特定的)小function涉及bashvariables间接和未unset
; 这是一个通用的解决scheme,不涉及文本replace或丢弃空的元素,并没有问题引用/空格等
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 de 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 [[
支持。
它通过确定input数组的索引并向后迭代来工作(所以删除元素并不会影响迭代次序)。 为了获得索引,你需要通过名称访问input数组,这可以通过bashvariables间接x=1; varname=x; echo ${!varname} # prints "1"
来完成x=1; varname=x; echo ${!varname} # prints "1"
x=1; varname=x; echo ${!varname} # prints "1"
x=1; varname=x; echo ${!varname} # prints "1"
。
你不能像aryname=a; echo "${$aryname[@]}
那样通过名字来访问数组aryname=a; echo "${$aryname[@]}
aryname=a; echo "${$aryname[@]}
,这会给你一个错误,你不能做aryname=a; echo "${!aryname[@]}"
,这会给你variablesaryname
的索引(尽pipe它不是一个数组),什么工作是aryref="a[@]"; echo "${!aryref}"
,这将打印数组的元素a
,保持贝壳字引用和空白完全一样echo "${a[@]}"
( aryref="!a[@]"
或者aryref="#a[@]"
或者"${!!aryref}"
echo "${a[@]}"
,但这只适用于打印数组的元素。 "${!!aryref}"
或者"${#!aryref}"
,他们都失败了)。
所以我通过bash间接复制原始数组的名称,并从副本获取索引。 要反向遍历索引,我使用C风格for循环。 我也可以通过${!arycopy[@]}
来访问索引,并使用tac
(这是一个围绕input行顺序的cat
来反转它们。
没有可变间接的函数解决scheme可能会涉及eval
,在这种情况下使用可能是安全的,也可能不安全(我不明白)。
http://wiki.bash-hackers.org/syntax/pe#substring_removal
$ {PARAMETER#PATTERN}#从开始删除
$ {PARAMETER ## PATTERN}#从头开始删除,贪婪的匹配
$ {PARAMETER%PATTERN}#从结尾删除
$ {PARAMETER %% PATTERN}#从结尾删除,贪婪的匹配
为了做一个完整的删除元素,你必须用一个if语句来做一个unset命令。 如果你不关心从其他variables中删除前缀或者支持数组中的空格,那么你可以放弃引号而忘记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?
希望有所帮助。
这是一个mapfile的单行解决scheme:
$ mapfile -t <array> < <(printf '%s\n' "${arr[@]}" | grep -xv "<element>")
例:
$ arr=("Adam" "Bob" "Claire" "David" "Eve" "Fred") $ echo "Size: ${#arr[*]} Contents: ${arr[*]}" Size: 6 Contents: Adam Bob Claire David Eve Fred $ mapfile -t arr < <(printf '%s\n' "${arr[@]}" | grep -xv "Claire") $ echo "Size: ${#arr[*]} Contents: ${arr[*]}" Size: 5 Contents: Adam Bob David Eve Fred
这种方法通过修改/交换grep命令来提供很大的灵活性,并且不会在数组中留下任何空string。
我所做的是:
array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"
BAM,该项目被删除。
这是一个简单而快速的解决scheme,可以在简单的情况下工作,但如果(a) $delete
有正则expression式特殊字符,或者(b)任何项目中有任何空格,将会中断。 从…开始:
array+=(pluto) array+=(pippo) delete=(pluto)
删除所有与$delete
完全匹配的条目:
array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)
导致echo $array
– > pippo,并确保它是一个数组: echo $array[1]
– > pippo
fmt
有点模糊不清: fmt -1
在第一列包装(把每个项目放在自己的行上,这就是问题出现在空间的项目中的地方)。fmt fmt -999999
解开它回到一行,项目。 还有其他方法可以做到,比如xargs
。
附录:如果要删除第一个匹配项,请使用sed,如下所述:
array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)
感觉如何?
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 iv; 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
只删除与您的项目相匹配的内容:
user@pc:~$ itm="two" #Item to be deleted user@pc:~$ n=(one two three twentytwo) #The array of items user@pc:~$ n=(`echo ${n[@]} | sed -r "s/\<${itm}\>//g"`) user@pc:~$ echo ${n[@]} one three twentytwo