Grep: l'asterisco (*) non funziona sempre


11

Se grep un documento che contiene quanto segue:

ThisExampleString

... per l'espressione This*Stringo *String, non viene restituito nulla. Tuttavia, This*restituisce la riga sopra come previsto.

Il fatto che l'espressione sia racchiusa tra virgolette non fa differenza.

Pensavo che l'asterisco indicasse un numero qualsiasi di caratteri sconosciuti? Perché funziona solo se è all'inizio dell'espressione? Se si tratta di comportamento previsto, cosa devo usare al posto delle espressioni This*Stringe *String?


perché non è così che funziona regex ... (in particolare:. * != any number of unknown charactersleggi il documento.)
njzk2

Risposte:


18

Un asterisco in espressioni regolari significa "corrisponde all'elemento precedente 0 o più volte".

Nel tuo caso particolare con grep 'This*String' file.txt, stai cercando di dire "hey, grep, abbinami alla parola Thi, seguito da szero minuscolo o più volte, seguito dalla parola String". La lettera minuscola snon si trova da nessuna parte Example, quindi grep ignora ThisExampleString.

Nel caso di grep '*String' file.txt, stai dicendo "grep, abbinami alla stringa vuota - letteralmente nulla - che precede la parola String". Certo, non ThisExampleStringè così che dovrebbe essere letto. (Ci sono altri significati possibili - puoi provarlo con e senza la -Ebandiera - ma nessuno dei significati è qualcosa di simile a quello che vuoi davvero qui.)

Sapendo che .significa "ogni singolo carattere", potremmo fare questo: grep 'This.*String' file.txt. Ora il comando grep lo leggerà correttamente: Thisseguito da qualsiasi carattere (pensalo come selezione di caratteri ASCII) ripetuto un numero qualsiasi di volte, seguito da String.


6
In Bash (e la maggior parte delle shell Unix) *è un personaggio speciale e dovrebbe essere citato o sfuggito ad esempio come questo: grep 'This*String' file.txto questo: grep This\*String file.txtper non essere sorpreso da risultati inaspettati.
pabouk,

2
@pabouk in shells, *è un carattere jolly. In grep, *è un operatore di espressioni regolari. Vedi unix.stackexchange.com/q/57957/70524
muru,

11
pabouk ha ragione, l'espansione del nome file avviene prima dell'esecuzione del comando; confrontare strace grep .* file.txt |& head -n 1 e strace grep '.*' file.txt |& head -n 1. Anche in realtà grepfunziona anche con qualsiasi carattere Unicode (ad esempio echo -ne ⇏ | grep ⇏le uscite )
Kos

1
@Serg: hai un'ottima reputazione qui, quindi ho pensato che notassi immediatamente cosa intendo. L'OP ha taggato la domanda bash quindi presumo che i comandi discussi siano interpretati da bash. Ciò significa che prima bashinterpreta i suoi caratteri speciali e solo dopo tutte le espansioni eseguite passa i parametri al processo generato. ----- Per esempio questo comando Bash: grep This.\*String file.txtsi riproducono /bin/grepcon questi parametri: 0 grep, 1: This.*String2: file.txt. Si noti che Bash ha rimosso la barra rovesciata e che il carattere di escape originale è *stato passato letteralmente.
pabouk,

7
La cosa divertente (e per la risoluzione dei problemi piuttosto brutta :) è che i tuoi comandi simili grep This.*String file.txtfunzioneranno normalmente perché molto probabilmente non ci sarà un file corrispondente all'espressione jolly della shell This.*String. In tal caso, per impostazione predefinita, Bash passerà l'argomento letteralmente incluso *.
pabouk,

8

Il *metacarattere in BRE 1 s, ERE 1 se PCRE 1 s corrisponde a 0 o più ricorrenze del modello precedentemente raggruppato (se un modello raggruppato precede il *metacarattere), 0 o più ricorrenze della precedente classe di caratteri (se una classe di caratteri è che precede il *metacarattere) o 0 o più occorrenze del carattere precedente (se né un modello raggruppato né una classe di caratteri precede il *metacarattere);

Ciò significa che nel This*Stringpattern, essendo il *metacarattere non preceduto né da un pattern raggruppato né da una classe di caratteri, il *metacarattere corrisponde a 0 o più occorrenze del carattere precedente (in questo caso il spersonaggio):

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Per abbinare 0 o più occorrenze di qualsiasi carattere, si desidera abbinare 0 o più occorrenze del .metacarattere, che corrisponde a qualsiasi carattere:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

