Comment réduire la gourmandise d'une expression régulière en AWK?

14

Je veux faire correspondre un motif non gourmand (expression régulière) awk. Voici un exemple:

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

Est-il possible d'écrire une expression régulière qui sélectionne la chaîne la plus courte?

@article{gjn,

au lieu de cette longue chaîne?:

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

Je veux obtenir ce résultat:

 Author =   {Grzegorz J. Nalepa},



J'ai un autre exemple:

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

Notez que j'ai changé les @caractères en caractères virgule ( ,) à la première position de la chaîne d'entrée et de l'expression régulière (et également changé .*en [^,]*). Est-il possible d'écrire une expression régulière qui sélectionne la chaîne la plus courte?

, Author =   {Grzegorz J. Nalepa},

au lieu de la chaîne plus longue?:

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

Je veux obtenir ce résultat:

,article{gjn
nowy1
la source
4
Tout comme les expressions rationnelles sont inadéquates pour une analyse HTML robuste, elles ne seront probablement pas en mesure d'effectuer ce type d'analyse grammaticale contextuelle. Cependant, si votre ensemble d'entrées est assez restreint et bien formé, vous pourrez peut-être vous en sortir avec regex tant que vous déclarez quelles sont vos restrictions. Par exemple, vous pouvez chercher à Authorsuivre une virgule et un espace, suivi d'un espace suivi par =suivi d'un espace suivi {par tout autre non }suivi }, bien que cela nécessite (entre autres) que vous ne puissiez pas imbriquer {}à l'intérieur de la = { ... }pièce.
jw013
@ jw013, merci pour votre explication. J'attendrai pourtant les suggestions des autres utilisateurs.
nowy1

Réponses:

18

Si vous souhaitez sélectionner @et jusqu'au premier ,après cela, vous devez le spécifier comme@[^,]*,

Ceci est @suivi d'un nombre quelconque ( *) de non-virgules ( [^,]) suivi d'une virgule ( ,).

Cette approche fonctionne comme l'équivalent @.*?,, mais pas pour des choses comme @.*?string, c'est là que ce qui est après est plus qu'un seul caractère. Nier un personnage est facile, mais nier les chaînes dans les expressions régulières est beaucoup plus difficile .

Une approche différente consiste à prétraiter votre entrée pour remplacer ou ajouter le préfixe stringà un caractère qui autrement n'apparaît pas dans votre entrée:

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

Si vous ne pouvez pas garantir que l'entrée ne contiendra pas votre caractère de remplacement ( \1ci-dessus), une approche consiste à utiliser un mécanisme d'échappement:

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

Cela fonctionne pour les strings fixes mais pas pour les expressions rationnelles arbitraires comme pour l'équivalent de @.*?foo.bar.

Stéphane Chazelas
la source
Merci beaucoup pour la bonne réponse. Dans mon montage, j'ai demandé un autre exemple (voir mon montage).
nowy1
6

Il existe déjà plusieurs bonnes réponses fournissant des solutions pour awkl'incapacité de faire des correspondances non gourmandes, donc je fournis quelques informations sur une autre façon de le faire en utilisant des expressions régulières compatibles Perl (PCRE). Notez que la plupart des awkscripts simples "match and print" peuvent facilement être réimplémentés en perlutilisant l' -noption de ligne de commande, et des scripts plus complexes peuvent être convertis avec le traducteur a2p Awk to Perl.

Perl a un opérateur non gourmand qui peut être utilisé dans les scripts Perl et tout ce qui utilise PCRE. Par exemple, également implémenté dans l' -Poption GNU grep .

PCRE n'est pas identique aux expressions régulières de Perl, mais il est très proche. C'est un choix populaire d'une bibliothèque d'expressions régulières pour de nombreux programmes, car elle est très rapide, et les améliorations Perl des expressions régulières étendues sont très utiles.

Depuis la page de manuel 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
cas
la source
3

Il s'agit d'un ancien article, mais les informations suivantes peuvent être utiles pour les autres.

Il existe un moyen, certes grossier, d'effectuer une correspondance RE non gourmande dans awk. L'idée de base est d'utiliser la fonction match (chaîne, RE) et de réduire progressivement la taille de la chaîne jusqu'à ce que la correspondance échoue, quelque chose comme (non testé):

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
}
Jim Mellander
la source
2

Pour les expressions générales, cela peut être utilisé comme une correspondance non gourmande:

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
}

J'utilise ceci basé sur la réponse de @ JimMellander. smatchse comporte comme match, en retournant:

la position dans s laquelle l'expression régulière rse produit, ou 0 dans le cas contraire. Les variables RSTARTet RLENGTHsont définies sur la position et la longueur de la chaîne correspondante.

ericbn
la source
1

Il n'y a aucun moyen dans awk de faire une correspondance non gourmande. Cependant, vous pourrez peut-être obtenir la sortie souhaitée. La suggestion de sch fonctionnera pour cette ligne. Si vous ne pouvez pas compter sur une virgule, mais que "Author" est toujours le début de ce que vous voulez, vous pouvez le faire:

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

Si le nombre de caractères précédant Auteur est toujours le même, vous pouvez le faire:

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

Vous avez juste besoin de savoir à quoi ressemblent vos données sur l'ensemble.

user17591
la source
0

Il y a toujours un moyen. Le problème donné peut être résolu assez facilement en utilisant des virgules comme séparateur.

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

Lorsque le nombre de champs varie, quelque chose de légèrement meilleur est généralement nécessaire. Dans ce cas, trouver un mot d'arrêt est souvent payant, car vous pouvez couper quoi que ce soit de la ligne en les utilisant. Dans le contexte de l'exemple, voici ce que j'entends par mots vides.

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

Je sais que c'est un ancien poste. Mais voici quelque chose qui utilise simplement awk comme OP comme demandé:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Sortie:,
Auteur = {Grzegorz J. Nalepa},

VINAY NAIR
la source
1
Cette réponse est erronée pour environ cinq raisons.
Scott
3
Pouvez-vous m'aider à comprendre ce qui ne va pas? La sortie semble cohérente avec ce qui est demandé. Essayer de comprendre pourquoi la réponse est bonne / non correcte.
VINAY NAIR