Come ridurre l'avidità di un'espressione regolare in AWK?


14

Voglio fare corrispondenze di pattern non avide (espressione regolare) in awk. Ecco un esempio:

echo "@article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/@.*,/,""); print }'

È possibile scrivere un'espressione regolare che seleziona la stringa più corta?

@article{gjn,

invece di questa lunga stringa ?:

@article{gjn, Author =   {Grzegorz J. Nalepa},

Voglio ottenere questo risultato:

 Author =   {Grzegorz J. Nalepa},



Ho un altro esempio:

echo " , articolo {gjn, Autore = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); Stampa }'
      ↑ ↑ ^^^^^

Nota che ho cambiato i @caratteri in virgola ( ,) nella prima posizione sia della stringa di input che dell'espressione regolare (e anche cambiato .*in [^,]*). È possibile scrivere un'espressione regolare che seleziona la stringa più corta?

, Author =   {Grzegorz J. Nalepa},

invece della stringa più lunga ?:

,article{gjn, Author =   {Grzegorz J. Nalepa},

Voglio ottenere questo risultato:

,article{gjn

4
Proprio come i regex sono inadeguati per un robusto analisi HTML, probabilmente non saranno in grado di eseguire questo tipo di analisi grammaticale sensibile al contesto. Tuttavia, se il tuo set di input è abbastanza limitato e ben formato, potresti essere in grado di cavartela con regex fintanto che dichiari quali sono le tue restrizioni. Ad esempio, potresti cercare di Authorseguire una virgola e uno spazio bianco, seguito da uno spazio bianco seguito da uno =spazio bianco {seguito da uno non }seguito da }, anche se questo richiede (tra le altre cose) che non puoi annidare {}all'interno della = { ... }parte.
jw013,

@ jw013, grazie per la tua spiegazione. Tuttavia aspetterò suggerimenti di altri utenti.
nowy1

Risposte:


18

Se si desidera selezionare @e fino al primo ,successivo, è necessario specificarlo come@[^,]*,

Questo è @seguito da qualsiasi numero ( *) di non-virgole ( [^,]) seguito da una virgola ( ,).

Questo approccio funziona come l'equivalente di @.*?,, ma non per cose del genere @.*?string, è qui che ciò che è dopo è più di un singolo personaggio. Negare un personaggio è facile, ma negare le stringhe in regexps è molto più difficile .

Un approccio diverso è pre-elaborare l'input per sostituire o anteporre stringa un carattere che altrimenti non si verifica nell'input:

gsub(/string/, "\1&") # pre-process
gsub(/@[^\1]*\1string/, "")
gsub(/\1/, "") # revert the pre-processing

Se non puoi garantire che l'input non contenga il tuo personaggio sostitutivo ( \1sopra), un approccio è quello di utilizzare un meccanismo di escape:

gsub(/\1/, "\1\3") # use \1 as the escape character and escape itself as \1\3
                   # in case it's present in the input
gsub(/\2/, "\1\4") # use \2 as our maker character and escape it
                   # as \1\4 in case it's present in the input
gsub(/string/, "\2&") # mark the "string" occurrences

gsub(/@[^\2]*\2string/, "")

# then roll back the marking and escaping
gsub(/\2/, "")
gsub(/\1\4/, "\2")
gsub(/\1\3/, "\1")

Funziona con strings fissi ma non con regexps arbitrari come per l'equivalente di @.*?foo.bar.


Grazie mille per la buona risposta. Nella mia modifica ho chiesto ancora un altro esempio (vedi la mia modifica).
nowy1

6

Esistono già diverse risposte valide che consentono di aggirare awkl'incapacità di fare partite non golose, quindi sto fornendo alcune informazioni su un modo alternativo per farlo usando le espressioni regolari compatibili Perl (PCRE). Si noti che la maggior parte degli awkscript "match and print" più semplici possono essere facilmente implementati perlutilizzando l' -nopzione della riga di comando e gli script più complessi possono essere convertiti con il traduttore a2p Awk in Perl.

Perl ha un operatore non avido che può essere utilizzato negli script Perl e in qualsiasi cosa utilizzi PCRE. Ad esempio, implementato anche -Pnell'opzione GNU grep .

PCRE non è identico alle espressioni regolari di Perl, ma è molto vicino. È una scelta popolare di una libreria di espressioni regolari per molti programmi, perché è molto veloce e i miglioramenti del Perl alle espressioni regolari estese sono molto utili.

Dalla pagina man perlre (1) :

   By default, a quantified subpattern is "greedy", that is, it will match
   as many times as possible (given a particular starting location) while
   still allowing the rest of the pattern to match.  If you want it to
   match the minimum number of times possible, follow the quantifier with
   a "?".  Note that the meanings don't change, just the "greediness":

       *?        Match 0 or more times, not greedily
       +?        Match 1 or more times, not greedily
       ??        Match 0 or 1 time, not greedily
       {n}?      Match exactly n times, not greedily (redundant)
       {n,}?     Match at least n times, not greedily
       {n,m}?    Match at least n but not more than m times, not greedily

3

Questo è un vecchio post, ma le seguenti informazioni potrebbero essere utili per gli altri.

Esiste un modo, è vero, di eseguire corrispondenze RE non avide in awk. L'idea di base è usare la funzione match (stringa, RE) e ridurre progressivamente la dimensione della stringa fino a quando la corrispondenza fallisce, qualcosa come (non testato):

if (match(string, RE)) {
    rstart = RSTART
    for (i=RLENGTH; i>=1; i--)
        if (!(match(substr(string,1,rstart+i-1), RE))) break;
    # At this point, the non-greedy match will start at rstart
    #  for a length of i+1
}

2

Per le espressioni generali, questo può essere usato come una corrispondenza non avida:

function smatch(s, r) {
    if (match(s, r)) {
        m = RSTART
        do {
            n = RLENGTH
        } while (match(substr(s, m, n - 1), r))
        RSTART = m
        RLENGTH = n
        return RSTART
    } else return 0
}

Sto usando questo basato sulla risposta di @ JimMellander. smatchsi comporta come match, restituendo:

la posizione in s cui si rverifica l' espressione regolare o 0 in caso contrario. Le variabili RSTARTe RLENGTHsono impostate sulla posizione e sulla lunghezza della stringa corrispondente.


1

In awk non c'è modo di fare abbinamenti non golosi. Tuttavia, potresti essere in grado di ottenere l'output desiderato. il suggerimento di sch funzionerà per quella linea. Se non puoi fare affidamento su una virgola, ma "Autore" è sempre l'inizio di ciò che desideri, puoi farlo:

awk '{ sub(/@.*Author/,"Author"); print }'

Se il numero di caratteri che precede l'autore è sempre lo stesso, puoi farlo:

awk '{ sub(/@.{21}/,""); print }'

Devi solo sapere come sono i tuoi dati nell'intero set.


0

C'è sempre un modo. Il problema dato può essere risolto abbastanza facilmente usando le virgole come separatore.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk -F, '{sub(/^[ \t]/, "", $2); print $2}'

Quando il numero di campi varia, di solito è necessario qualcosa di leggermente migliore. In tal caso, trovare un punto di arresto spesso ripaga, poiché è possibile tagliare qualsiasi cosa dalla linea usandoli. Nel contesto dell'esempio, ecco cosa intendo per parole stop.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'

0

So che questo è un vecchio post. Ma ecco qualcosa che usa awk come OP come richiesto:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Output
:, Autore = {Grzegorz J. Nalepa},


1
Questa risposta è errata per circa cinque motivi.
Scott,

3
Potete per favore aiutarmi a capire cosa c'è che non va? L'output sembra coerente con quanto richiesto. Cercare di capire perché la risposta è giusta / non giusta.
VINAY NAIR,
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.