Il *metacarattere in BRE ed ERE è sempre "avido", cioè corrisponderà alla partita più lunga:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Questo potrebbe non essere il comportamento desiderato; in caso contrario, puoi accendere il grepmotore PCRE (usando l' -Popzione) e aggiungere il ?metacarattere, che se messo dopo il *e i +metacaratteri ha l'effetto di cambiare la loro avidità:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1: espressioni regolari di base, espressioni regolari estese ed espressioni regolari compatibili Perl


Grazie per la risposta molto istruttiva. Tuttavia, ho scelto una risposta diversa perché era più breve e più facile da capire. +1 per fornire così tanti dettagli.
Trae,

@Trae Prego. Va bene, sono d'accordo che forse questo era troppo complesso e ha fatto troppe ipotesi per qualcuno che non ha familiarità con l'argomento.
kos,

4

Una delle spiegazioni trovate qui link :

L'asterisco " *" non significa la stessa cosa nelle espressioni regolari come nel jolly; è un modificatore che si applica al singolo carattere precedente o espressione come [0-9]. Un asterisco corrisponde a zero o più di ciò che lo precede. Quindi [A-Z]*corrisponde a un numero qualsiasi di lettere maiuscole, incluso nessuno, mentre [A-Z][A-Z]*corrisponde a una o più lettere maiuscole.


1

*ha uno speciale significato sia come un guscio globbing carattere ( "jolly") e come un normale espressione metacarattere . È necessario prendere in considerazione entrambi, anche se se si cita la propria espressione regolare, è possibile impedire alla shell di trattarla in modo speciale e assicurarsi che la passi invariata grep. Sebbene sorta di simile concettualmente, quale *mezzo per il guscio è molto diverso da quello che significa grep.

Innanzitutto la shell considera *un jolly.

Tu hai detto:

Il fatto che l'espressione sia racchiusa tra virgolette non fa differenza.

Dipende da quali file esistono in qualunque directory ci si trovi quando si esegue il comando. Per i modelli che contengono il separatore di directory /, può dipendere da quali file esistono nell'intero sistema. Dovresti sempre citare espressioni regolari per grep- e le virgolette singole sono in genere le migliori - a meno che tu non sia sicuro di stare bene con i nove tipi di trasformazioni potenzialmente sorprendenti che altrimenti la shell esegue prima di eseguire il grepcomando.

Quando la shell incontra un *carattere che non è citato , significa che significa "zero o più di qualsiasi carattere" e sostituisce la parola che lo contiene con un elenco di nomi di file che corrispondono al modello. (I nomi di file che iniziano con .sono esclusi - a meno che il modello stesso non inizi con . o non abbia configurato la shell per includerli comunque.) Questo è noto come globbing - e anche dall'espansione del nome del file e dell'espansione del percorso .

L'effetto di grepsolito è che il primo nome del file corrispondente viene preso come l'espressione regolare - anche se sarebbe abbastanza ovvio a un lettore umano che è non è inteso come un'espressione regolare - mentre tutti gli altri nomi di file elencati automaticamente dal tuo i glob vengono presi come file all'interno dei quali cercare le corrispondenze. (Non vedi la lista - è passata opacamente a grep.) Praticamente non vuoi che ciò accada.

Il motivo di questo è a volte non è un problema - e nel suo caso particolare, almeno fino ad ora , non't - è che *sarà lasciato solo se tutte le seguenti condizioni :

  1. C'erano nessun file i cui nomi abbinati. ... O hai disabilitato il globbing nella tua shell, in genere con set -fo equivalente set -o noglob. Ma questo è raro e probabilmente sapresti di averlo fatto.

  2. Stai usando una shell il cui comportamento predefinito è lasciare *da solo quando non ci sono nomi di file corrispondenti. Questo è il caso di Bash, che probabilmente stai usando, ma non in tutte le shell in stile Bourne. (Il comportamento predefinito nella popolare shell Zsh, ad esempio, è per globs di (a) espandere o (b) produrre un errore.) ... O hai cambiato questo comportamento della tua shell - come ciò varia attraverso le conchiglie.

  3. Altrimenti non hai detto alla tua shell di consentire la sostituzione dei globs con nulla quando non ci sono file corrispondenti, né di fallire con un messaggio di errore in questa situazione. In Bash ciò sarebbe stato possibile abilitando l' opzionenullglob o failglob shell , rispettivamente.

A volte puoi fare affidamento su # 2 e # 3 ma raramente puoi fare affidamento su # 1. Un grepcomando con un modello non quotato che funziona ora potrebbe smettere di funzionare quando si hanno file diversi o quando lo si esegue da una posizione diversa. Cita la tua espressione regolare e il problema scompare.

Quindi il grepcomando considera *un quantificatore.

Le altre risposte - come quelle di Sergiy Kolodyazhnyy e di Kos - affrontano anche questo aspetto di questa domanda, in modi leggermente diversi. Quindi incoraggio coloro che non li hanno ancora letti a farlo, prima o dopo aver letto il resto di questa risposta.

Supponendo che lo *faccia fare grep - che la quotazione dovrebbe garantire - grepquindi lo porta a significare che l'elemento che lo precede può accadere un numero qualsiasi di volte , piuttosto che dover ricorrere esattamente una volta . Potrebbe ancora succedere una volta. O potrebbe non essere presente affatto. O potrebbe essere ripetuto. Il testo che si adatta a una di queste possibilità verrà adattato.

Cosa intendo per "oggetto"?

  • Un singolo personaggio . Poiché bpartite letterale b, b*zero o più bs, quindi ab*cpartite ac, abc, abbc, abbbc, etc.

    Allo stesso modo, dal momento che .qualsiasi carattere , .*corrisponde a zero o più caratteri 1 , quindi a.*cpartite ac, akc, ahjglhdfjkdlgjdfkshlgc, anche acccccchjckhcc, ecc Or

  • Una classe di personaggi . Poiché [xy]partite xo y, [xy]*zero o più caratteri dove ciascuno è o xo y, quindi p[xy]*qpartite pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, etc.

    Questo vale anche per stenografia forme di classi di personaggi come \w, \W, \s, e \S. Poiché \wcorrisponde a qualsiasi carattere di parola, \w*corrisponde a zero o più caratteri di parole. O

  • Un gruppo . Dal momento che \(bar\)le partite bar, \(bar\)*zero o più bars, quindi foo\(bar\)*bazpartite foobaz, foobarbaz, foobarbarbaz, foobarbarbarbaz, etc.

    Con le opzioni -Eo -P, greptratta la tua espressione regolare come rispettivamente ERE o PCRE , piuttosto che come BRE , e quindi i gruppi sono circondati ( )invece di \( \), quindi useresti (bar)invece di \(bar\)e foo(bar)bazinvece di foo\(bar\)baz.

man grepfornisce una spiegazione ragionevolmente accessibile della sintassi BRE ed ERE alla fine, oltre a elencare tutte le opzioni della riga di comando grepaccettate all'inizio. Raccomando quella pagina di manuale come risorsa, e anche la documentazione GNU Grep e questo tutorial / sito di riferimento (che ho collegato a un numero di pagine sopra, sopra).

Per i test e l'apprendimento grep, ti consiglio di chiamarlo con uno schema ma senza nome file. Quindi prende l'input dal tuo terminale. Inserisci le righe; le righe che ti fanno eco sono quelle che contenevano il testo corrispondente al tuo motivo. Per uscire, premere Ctrl+ Dall'inizio di una riga, che segnala la fine dell'ingresso. (Oppure puoi premere Ctrl+ Ccome con la maggior parte dei programmi da riga di comando.) Ad esempio:

grep 'This.*String'

Se usi la --colorbandiera, grepevidenzierai le parti specifiche delle tue linee che corrispondono alla tua espressione regolare, il che è molto utile sia per capire cosa fa un'espressione regolare sia per trovare quello che stai cercando una volta che lo fai. Per impostazione predefinita, gli utenti di Ubuntu hanno un alias Bash che provoca grep --color=autol'esecuzione - il che è sufficiente per questo scopo - quando si esegue grepdalla riga di comando, quindi probabilmente non è nemmeno necessario passare --colormanualmente.

1 Pertanto, .*in un'espressione regolare significa cosa *significa in un guscio glob. Tuttavia, la differenza è che grepstampa automaticamente le linee che contengono la tua corrispondenza ovunque in esse, quindi in genere non è necessario avere .*all'inizio o alla fine di un'espressione regolare.

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.