2
votes

SED remplace quelques premières occurrences (et plages) de motif

est-il possible de changer les 4 premières (ou plus) occurrences de chaîne dans ce scénario en utilisant SED (opposé de sed -r 's / [^ [: space:]] * / TEST / 4g' code>):

echo one two three four five six seven | awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}'  | sed -r 's/[^ ]*/TEST/4g' |  awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}'

Je le fais fonctionner en inversant l'ordre des mots en ligne en utilisant AWK deux fois, mais c'est long, complexe et je veux l'obtenir avec juste SED:

XXX

Il y a peut-être aussi une option pour changer les plages d'occurrence comme 3-5, 6-12, ...?

Exemple d'entrée:

un deux trois quatre cinq six sept

huit neuf dix onze douze treize quatorze

quinze seize dix-sept dix-huit dix-neuf vingt-et-un


10 commentaires

awk est meilleur pour cela, vous ne comprendrez pas une commande sed cryptique six mois après l'avoir écrite.


unix.stackexchange.com/a/155810


Cette réponse ne fonctionnera pas car ici, le texte recherché n'est pas statique. Il y a cependant d'autres réponses qui pourraient convenir ici.


@CorentinLimier Je connais cette option, cela ne fonctionnera que pour le même mot :)


Cela ne répond pas à votre question mais vous pouvez simplifier votre code en utilisant rev : echo un deux trois quatre cinq six sept | rev | sed 's / [^] * / TSET / 4g' | rev . J'essaie d'en trouver un meilleur car la commande sed doit être mise à jour si la ligne contient un nombre de mots différent.


Veuillez ajouter un exemple d'entrée et la sortie souhaitée pour cet exemple d'entrée à votre question.


Cela semble fonctionner avec GNU sed , mais vous ne pouvez pas faire les plages: sed 's / [^] [^] * / \ n & / g;: t; / \ n / { x; /. \ {4 \} /! {s /$/./; x; s / \ n [^] [^] * / TEST /; bt}; x}; s / \ n // g ' <<< "un deux trois quatre cinq six sept"


Un moyen simple de changer les quatre premières chaînes d'une ligne est d'ajouter des marqueurs aux chaînes que vous souhaitez remplacer, par exemple. Les plages sed 's / \ S \ + / \ n & / g; s / \ n // 5g; s / \ n \ S \ + / TEST / g' fichier sur une ligne peuvent être atteintes en utilisant une méthode similaire.


@potong - vaut une réponse. Je ne voyais pas comment vous arriviez aux plages de cette façon, mais il vous suffit d'ajouter la limite inférieure au premier remplacement. Soigné. Cela rapporte également des points pour avoir la chaîne TEST une seule fois.


@potong nice :) jusqu'ici le meilleur pour moi.


4 Réponses :


3
votes

Qu'en est-il d'un seul AWK? ":

$ perl -pe '$c=0;s/\S+/++$c~~[3..5]?"TEST":$&/ge' input
Smartmatch is experimental at -e line 1. <== This is a warning that goes to STDERR
one two TEST TEST TEST six seven
eight nine TEST TEST TEST thirteen fourteen
fifteen sixteen TEST TEST TEST twenty twenty-one

Exécution de test:

perl -pe 's/\S+/++$c~~[3..5]?"TEST":$&/ge'

Cette solution est courte, lisible et maintenable. Si cela ne vous satisfait pas, veuillez ajouter quelques détails sur votre problème spécifique.


Solution équivalente à Perl :

$ awk '{for(i=3;i<6;i++)$i="TEST";print}' input
one two TEST TEST TEST six seven
eight nine TEST TEST TEST thirteen fourteen
fifteen sixteen TEST TEST TEST twenty twenty-one
Exécution du test:
awk '{for(i=3;i<6;i++)$i="TEST";print}'

Peut-être y a-t-il une option pour changer les plages d'occurrence comme 3-5, 6-12

AWK:

$ echo one two three four five six seven | perl -pe 's/\S+/$i++<4?"TEST":$&/ge'
TEST TEST TEST TEST five six seven

Exécution de test sur le fichier d'entrée nouvellement fourni:

perl -pe 's/\S+/$i++<4?"TEST":$&/ge'


3 commentaires

C'est correct, mais je recherche quelque chose basé sur SED si c'est encore possible et assez facile à mettre en œuvre et à retenir.


@mike Oui, vous avez clairement indiqué que vous recherchiez une solution simple et uniquement sed. Je me demandais si c'était juste pour apprendre sed (auquel cas «pas possible» pourrait être une réponse) ou s'il y a des exigences imposées par le problème en question (auquel cas fournir un peu plus de contexte pourrait donner de meilleures réponses) .


