Come fare grep per gruppi di n cifre, ma non più di n?


33

Sto imparando Linux e ho una sfida che non riesco a risolvere da solo. Ecco qui:

grep una riga da un file che contiene 4 numeri di fila ma non più di 4.

Non sono sicuro di come affrontare questo. Posso cercare numeri specifici ma non il loro importo in una stringa.


2
Una linea come dovrebbe 1234a12345essere visualizzata o no?
Eliah Kagan,

@Buddha devi spiegare la tua domanda insieme a un esempio.
Avinash Raj,

se i numeri sono preceduti dallo spazio o dall'inizio dell'ancoraggio della linea e seguiti da uno spazio o dalla fine dell'ancoraggio della linea, è possibile utilizzare semplicemente i limiti delle parole. \b\d{4}\b
Avinash Raj,

1
Questa domanda differisce da alcune domande sulle espressioni regolari essendo esplicitamente sull'uso di grep . Le domande sull'utilizzo dei programmi di utilità Unix in Ubuntu, come grep, sed e awk, sono sempre state considerate valide qui. A volte le persone chiedono come fare un lavoro con lo strumento sbagliato ; quindi la mancanza di contesto è un grosso problema, ma non è quello che sta succedendo qui. Si tratta di un argomento in questione, abbastanza chiaro per ricevere una risposta utile, utile per la nostra comunità e non vi è alcun vantaggio nel prevenire ulteriori risposte o spingerlo verso la cancellazione o la migrazione. Sto votando per riaprirlo.
Eliah Kagan,

1
Grazie mille ragazzi, non avevo idea di ricevere così tanti feedback. Questa è la risposta che stavo cercando: grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])'. Il comando deve essere in grado di tirare una stringa come questa (cosa che fa): abc1234abcd99999
Buddha

Risposte:


52

Esistono due modi per interpretare questa domanda; Tratterò entrambi i casi. Potresti voler visualizzare le righe:

  1. che contengono una sequenza di quattro cifre che non fa parte di una sequenza più lunga di cifre, oppure
  2. che contiene una sequenza di quattro cifre ma non più una sequenza di cifre (nemmeno separatamente).

Ad esempio, verrà visualizzato (1) 1234a56789, ma (2) non verrà visualizzato.


Se si desidera visualizzare tutte le righe che contengono una sequenza di quattro cifre che non fa parte di una sequenza più lunga di cifre, un modo è:

grep -P '(?<!\d)\d{4}(?!\d)' file

Questo usa espressioni regolari Perl , che Ubuntu grep( GNU grep ) supporta tramite -P. Non corrisponderà a testo simile 12345, né corrisponderà al 1234o 2345che ne fanno parte. Ma corrisponderà al 1234di 1234a56789.

Nelle espressioni regolari Perl:

  • \dindica qualsiasi cifra (è un modo breve per dire [0-9]o [[:digit:]]).
  • x{4}partite x4 volte. (La { }sintassi non è specifica per le espressioni regolari Perl; è presente anche in espressioni regolari estese grep -E). \d{4}Lo stesso vale per \d\d\d\d.
  • (?<!\d)è un'asserzione look-behind negativa di larghezza zero. Significa "se non preceduto da \d."
  • (?!\d)è un'asserzione di previsione negativa di larghezza zero. Significa "a meno che non sia seguito da \d".

(?<!\d)e (?!\d)non abbinare il testo al di fuori della sequenza di quattro cifre; invece (se usati insieme) impediranno di far corrispondere una sequenza di quattro cifre se fa parte di una sequenza più lunga di cifre.

Usare solo il look-behind o solo il look-ahead non è sufficiente perché la sottosequenza di quattro cifre più a destra o a sinistra sarebbe ancora abbinata.

