Confus par la sortie sed lors de l'utilisation de N. Quelqu'un peut-il expliquer ces résultats?

8

J'apprends sed. Tout semblait bien se passer jusqu'à ce que je tombe sur le N (multiligne ensuite). J'ai créé ce fichier (guide.txt) à des fins de pratique / compréhension / contexte. Voici le contenu dudit fichier ...

This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator

Mon objectif est donc de remplacer TOUTES les instances de "Network Administrator" par "System User". Parce que la première instance de "Administrateur réseau" est séparée par une nouvelle ligne (\ n), j'ai besoin de l'opérateur suivant multiligne (N) pour ajouter la ligne qui commence par "Administrateur" avec la ligne précédente se terminant par "Réseau \ n" . Aucun problème. Mais je veux également attraper toutes les autres instances monolignes "Administrateur réseau".

De mes recherches, j'ai appris que j'aurais besoin de deux commandes de substitution; un pour la chaîne séparée par la nouvelle ligne et un pour les autres. De plus, il y a du jive à cause de la dernière ligne contenant la correspondance de substitution et la multi-ligne suivante. Alors je crée ça ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt

Cela renvoie ces résultats ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User

Je pensais que la substitution sur une seule ligne intercepterait toutes les instances "normales" de "Administrateur réseau" et la remplacerait par "Utilisateur système", tandis que l'instruction multiligne fonctionnerait comme par magie sur l'instance séparée par une nouvelle ligne, mais comme vous peut le voir retourné, ce que je considère, des résultats inattendus.

Après quelques tripotages, j'ai atterri là-dessus ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt

Et voilà, j'obtiens la sortie souhaitée de ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Pourquoi cela fonctionne-t-il et le script sed original ne fonctionne pas? Je veux vraiment comprendre ça.

Merci d'avance pour votre aide.

dlowrie290
la source
Bon pour vous pour apprendre Sed! J'ai utilisé Sed pour résoudre une question très similaire plus tôt sur ce site; peut être intéressant.
Wildcard
Et deux autres exemples délicats de Sed: unix.stackexchange.com/a/277375/135943 , unix.stackexchange.com/a/257913/135943
Wildcard

Réponses:

6

Pendant que vous apprenez sed, je prendrai le temps d'ajouter à la réponse de @ John1024:

1) Veuillez noter que vous utilisez \ndans la chaîne de remplacement. Cela fonctionne dans GNU sed, mais ne fait pas partie de POSIX, il insérera donc une barre oblique inverse et un ndans de nombreux autres sed(l'utilisation \ndans le modèle est portable, btw).

Au lieu de cela, je suggère de faire s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g: le [[:space:]]correspondra à la nouvelle ligne ou à l'espace, vous n'avez donc pas besoin de deux scommandes, mais combinez-les en une. En l'entourant de \(...\)vous pouvez vous y référer dans le remplacement: le \1sera remplacé par tout ce qui a été apparié dans la première paire de \(\).

2) Pour faire correspondre correctement les modèles sur deux lignes, vous devez connaître le N;P;Dmodèle:

 sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'

Le Nest toujours ajouter la ligne suivante (sauf pour la dernière ligne, c'est pourquoi il est "adressé" avec $!(= sinon la dernière ligne; vous devriez toujours envisager de précéder Navec $!pour éviter de terminer accidentellement le script). Ensuite, après le remplacement, les Pimpressions uniquement la première ligne de l'espace de motif et la Dsupprime cette ligne et commence le cycle suivant avec les restes de l'espace de motif (sans lire la ligne suivante). C'est probablement ce que vous aviez initialement prévu.

N'oubliez pas ce modèle, vous en aurez souvent besoin.

3) Un autre modèle utile pour l'édition multiligne, surtout lorsque plus de deux lignes sont impliquées: Maintenez la collecte d'espace, comme je l'ai suggéré à John:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

Je le répète pour l'expliquer: Hajoute chaque ligne à l'espace d'attente. Comme cela entraînerait une nouvelle ligne supplémentaire avant la première ligne, la première ligne doit être déplacée au lieu d'être ajoutée avec 1h. Ce qui $!dsignifie "pour toutes les lignes sauf la dernière, supprimez l'espace de motif et recommencez". Ainsi, le reste du script n'est exécuté que pour la dernière ligne. À ce stade, le fichier entier est collecté dans l'espace d'attente (ne l'utilisez donc pas pour les très gros fichiers!) Et le gdéplace vers l'espace de motif, de sorte que vous pouvez effectuer tous les remplacements à la fois comme vous le pouvez avec l' -zoption de GNU sed.

