web-dev-qa-db-fra.com

Comment sortir uniquement les groupes capturés avec sed?

Existe-t-il un moyen de dire à sed de ne générer que les groupes capturés? Par exemple, étant donné l'entrée:

This is a sample 123 text and some 987 numbers

et motif:

/([\d]+)/

Pourrais-je obtenir uniquement les sorties 123 et 987 de la même manière que les références arrières?

248
Pablo

La clé pour que cela fonctionne est de dire à sed d'exclure ce que vous ne voulez pas afficher, ainsi que de spécifier ce que vous voulez.

_string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'
_

Cela dit:

  • ne pas imprimer par défaut chaque ligne (_-n_)
  • exclure zéro ou plusieurs non-chiffres
  • inclure un ou plusieurs chiffres
  • exclure un ou plusieurs chiffres
  • inclure un ou plusieurs chiffres
  • exclure zéro ou plusieurs non-chiffres
  • affiche la substitution (p)

En général, dans sed, vous capturez des groupes à l'aide de parenthèses et vous affichez ce que vous capturez à l'aide d'une référence arrière:

_echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'
_

affichera "bar". Si vous utilisez _-r_ (_-E_ pour OS X) pour les expressions rationnelles étendues, vous n'avez pas besoin d'échapper aux parenthèses:

_echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'
_

Il peut y avoir jusqu'à 9 groupes de capture et leurs références arrières. Les références arrière sont numérotées dans l'ordre d'apparition des groupes, mais elles peuvent être utilisées dans n'importe quel ordre et peuvent être répétées:

_echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'
_

affiche "une barre a".

Si vous avez GNU grep (cela peut également fonctionner sous BSD, y compris OS X):

_echo "$string" | grep -Po '\d+'
_

ou des variations telles que:

_echo "$string" | grep -Po '(?<=\D )(\d+)'
_

L'option _-P_ active les expressions régulières compatibles Perl. Voir man 3 pcrepattern ou man 3 pcresyntax .

292

Sed a mémorisé jusqu'à neuf modèles, mais vous devez utiliser des parenthèses insérées pour mémoriser des parties de l'expression régulière.

Voir ici pour des exemples et plus de détails

52
Peter McG

vous pouvez utiliser grep

grep -Eow "[0-9]+" file
30
ghostdog74

Abandonnez et utilisez Perl

Puisque sed ne le coupe pas, jetons simplement la serviette et utilisons Perl, du moins c'est LSB alors que grep GNU les extensions ne sont pas :-)

  • Imprimez l'intégralité de la pièce correspondante. Aucun groupe ni aucune recherche ne sont nécessaires:

    cat <<EOS | Perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS
    

    Sortie:

    12
    3456
    
  • Correspondance simple par ligne, champs de données souvent structurés:

    cat <<EOS | Perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS
    

    Sortie:

    1
    34
    

    À regarder derrière:

    cat <<EOS | Perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
    
  • Plusieurs champs:

    cat <<EOS | Perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS
    

    Sortie:

    1 2
    34 56
    
  • Correspondances multiples par ligne, données souvent non structurées:

    cat <<EOS | Perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS
    

    Sortie:

    1 
    34 78
    

    À regarder derrière:

    cat EOS<< | Perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS
    

    Sortie:

    1
    3478
    

Je crois que le modèle donné dans la question était à titre d'exemple uniquement, et le but était de faire correspondre le modèle any .

Si vous avez une extension sed avec l’extension GNU permettant l’insertion d’une nouvelle ligne dans l’espace du motif, voici une suggestion:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Ces exemples sont avec tcsh (oui, je sais c'est le mauvais shell) avec CYGWIN. (Edit: pour bash, supprimez set et les espaces autour de =.)

8
Joseph Quinsey

course (s) de chiffres

Cette réponse fonctionne avec n'importe quel nombre de groupes de chiffres. Exemple:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Réponse élargie.

Est-il possible d'indiquer à sed de ne produire que les groupes capturés?

Oui. remplace tout le texte par le groupe de capture:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

Ou avec une syntaxe étendue (moins de guillemets et permettent l'utilisation de +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Pour éviter d’imprimer le texte original en l’absence de numéro, utilisez:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) N'imprime pas l'entrée par défaut.
  • (/ p) n'imprime que si un remplacement a été effectué.

Et pour faire correspondre plusieurs numéros (et aussi les imprimer):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Cela fonctionne pour n'importe quel nombre d'exécutions de chiffres:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Ce qui est très similaire à la commande grep:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

À propos de\d

et motif: /([\d]+)/

Sed ne reconnaît pas la syntaxe '\ d' (raccourci). L'équivalent ascii utilisé ci-dessus [0-9] n'est pas exactement équivalent. La seule solution consiste à utiliser une classe de caractères: '[[: digit:]] `.

La réponse sélectionnée utilise de telles "classes de caractères" pour construire une solution:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Cette solution ne fonctionne que pour (exactement) deux séries de chiffres.

Bien sûr, étant donné que la réponse est en cours d'exécution dans le shell, nous pouvons définir quelques variables pour raccourcir cette réponse:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Mais, comme cela a déjà été expliqué, l’utilisation d’une commande s/…/…/gp est préférable:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Cela couvrira les passages répétés de chiffres et l’écriture d’une commande plus courte.

7
Arrow

Essayer

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Je l'ai sous cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$
5
Bert F

Ce n'est pas ce que l'OP a demandé (capturer des groupes) mais vous pouvez extraire les nombres en utilisant:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Donne ce qui suit:

123
987
2
Thomas Bratt