Utilizzo del comando grep con un array di parole
Ho un file di testo con alcune linee e ho salvato il file di testo in un array usando
readarray -t array < Textfile
Il file di testo contiene ad esempio quanto segue:
123
456
789
Ora vorrei usare il comando grep per cercare i risultati in un altro file di testo usando l'array, quindi stampa le righe dove appaiono "123", "456" o "789". Per testare l'array, ho provato a fare in modo che grep cerchi "123", "456" e "789" lo stesso file di testo e visualizzi le righe corrispondenti, se appare almeno una delle tre serie di caratteri.
Provai
grep "${array[*]}" Textfile
ma questo non mostra alcun risultato. Che cosa sto facendo di sbagliato?
Risposte
Prima di tutto, potresti semplicemente usare il file stesso. È molto più semplice che provare a utilizzare un array di shell:
grep -f file1.txt file2.txt
Ciò stamperà tutte le righe da file2.txtquella corrispondenza con qualsiasi riga in file1.txt.
Se si dispone di utilizzare un array per qualche ragione, le cose sono più complicate. Non puoi farlo solo grep "${array[*]}" Textfileperché "${array[*]}"si espanderà nell'elenco di elementi nell'array separati da uno spazio:
$ array=("foo" "bar" "baz") $ echo "${array[*]}"
foo bar baz
Il che significa che il tuo grepcomando diventerebbe:
grep 'foo bar baz' file
E questo significa "grep per foo bar baznel file file". Quello che vuoi fare è grep per foo, o bar , o baz . Questo può essere fatto usando l'opzione -E di grepe unendo i modelli che vuoi cercare con |:
grep -E 'foo|bar|baz' file
Per fare ciò, dovrai fare qualcosa di complicato come:
grep -E "$(printf '%s|' "${array[@]}" | sed 's/|$//')" file
O forse:
grep -E "$(export IFS="|"; echo "${array[*]}")" file
Nel complesso, è semplicemente migliore, più veloce e più facile usare il file e dimenticare l'array.
grep "${array[*]}" Textfile
Funzionerà fintanto che imposti la IFSnuova riga (o qualsiasi cosa che inizi con una nuova riga) e utilizzi --o -eper assicurarti che funzioni anche se il primo elemento inizia con -.
"${array[*]}"nelle shell di tipo Korn, come "$*"nelle shell POSIX, si espande alla lista degli elementi uniti con il primo carattere di $IFS. Il valore predefinito di $IFSè <SPC><TAB><NL>( <SPC><TAB><NL><NUL>in zsh), quindi per impostazione predefinita, dovresti ottenere l'elemento unito con caratteri SPC. Perché grep, avresti bisogno che le diverse espressioni regolari siano separate da una nuova riga per grepeseguire il ciclo per ciascuna a turno.
IFS=$'\n'
grep -e "${array[*]}" file
In zsh, doing grep -e "${(pj:\n:)array}" file( joining esplicitamente con newline invece di modificare $IFSglobalmente), o grep -e$^array file(che espande l'array in fish/ rc-style, dove diventa grep -efirst -esecond fileun altro modo per fornire diversi pattern grep) sarebbe più pulito.
Un'altra opzione è fare:
printf '%s\n' "${array[@]}" | grep -f - file
Questa volta si passa la lista di pattern delimitati da una nuova riga sullo grepstdin di invece che tramite un argomento.
Questi approcci funzionerebbero se i tuoi pattern sono stringhe fisse (con -F), espressioni regolari estese (con -E) o espressioni regolari di base (impostazione predefinita).
Puoi assicurarti che l'elenco dei modelli non sia vuoto:
(( ${#array[@]} > 0 )) && grep ...
La chiamata grepcon un pattern vuoto produce risultati diversi a seconda grepdell'implementazione e generalmente non di ciò che si desidera.