Uno dei vantaggi dell'utilizzo delle asserzioni look-behind e look-ahead è che il modello corrisponde solo alle sequenze di quattro cifre stesse e non al testo circostante. Questo è utile quando si utilizza l'evidenziazione del colore (con l' --coloropzione).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Di default in Ubuntu, ogni utente ha alias grep='grep --color=auto'nel proprio ~.bashrcfile . Quindi si ottiene automaticamente l'evidenziazione del colore quando si esegue un semplice comando che inizia con grep(questo è quando gli alias vengono espansi) e l'output standard è un terminale (questo è ciò che controlla). Le partite sono in genere evidenziate in una sfumatura di rosso (vicino al vermiglio ), ma l'ho mostrato in grassetto corsivo. Ecco uno screenshot:--color=auto
Schermata che mostra quel comando grep, con 12345abc789d0123e4 come output, con 0123 evidenziato in rosso.

E puoi persino grepstampare solo il testo corrispondente, e non l'intera riga, con -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Modo alternativo, senza asserzioni Look-Behind e Look-Ahead

Tuttavia, se tu:

  1. è necessario un comando che verrà eseguito anche su sistemi in cui grepnon supporta -Po non desidera utilizzare un'espressione regolare Perl e
  2. non è necessario abbinare in modo specifico le quattro cifre, come di solito accade se il tuo obiettivo è semplicemente quello di visualizzare linee contenenti corrispondenze e
  3. va bene con una soluzione un po 'meno elegante

... allora puoi ottenerlo con un'espressione regolare estesa :

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Questo corrisponde a quattro cifre e al carattere non cifra - o all'inizio o alla fine della linea - che le circonda. In particolare:

  • [0-9]corrisponde a qualsiasi cifra (come [[:digit:]], o \dnelle espressioni regolari Perl) e {4}significa "quattro volte". Quindi [0-9]{4}corrisponde a una sequenza di quattro cifre.
  • [^0-9]partite caratteri non nella gamma di 0tramite 9. È equivalente a [^[:digit:]](o \D, nelle espressioni regolari Perl).
  • ^, quando non appare tra [ ]parentesi, corrisponde all'inizio di una riga. Allo stesso modo, $corrisponde alla fine di una riga.
  • |mezzi o e le parentesi sono per il raggruppamento (come in algebra). Quindi (^|[^0-9])corrisponde all'inizio della riga o a un carattere non cifra, mentre ($|[^0-9])corrisponde alla fine della riga o a un carattere non cifra.

Quindi le corrispondenze si verificano solo nelle righe che contengono una sequenza di quattro cifre ( [0-9]{4}) che è contemporaneamente:

  • all'inizio della riga o preceduto da una non cifra ( (^|[^0-9])) e
  • alla fine della riga o seguito da una non cifra ( ($|[^0-9])).

Se, d'altra parte, vuoi visualizzare tutte le righe che contengono una sequenza di quattro cifre, ma non contengono alcuna sequenza di più di quattro cifre (anche una che è separata da un'altra sequenza di sole quattro cifre), concettualmente il tuo l'obiettivo è trovare linee che corrispondano a un modello ma non a un altro.

Pertanto, anche se sai come farlo con un singolo motivo, suggerirei di usare qualcosa come il secondo suggerimento di grepMatt , ing per i due motivi separatamente.

