2010-08-26 11 views
12

Dire che ho un array bash (ad esempio l'array di tutti i parametri) e voglio eliminare tutti i parametri che corrispondono a un determinato pattern o in alternativa copiare tutti gli elementi rimanenti in un nuovo array . In alternativa, al contrario, mantieni gli elementi corrispondenti a un modello.bash: come eliminare elementi da un array in base a un motivo

Un esempio per l'illustrazione:

x=(preffoo bar foo prefbaz baz prefbar) 

e voglio cancellare tutto a partire da pref al fine di ottenere

y=(bar foo baz) 

(l'ordine non è rilevante)

E se io vuoi la stessa cosa per una lista di parole separate da spazi bianchi?

x="preffoo bar foo prefbaz baz prefbar" 

e di nuovo cancellare tutto a cominciare pref al fine di ottenere

y="bar foo baz" 

risposta

5

Per togliere una stringa piatta (Hu lk ha già dato la risposta per gli array), è possibile attivare l'opzione extglob shell ed eseguire il seguente espansione

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

L'opzione extglob è necessario per i *(pattern-list) e ?(pattern-list) forme. Ciò consente di utilizzare espressioni regolari (anche se in una forma diversa rispetto alla maggior parte delle espressioni regolari) anziché solo l'espansione del percorso (*?[).

La risposta che Hulk ha fornito per gli array funziona solo sugli array. Se sembra funzionare su stringhe piatte, è solo perché nel test l'array non è stato disattivato prima.

ad es.

$ 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 grazie per chiarire la confusione dal post di Hulk e sottolineare questo altro percorso. – kynan

4

Si può fare questo:

eliminare tutte le occorrenze della stringa.

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

Edit:

Per gli spazi bianchi suo genere dello stesso

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

uscita:

foo bar baz

+0

Qualcosa di simile per una stringa di parole separate da spazi bianchi? – kynan

+0

Sembra che non funzioni abbastanza, che rimuova tutto dopo la prima occorrenza di 'pref' – kynan

+0

guarda la mia soluzione alternativa. O non ho capito la tua domanda – Hulk

8

Un altro modo per togliere una stringa appartamento è per convertirlo in un array quindi utilizzare il metodo array:

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

Contrast questo con inizio e fine di una serie:

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

Ti ho dato un +1 per mostrare entrambi e farmi pensare ... –

+0

Mi piace molto questa roba in quanto riduce davvero la quantità precedente di codice che uso per questo tipo di azione. –

+0

Non funziona molto bene con gli array. Ottenere una matrice fuori è difficile se gli elementi iniziali contengono spazi, per esempio. Per esempio, declare -a ARR = ('element1' 'con spazio' 'con due spazi' 'element4') 'e quindi esegui' VAR = ($ {ARR [@] // element * /}) '. Quello che otterrete in 'VAR' non è un array di due elementi (' with space' e 'with two spaces') ma un array di cinque elementi (' with', 'space',' with', 'two', 'spaces'). –

1

Ho definito e utilizzato la seguente funzione:

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

Esempio Utilizzo:

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

uscita:

egg omelette

L'output di funzione è una stringa. Per riconvertirlo in un array:

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

Se gli elementi dell'array contengono spazi questo non li conserverà ma dividerà invece creando nuovi array di elementi. Potete vederlo con 'declare -a arr = ('element1' 'con spazio' 'con due spazi' 'element4')' e il filtro per 'elemento'. Il risultato invece di contenere solo 'con spazio' e' con due spazi' conterrà ogni parola come elemento separato. –

3

filtraggio un array è difficile se si considera la possibilità di elementi contenenti spazi (per non parlare nemmeno caratteri "più strano"). In particolare, le risposte fornite finora (che si riferiscono a varie forme di ${x[@]//pref*/}) non funzioneranno con tali array.

Ho studiato un po 'questo problema e ho trovato una soluzione, ma non è una bella copertina. Ma almeno lo è.

Per esempi di esempi, supponiamo che arr nomi l'array che vogliamo filtrare. Iniziamo con l'espressione nucleo:

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

Esistono già alcuni elementi degni di nota:

  1. "${!ARR[@]}" restituisce indici dell'array (al contrario di elementi).
  2. Il modulo "${!ARR[@]}" è un must. Non devi saltare le quotazioni o cambiare @ a *. Oppure l'espressione si romperà su array associativi in ​​cui le chiavi contengono spazi (ad esempio).
  3. La parte successiva allo do può essere qualsiasi cosa tu voglia. L'idea è solo che devi fare unset come mostrato per gli elementi che non vuoi avere nell'array.
  4. It is advised or even needed per utilizzare -v e quotazioni con unset oppure possono accadere cose brutte.
  5. Se la parte successiva a do è come suggerito sopra, è possibile utilizzare && o || per filtrare gli elementi che passano o non superano la condizione.
  6. La seconda riga, riassegnazione di ARR, è necessaria solo con gli array non associativi e interromperà con gli array associativi. (Non sono uscito rapidamente con un'espressione generica che gestirà entrambe le cose mentre non ne ho bisogno ...). Per gli array ordinari è necessario se si desidera avere indici consecutivi.Poiché unset su un elemento di matrice non modifica (elimina uno) elementi di indici più alti, crea solo un buco negli indici. Ora se si esegue un'iterazione solo sull'array (o lo si espande nel suo complesso), questo non crea problemi. Ma per altri casi è necessario riassegnare gli indici. Nota anche che se hai avuto qualche buco negli indici prima che venga rimosso. Pertanto, se è necessario preservare i fori esistenti, è necessario eseguire una logica maggiore a fianco della unset e della riassegnazione finale.

Ora come si arriva alla condizione. L'espressione [[ ]] è un modo semplice se puoi usarla. (Vedi here.) In particolare supporta la corrispondenza delle espressioni regolari usando lo Extended Regular Expressions. (Vedere here.) Prestare attenzione anche all'utilizzo di grep o di qualsiasi altro strumento basato sulla linea per questo se si prevede che gli elementi dell'array possano contenere non solo spazi ma anche nuove linee. (Anche se un nome di file molto brutto potrebbe avere un carattere di nuova riga credo ...)


Riferendosi alla domanda stessa l'espressione [[ ]] dovrebbe essere:

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

(con && unset come sopra)


Vediamo finalmente come funziona con questi casi difficili. In primo luogo si costruisce la matrice:

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') 

possiamo vedere che abbiamo tutti i casi complessi eseguendo declare -p ARR e ottenere:

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")' 

Ora corriamo l'espressione di filtro:

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

e un altro test (declare -p ARR) fornisce:

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

nota come tutti gli elementi che iniziano con pref sono stati rimossi ma gli indici non sono cambiati. Si noti inoltre che ${ARRAY[8]} è ancora lì poiché inizia con una nuova riga anziché pref.

Ora per la riassegnazione finale:

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

e verificare (declare -p ARR):

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

che è esattamente quello che si aspettava.


Per le note di chiusura. Sarebbe bello se questo potesse essere cambiato in un unico rivestimento flessibile. Ma non penso che ci sia un modo per renderlo più breve e semplice come lo è ora senza definire funzioni o simili.

Per quanto riguarda la funzione, sarebbe anche meglio che accetti array, restituisca array e sia facile configurare il test da escludere o conservare. Ma non sono abbastanza bravo con Bash per farlo adesso.

Problemi correlati