@mike avec sed autre chose que s / old / new / ne sera pas assez facile à implémenter et à retenir. ce sera plutôt une collection cauchemardesque de runes qui vous quittera pleurnicher dans votre sommeil lorsque vous le rencontrez dans votre code 6 mois plus tard et que vous avez besoin de le comprendre.



1
votes

La réponse a été fournie ici par mikeserv . REMARQUE : si vous souhaitez traiter une plage, vous devez utiliser la limite maximale, car elle traitera autant de correspondances que possible sans générer d'exceptions / erreurs.

GNU sed: p>

s/1/\
&/g

POSIX sed:

nl='
';
echo 'one two three four five six seven' | sed "s/[^[:space:]]*/\\$nl&/g;:t${nl}/\n/{x;/.\{4\}/!{${nl}s/$/./;x;s/\n[^[:space:]]*/TEST/;bt$nl};x$nl};s/\n//g"

Voir le démo en ligne sed .

Explication originale (notez qu'ici, 1 est remplacé par 2 , vous pouvez utiliser tout autre modèle):

Là, j'utilise deux techniques notables. En premier lieu, chaque l'occurrence de 1 sur une ligne est remplacée par \ n1 . De cette façon, comme je faites ensuite les remplacements récursifs, je peux être sûr de ne pas remplacer le occurrence deux fois si ma chaîne de remplacement contient mon remplacement chaîne. Par exemple, si je remplace he par hey cela fonctionnera toujours.

Je fais ceci comme:

echo 'one two three four five six seven' | \
  sed 's/[^[:space:]]*/\n&/g;:t;/\n/{x;/.\{4\}/!{s/$/./;x;s/\n[^[:space:]]*/TEST/;bt};x};s/\n//g'

Deuxièmement, je compte les remplacements en ajoutant un caractère à h ancien espace pour chaque occurrence. Une fois que j'atteins trois, plus rien ne se produit. Si vous appliquez cela à vos données et remplacez le \ {3 \} par le total les remplacements que vous désirez et les adresses / \ n1 / à ce que vous voulez dire pour remplacer, vous devez en remplacer autant que vous le souhaitez.


2 commentaires

Oui, j'y avais réfléchi, mais j'ai relu le PO à la recherche de quelque chose qui ne soit pas long et complexe et j'ai abandonné l'idée. Ayant à choisir entre les deux exigences également inexpliquées de simple et sed , je suis allé avec le premier et j'ai laissé tomber le second. C'est pourtant un excellent exercice pour apprendre le sed, c'est le but.


Wow, cela fonctionne en effet mais très complexe, j'ai pensé qu'il y avait moyen de rendre ce cas facile à implémenter et à comprendre.



0
votes

C'est une tâche complètement inappropriée pour sed car sed est pour faire de simples s / old / new / sur des chaînes individuelles, c'est tout . Avec n'importe quel awk dans n'importe quel shell sur chaque boîte UNIX:

echo one two three four five six seven |
    awk -v beg=3 -v end=5 '{for (i=beg; i<=end; i++) $i="TEST"}1'
one two TEST TEST TEST six seven

et si vous avez besoin de le paramétrer:

$ echo one two three four five six seven | awk '{for (i=1; i<=4; i++) $i="TEST"}1'
TEST TEST TEST TEST five six seven

$ echo one two three four five six seven | awk '{for (i=3; i<=5; i++) $i="TEST"}1'
one two TEST TEST TEST six seven


0 commentaires

0
votes
$ echo "one two three four fix six" | \
sed -E ':r s/(^|(TEST )+)[^ ]*/\1TEST/;/^(TEST ){4}/!br'
TEST TEST TEST TEST fix six
Explanation:
:r label named r to branch back to
s/(^|(TEST )+)[^ ]*/\1TEST/; replacement that replaces just one occurrence of a non-TEST word, preceeded by either the start of the line or 1 or more TESTs
/^(TEST ){4}/!br' regex for what's wanted, followed by the !br to branch back to :r if it's not matched yet.
Clearly this is fragile.  It will loop infinitely if any lines don't have four words.  Might be GNU sed only.

2 commentaires

Qu'est-ce que ce caractère "|" fait après "^"?


Entre parenthèses, une barre verticale est un «ou». (alice | bob) correspond à l'un ou l'autre des mots. Le ^ | peut ressembler à deux opérateurs logiques, mais c'est ^ pour correspondre au début de l'espace de motif, suivi d'un ou.