2010-08-26 27 views
12

Digamos que tengo una matriz bash (por ejemplo, la matriz de todos los parámetros) y quiero eliminar todos los parámetros que coinciden con un patrón determinado o copiar todos los elementos restantes a una nueva matriz . Alternativamente, al revés, mantenga elementos que coincidan con un patrón.bash: cómo eliminar elementos de una matriz según un patrón

Un ejemplo para la ilustración:

x=(preffoo bar foo prefbaz baz prefbar) 

y quiero eliminar todo lo que empieza con pref el fin de obtener

y=(bar foo baz) 

(el orden no es relevante)

¿Qué pasa si ¿Quieres lo mismo para una lista de palabras separadas por espacios en blanco?

x="preffoo bar foo prefbaz baz prefbar" 

y otra vez eliminar todo lo que empieza con pref el fin de obtener

y="bar foo baz" 

Respuesta

5

Para despojar a una cadena plana (Hu Lc ya ha dado la respuesta para las matrices), se puede activar la opción del shell extglob y ejecute el siguiente expansión

$ shopt -s extglob 
$ unset x 
$ x="preffoo bar foo prefbaz baz prefbar" 
$ echo ${x//pref*([^ ])?()} 
bar foo baz 

Se necesita la opción extglob para las formas *(pattern-list) y ?(pattern-list). Esto le permite usar expresiones regulares (aunque en una forma diferente a la mayoría de las expresiones regulares) en lugar de solo la expansión del nombre de ruta (*?[).

La respuesta que Hulk ha dado a las matrices solo funcionará en las matrices. Si parece funcionar en cadenas planas, es solo porque al probar la matriz no se desarmó primero.

p. Ej.

$ x=(preffoo bar foo prefbaz baz prefbar) 
$ echo ${x[@]//pref*/} 
bar foo baz 
$ x="preffoo bar foo prefbaz baz prefbar" 
$ echo ${x[@]//pref*/} 
bar foo baz 
$ unset x 
$ x="preffoo bar foo prefbaz baz prefbar" 
$ echo ${x[@]//pref*/} 

$ 
+1

+1 gracias por aclarar la confusión de la publicación de Hulk y señalar este otro camino. – kynan

4

usted puede hacer esto:

elimine todas las apariciones de la subcadena.

# Not specifing a replacement defaults to 'delete' ... 
echo ${x[@]//pref*/}  # one two three four ve ve 
#    ^^   # Applied to all elements of the array. 

Editar:

Para espacios en blanco que es algo misma

x="preffoo bar foo prefbaz baz prefbar" 
echo ${x[@]//pref*/} 

Salida:

Baz foo bar

+0

¿Algo similar para una cadena de palabras separadas por espacios en blanco? – kynan

+0

Parece que no funciona, que elimina todo después de la primera aparición de 'pref' – kynan

+0

mira mi solución.O no entendí tu pregunta – Hulk

8

Otra forma de tira de una cadena plana es para convertirlo en una matriz a continuación, utilizar el método de matriz:

x="preffoo bar foo prefbaz baz prefbar" 
x=($x) 
x=${x[@]//pref*} 

Contraste esto con inicio y final con una matriz:

x=(preffoo bar foo prefbaz baz prefbar) 
x=(${x[@]//pref*}) 
+0

Te di un +1 por mostrar ambos y hacerme pensar ... –

+0

Me gusta mucho esta cosa, ya que realmente reduce la cantidad previa de código que uso para este tipo de acción. –

+0

Realmente no funciona tan bien con las matrices. Obtener una matriz de eso es difícil si los elementos iniciales contienen espacios, por ejemplo. Tenga por ejemplo 'declare -a ARR = ('element1' 'con espacio' 'con dos espacios' 'element4')' y luego haga 'VAR = ($ {ARR [@] // element * /})'. Lo que obtendrás en 'VAR' no es una matriz de dos elementos (' con espacio' y 'con dos espacios') sino una matriz de cinco elementos (' con', 'espacio',' con', 'dos', 'espacios'). –

1

Definí y usé la siguiente función:

# Removes elements from an array based on a given regex pattern. 
# Usage: filter_arr pattern array 
# Usage: filter_arr pattern element1 element2 ... 
filter_arr() { 
    arr=([email protected]) 
    arr=(${arr[@]:1}) 
    dirs=($(for i in ${arr[@]} 
     do echo $i 
    done | grep -v $1)) 
    echo ${dirs[@]} 
} 
uso

Ejemplo:

$ arr=(chicken egg hen omelette) 
$ filter_arr "n$" ${arr[@]} 

de salida:

tortilla de huevo

La salida de la función es una cadena. Para volver a convertirlo en una matriz:

$ arr2=(`filter_arr "n$" ${arr[@]}`) 
+0

Si los elementos de la matriz contienen espacios, esto no los conservará, sino que se dividirá creando nuevas matrices de elementos. Puedes verlo teniendo 'declare -a arr = ('element1' 'con espacio' 'con dos espacios' 'element4')' y filtrando por 'element'. El resultado en lugar de contener simplemente 'con espacio' y' con dos espacios' contendrá cada palabra como elemento separado. –

3

Filtrado de una matriz es complicado si se tiene en cuenta la posibilidad de elementos que contienen espacios (por no mencionar siquiera los personajes "más raras"). En particular, las respuestas dadas hasta ahora (haciendo referencia a varias formas de ${x[@]//pref*/}) fallarán con dichas matrices.

He investigado un poco este problema y he encontrado una solución, pero no es una buena idea. Pero al menos lo es.

Para ejemplos de ilustración supongamos arr nombres de la matriz que queremos filtrar. Comenzaremos con la expresión núcleo:

for index in "${!ARR[@]}" ; do [[ …condition… ]] && unset -v 'ARR[$index]' ; done 
ARR=("${ARR[@]}") 

Ya hay algunos elementos dignos de mención:

  1. "${!ARR[@]}" evalúa a los índices de la matriz (en contraposición a los elementos).
  2. El formulario "${!ARR[@]}" es obligatorio. No debe saltear citas o cambiar @ a *. O bien, la expresión se romperá en las matrices asociativas donde las claves contienen espacios (por ejemplo).
  3. La parte posterior a do puede ser lo que desee. La idea es solo que debe hacer unset como se muestra para los elementos que no desea tener en la matriz.
  4. It is advised or even needed para usar -v y comillas con unset o bien pueden ocurrir cosas malas.
  5. Si la parte posterior a do es como se sugirió anteriormente, puede usar && o || para filtrar los elementos que pasan o no la condición.
  6. La segunda línea, reasignación de ARR, solo se necesita con matrices no asociativas y se romperá con las matrices asociativas. (No salí rápidamente con una expresión genérica que manejará ambas cosas mientras no la necesite ...). Para matrices comunes, es necesario si desea tener índices consecutivos.Debido a que unset en un elemento de matriz no modifica (descarta en uno) los elementos de índices más altos, simplemente hace un agujero en los índices. Ahora bien, si solo iteras sobre la matriz (o la expandes como un todo) esto no crea ningún problema. Pero para otros casos necesita reasignar índices. Tenga en cuenta también que si tiene algún agujero en los índices antes de que se elimine también. Por lo tanto, si necesita preservar los agujeros existentes, debe hacerse más lógica junto con el unset y la reasignación final.

Ahora bien, se trata de la condición. La expresión [[ ]] es una manera fácil si puede usarla. (Consulte here). En particular, admite la coincidencia de expresiones regulares usando Extended Regular Expressions. (Consulte here.) También tenga cuidado al usar grep o cualquier otra herramienta basada en línea para esto si espera que los elementos de la matriz puedan contener no solo espacios sino también nuevas líneas. (Aunque un nombre de archivo muy desagradable podría tener un carácter de nueva línea que pienso ...)


En referencia a la pregunta misma la expresión [[ ]] tendría que ser:

[[ ${ARR[$index]} =~ ^pref ]] 

(con && unset que el anterior)


Finalmente, veamos cómo funciona esto en esos casos difíciles. En primer lugar se construye la matriz:

declare -a ARR='([0]="preffoo" [1]="bar" [2]="foo" [3]="prefbaz" [4]="baz" [5]="prefbar" [6]="pref with spaces")' 
ARR+=($'pref\nwith\nnew line') 
ARR+=($'\npref with new line before') 

podemos ver que tenemos todos los casos complejos mediante la ejecución de declare -p ARR y conseguir:

declare -a ARR='([0]="preffoo" [1]="bar" [2]="foo" [3]="prefbaz" [4]="baz" [5]="prefbar" [6]="pref with spaces" [7]="pref 
with 
new line" [8]=" 
pref with new line before")' 

Ahora corremos la expresión de filtro:

for index in "${!ARR[@]}" ; do [[ ${ARR[$index]} =~ ^pref ]] && unset -v 'ARR[$index]' ; done 

y otra prueba (declare -p ARR) da esperado:

observe cómo se eliminaron todos los elementos que comienzan con pref pero los índices no cambiaron. Tenga en cuenta también que ${ARRAY[8]} todavía está allí ya que comienza con una nueva línea en lugar de pref.

Ahora para la reasignación definitiva:

ARR=("${ARR[@]}") 

y comprobar (declare -p ARR):

declare -a ARR='([0]="bar" [1]="foo" [2]="baz" [3]=" 
pref with new line before")' 

que es exactamente lo que se esperaba.


Para las notas de cierre. Sería bueno si esto pudiera cambiarse en un trazador de líneas flexible. Pero no creo que haya una manera de hacerlo más corto y más simple como lo es ahora sin definir funciones o similares.

En cuanto a la función, sería bueno tenerla aceptar array, return array y tener una prueba fácil de configurar para excluir o mantener. Pero no soy lo suficientemente bueno con Bash para hacerlo ahora.

Cuestiones relacionadas