Non trarrai alcun vantaggio da nessuna delle funzionalità avanzate delle espressioni regolari Perl quando lo fai, quindi potresti preferire non usarle. Ma in linea con lo stile sopra, ecco un accorciamento della soluzione di matt usando \d(e parentesi graffe) al posto di [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Dal momento che utilizza [0-9], il modo di matt è più portatile - funzionerà su sistemi in cui grepnon supporta le espressioni regolari Perl. Se usi [0-9](o [[:digit:]]) invece di \d, ma continui a usarlo { }, otterrai la portabilità di Matt in modo un po 'più conciso:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Modo alternativo, con un unico motivo

Se davvero preferisci un grepcomando che

  1. usa una singola espressione regolare (non due greps separate da una pipe , come sopra)
  2. per visualizzare le righe che contengono almeno una sequenza di quattro cifre,
  3. ma nessuna sequenza di cinque (o più) cifre,
  4. e non ti dispiace abbinare l'intera linea, non solo le cifre (probabilmente non ti dispiace)

... allora puoi usare:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

Il -xflag fa grepvisualizzare solo le linee in cui l'intera riga corrisponde (anziché qualsiasi riga contenente una corrispondenza).

Ho usato un'espressione regolare Perl perché penso che la brevità \de \Daumentare sostanzialmente la chiarezza in questo caso. Ma se hai bisogno di qualcosa di portatile per i sistemi grepche non supportano -P, puoi sostituirli con [0-9]e [^0-9](o con [[:digit:]]e [^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

Il modo in cui funzionano queste espressioni regolari è:

  • Nel mezzo \d{4}o [0-9]{4}corrisponde a una sequenza di quattro cifre. Potremmo avere più di uno di questi, ma dobbiamo averne almeno uno.

  • A sinistra (\d{0,4}\D)*o ([0-9]{0,4}[^0-9])*corrisponde a zero o più ( *) istanze di non più di quattro cifre seguite da una non cifra. Le cifre zero (ovvero nulla) sono una possibilità per "non più di quattro cifre". Questo corrisponde a (a) la stringa vuota o (b) qualsiasi stringa che termina in una non cifra e che non contiene sequenze di più di quattro cifre.

    Poiché il testo immediatamente a sinistra della centrale \d{4}(o [0-9]{4}) deve essere vuoto o terminare con una non cifra, ciò impedisce alla centrale \d{4}di far corrispondere quattro cifre che hanno un'altra (quinta) cifra a sinistra di esse.

  • A destra, (\D\d{0,4})*o ([^0-9][0-9]{0,4})*corrisponde a zero o più ( *) istanze di una non cifra seguita da non più di quattro cifre (che, come prima, potrebbero essere quattro, tre, due, una o addirittura nessuna). Questo corrisponde a (a) la stringa vuota o (b) qualsiasi stringa che inizia in una non cifra e non contiene sequenze di più di quattro cifre.

    Poiché il testo immediatamente a destra della centrale \d{4}(o [0-9]{4}) deve essere vuoto o iniziare con una non cifra, questo impedisce alla centrale \d{4}di far corrispondere quattro cifre che hanno un'altra (quinta) cifra appena alla loro destra.

Ciò garantisce che una sequenza di quattro cifre sia presente da qualche parte e che nessuna sequenza di cinque o più cifre sia presente ovunque.

Non è male o sbagliato farlo in questo modo. Ma forse il motivo più importante per considerare questa alternativa è che chiarisce invece il vantaggio dell'uso (o simile), come suggerito sopra e nella risposta di Matt .grep -P '\d{4}' file | grep -Pv '\d{5}'

In questo modo, è chiaro che il tuo obiettivo è selezionare linee che contengono una cosa ma non un'altra. Inoltre la sintassi è più semplice (quindi può essere compresa più rapidamente da molti lettori / manutentori).


9

Questo ti mostrerà 4 numeri di fila ma non di più

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Nota ^ significa che no

C'è un problema con questo, anche se non sono sicuro di come risolvere ... se il numero è la fine della riga, non verrà visualizzato.

Questa versione più brutta funzionerebbe comunque per quel caso

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]

oops, non avevo bisogno di essere egrep - l'ho modificato
matt

2
Il primo è sbagliato: trova a12345b, perché corrisponde 2345b.
Volker Siegel,

0

Se grepnon supporta le espressioni regolari perl ( -P), utilizzare il seguente comando shell:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

dove printf '[0-9]%.0s' {1..4}produrrà 4 volte [0-9]. Questo metodo è utile quando hai cifre lunghe e non vuoi ripetere il modello (basta sostituire 4con il numero delle tue cifre da cercare).

Utilizzando -wcercherà le parole intere. Tuttavia, se sei interessato a stringhe alfanumeriche, come 1234a, quindi aggiungi [^0-9]alla fine del modello, ad es

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

L'uso $()è sostanzialmente una sostituzione di comando . Controlla questo post per vedere come printfripete il modello.


0

Puoi provare sotto il comando sostituendolo filecon il nome del file effettivo nel tuo sistema:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Puoi anche consultare questo tutorial per ulteriori usi del comando grep.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.