C'est un autre schéma utile que je suggère de garder à l'esprit.

Philippos
la source
Hou la la! Grande explication! Ceci couplé avec la réponse de John m'a vraiment donné une meilleure idée de ce problème et séduit en général. On dirait que j'ai beaucoup plus à apprendre. J'aimerais pouvoir vérifier vos deux solutions comme réponses. Merci beaucoup pour vos deux efforts. Ils sont très appréciés.
dlowrie290
7

Tout d'abord, notez que votre solution ne fonctionne pas vraiment. Considérez ce fichier de test:

$ cat test1
Network
Administrator Network
Administrator

Et puis exécutez la commande:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator

Le problème est que le code ne remplace pas le dernier Network\nAdministrator.

Cette solution fonctionne:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User

Nous pouvons également l'appliquer à votre guide.txt:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

La clé est de continuer à lire en lignes jusqu'à ce que vous en trouviez une qui ne se termine pas parNetwork . Lorsque cela est accompli, les substitutions peuvent être effectuées.

Note de compatibilité: Toutes les utilisations ci-dessus \ndans le texte de remplacement. Cela nécessite GNU sed. Cela ne fonctionnera pas sur BSD / OSX sed.

[Pointe du chapeau à Philippos .]

Version multiligne

Si cela aide à clarifier, voici la même commande répartie sur plusieurs lignes:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

Comment ça fonctionne

  1. :a

    Cela crée une étiquette a.

  2. /Network$/{ $!{N;ba} }

    Si cette ligne se termine par Network, alors, si ce n'est pas la dernière ligne ( $!), lisez et ajoutez la ligne suivante ( N) et ramenez-la à label a( ba).

  3. s/Network\nAdministrator/System\nUser/g

    Faites la substitution avec la nouvelle ligne intermédiaire.

  4. s/Network Administrator/System User/g

    Effectuez la substitution avec le blanc intermédiaire.

Solution plus simple (GNU uniquement)

Avec GNU sed ( pas BSD / OSX), nous n'avons besoin que d'une seule commande de substitution:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' test1
System
User System
User

Et sur le guide.txtdossier:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Dans ce cas, -zindique à sed de lire jusqu'au premier caractère NUL. Étant donné que les fichiers texte n'ont jamais de caractère nul, cela a pour effet de lire l'intégralité du fichier à la fois. On peut alors faire la substitution sans se soucier de manquer une ligne.

Cette méthode n'est pas bonne si le fichier est énorme (ce qui signifie généralement des gigaoctets). Si elle est si grande, alors la lecture en une seule fois peut épuiser la RAM du système.

Solution qui fonctionne à la fois sur GNU et BSD sed

Comme l'a suggéré Phillipos , voici une solution portable:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'
John1024
la source
1
Excellente information, John! Merci d'avoir jeté un peu de lumière à ce sujet et votre solution alternative est très agréable. Cela étant dit, je ne comprends toujours pas pourquoi ma solution n'est pas une solution. Cela semble fonctionner, mais pas avec votre fichier test.txt. Pourquoi ma solution semble-t-elle fonctionner, mais pas vraiment? Merci beaucoup pour l'aide.
dlowrie290
1
@ dlowrie290 Votre solution lit en lignes par paires. Si Network Administratorest réparti entre la première et la deuxième ligne de cette paire, votre solution réussit la substitution. Il imprime ensuite ces deux lignes et lit dans la paire suivante. Si, cependant, la deuxième ligne de la première paire se termine par Networket la première ligne de la deuxième paire commence par Administrator, le code la manque. Mon code évite cela en lisant les lignes jusqu'à ce qu'il en trouve une qui ne se termine pas Network.
John1024
2
Veuillez noter que votre première solution multiligne dépend également des extensions GNU pour sed: Le \nremplacement n'est pas défini dans la norme. sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'est un moyen portable de le faire.
Philippos
@Philippos Excellents points. Réponse mise à jour pour inclure la solution portable.
John1024
1
Merci pour la clarification, John! Encore une fois, des choses formidables et votre temps / efforts sont très appréciés!
dlowrie290