Eliminar elemento de la matriz

Necesito eliminar un elemento de una matriz en bash shell. En general, simplemente lo haría:

array=("${(@)array:#}") 

Lamentablemente, el elemento que deseo eliminar es una variable, por lo que no puedo usar el comando anterior. Aquí abajo un ejemplo:

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

¿Alguna idea?

Los siguientes trabajos funcionan como te gustaría en bash y zsh :

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

Si necesita eliminar más de un elemento:

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

Advertencia

Esta técnica en realidad elimina los prefijos que coinciden $delete de los elementos, no necesariamente elementos completos.

Actualizar

Para eliminar realmente un elemento exacto, debe recorrer el conjunto, comparar el objective con cada elemento y usar unset para eliminar una coincidencia exacta.

 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 

Tenga en cuenta que si hace esto y se eliminan uno o más elementos, los índices ya no serán una secuencia continua de números enteros.

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

El hecho simple es que las matrices no fueron diseñadas para ser utilizadas como estructuras de datos mutables. Se utilizan principalmente para almacenar listas de elementos en una sola variable sin necesidad de perder un carácter como delimitador (por ejemplo, para almacenar una lista de cadenas que pueden contener espacios en blanco).

Si las lagunas son un problema, entonces necesita reconstruir la matriz para llenar los vacíos:

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

Podría construir una nueva matriz sin el elemento no deseado, luego asignarla nuevamente a la matriz anterior. Esto funciona en bash :

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

Esto produce:

 echo "${array[@]}" pippo 

Para ampliar las respuestas anteriores, se puede utilizar lo siguiente para eliminar múltiples elementos de una matriz, sin una coincidencia parcial:

 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 

Esto dará como resultado una matriz que contiene: (dos en dos tres tres cuatro “uno seis”)

Esta es la forma más directa de deshacer un valor si conoce su posición.

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

El script de shell POSIX no tiene matrices.

Entonces, lo más probable es que estés usando un dialecto específico como bash , korn shells o zsh .

Por lo tanto, su pregunta a partir de ahora no puede ser respondida.

Tal vez esto funcione para ti:

 unset array[$delete] 

Aquí hay una pequeña función (probablemente muy específica de bash) que implica la indirección y el unset variable bash; es una solución general que no implica la sustitución de texto o el descarte de elementos vacíos y no tiene problemas con las citas / espacios en blanco, etc.

 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 

Úselo como delete_ary_elmt ELEMENT ARRAYNAME sin ningún $ sigil. Cambie la == $word por == $word* para las coincidencias de prefijo; use ${elmt,,} == ${word,,} para las coincidencias insensibles a mayúsculas y minúsculas; etc., lo que sea bash [[ admite.

Funciona al determinar los índices de la matriz de entrada y al iterar sobre ellos hacia atrás (por lo que eliminar elementos no arruina el orden de iteración). Para obtener los índices necesita acceder a la matriz de entrada por su nombre, lo que se puede hacer a través de la variable indirecta de bash x=1; varname=x; echo ${!varname} # prints "1" x=1; varname=x; echo ${!varname} # prints "1" x=1; varname=x; echo ${!varname} # prints "1" .

No puede acceder a las matrices por nombre como aryname=a; echo "${$aryname[@]} aryname=a; echo "${$aryname[@]} , esto le da un error. No puede hacer aryname=a; echo "${!aryname[@]}" , esto le da los índices de la variable aryname (aunque no es una matriz). Lo que HACE el trabajo es aryref="a[@]"; echo "${!aryref}" , que imprimirá los elementos de la matriz a , conservando las palabras de shell-word y los espacios en blanco exactamente como echo "${a[@]}" . Pero esto solo funciona para imprimir los elementos de una matriz, no para imprimir su longitud o índices ( aryref="!a[@]" o aryref="#a[@]" o "${!!aryref}" o "${#!aryref}" , todos fallan).

Así que copio la matriz original por su nombre a través de indirección bash y obtengo los índices de la copia. Para iterar sobre los índices en reversa, uso un estilo C para el ciclo. También podría hacerlo accediendo a los índices a través de ${!arycopy[@]} y revirtiéndolos con tac , que es un cat que da la vuelta al orden de la línea de entrada.

Una solución de función sin direccionamiento variable probablemente tendría que involucrar a eval , que puede o no ser seguro de usar en esa situación (no puedo decirlo).

Aquí hay una solución de una línea con mapfile:

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

Ejemplo:

 $ arr=("Adam" "Bob" "Clair"$'\n'"Smith" "David" "Eve" "Fred") $ echo "Size: ${#arr[*]} Contents: ${arr[*]}" Size: 6 Contents: Adam Bob Clair 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 

Este método permite una gran flexibilidad al modificar / intercambiar el comando grep y no deja cadenas vacías en la matriz.

En realidad, me acabo de dar cuenta de que la syntax del intérprete de comandos tiene un comportamiento incorporado que permite una fácil reconstrucción de la matriz cuando, como se plantea en la pregunta, se debe eliminar un elemento.

 # 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 

¿Observe cómo construimos la matriz usando la syntax de x+=() bash?

En realidad, podría agregar más de un elemento con eso, el contenido de una matriz completa a la vez.

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

$ {PARAMETER # PATTERN} # eliminar del comienzo

$ {PARAMETER ## PATTERN} # eliminar desde el principio, partido codicioso

$ {PARAMETER% PATTERN} # eliminar del final

$ {PARAMETER %% PATTERN} # eliminar del final, partido codicioso

Para hacer un elemento de eliminación completa, debe hacer un comando no establecido con una instrucción if. Si no te importa eliminar prefijos de otras variables o apoyar espacios en blanco en la matriz, entonces puedes soltar las comillas y olvidarte de los bucles.

Consulte el ejemplo a continuación para conocer algunas formas diferentes de limpiar una matriz.

 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 

Salida

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

Espero que ayude.

Usando unset

Para eliminar un elemento en un índice particular, podemos usar unset y luego copiarlo a otra matriz. En este caso, no se requiere solo unset . Como unset no elimina el elemento, simplemente establece una cadena nula para el índice particular en el conjunto.

 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]}" 

La salida es

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

Usando :

Podemos eliminar algunos elementos usando : también. Por ejemplo, si queremos eliminar el primer elemento, podemos usar :1 como se menciona a continuación.

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

La salida es

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

En ZSH, esto es absolutamente fácil (tenga en cuenta que esto utiliza una syntax compatible con bash mayor de la necesaria siempre que sea posible para facilitar la comprensión):

 # 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[@]}" 

Resultados:

 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 

Solo respuesta parcial

Para eliminar el primer elemento de la matriz

 unset array[0] 

Para eliminar el último elemento de la matriz

 unset array[-1] 

Lo que hago es:

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

BAM, ese artículo es eliminado.

Esta es una solución rápida y sucia que funcionará en casos simples pero se romperá si (a) hay caracteres especiales de expresiones regulares en $delete , o (b) hay espacios en absoluto en los elementos. Empezando con:

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

Borre todas las entradas que coincidan exactamente con $delete :

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

dando como resultado echo $array -> pippo, y asegurándose de que sea una matriz: echo $array[1] -> pippo

fmt es un poco oscuro: fmt -1 envuelve en la primera columna (para colocar cada elemento en su propia línea. Ahí es donde surge el problema con los elementos en los espacios.) fmt -999999 desenvuelve en una línea, volviendo a poner los espacios entre artículos. Hay otras formas de hacerlo, como xargs .

Adición: Si desea eliminar solo la primera coincidencia, use sed, como se describe aquí :

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

¿Qué tal algo así como:

 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