Remplacer une chaîne contenant des caractères de nouvelle ligne

10

Avec le bashshell, dans un fichier avec des lignes comme les suivantes

first "line"
<second>line and so on

Je voudrais remplacer une ou plusieurs occurrences de "line"\n<second>avec other characterset obtenir à chaque fois:

first other characters line and so on

Je dois donc remplacer une chaîne à la fois par des caractères spéciaux tels que "et <et par un caractère de nouvelle ligne.

Après avoir cherché entre les autres réponses, j'ai trouvé que l' sedon peut accepter les sauts de ligne dans le côté droit de la commande (donc, la other characterschaîne), mais pas dans la gauche.

Existe-t-il un moyen (plus simple que cela ) d'obtenir ce résultat avec sedou grep?

BowPark
la source
travaillez-vous avec un mac? la \ndéclaration ewline que vous faites est pourquoi je demande. les gens demandent rarement s'ils peuvent faire ce s//\n/que vous pouvez faire avec GNU sed, bien que la plupart des autres sedrejetteront cette fuite du côté droit. néanmoins, l' \néchappement fonctionnera à gauche dans n'importe quel POSIX sedet vous pouvez les traduire de manière portable comme y/c/\n/si cela aurait le même effet s/c/\n/get n'est donc pas toujours aussi utile.
mikeserv

Réponses:

3

Trois sedcommandes différentes :

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Ils s'appuient tous les trois sur la s///commande de base d' ubstitution:

s/"[^"]*"\n<[^>]*>/other characters /

Ils essaient également tous de faire attention dans leur traitement de la dernière ligne, car les seds ont tendance à différer sur leur sortie dans les cas de bord. Il s'agit de la signification d' $!une adresse correspondant à chaque ligne qui n'est !pas la $dernière.

Ils utilisent également tous la Ncommande ext pour ajouter la ligne d'entrée suivante à l'espace de motif à la suite d'un \ncaractère ewline. Quiconque travaille seddepuis un certain temps aura appris à se fier au \ncaractère ewline - car la seule façon de l'obtenir est de le mettre explicitement là.

Tous les trois essaient de lire le moins d'entrées possible avant d'agir - sedagissent dès que possible et n'ont pas besoin de lire l'intégralité d'un fichier d'entrée avant de le faire.

Bien qu'ils fassent tout N, ils diffèrent tous les trois dans leurs méthodes de récursivité.

Première commande

La première commande utilise une N;P;Dboucle très simple . Ces trois commandes sont intégrées à tout compatible POSIX sedet se complètent bien.

  • N- comme déjà mentionné, ajoute la Nligne d'entrée ext à l'espace de motif après un \ndélimiteur ewline inséré .
  • P- comme p; il Pimprime l'espace de motif - mais uniquement jusqu'au premier \ncaractère de ligne électronique apparaissant . Et donc, étant donné l'entrée / commande suivante:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Prints seulement un . Cependant, avec ...

  • D- comme d; il Dsupprime l'espace modèle et commence un autre cycle linéaire. Contrairement à d , Dne supprime que jusqu'à la première ligne \nélectronique apparaissant dans l'espace de motif. S'il y a plus dans l'espace de motif suivant le \ncaractère ewline, sedcommence le cycle de ligne suivant avec ce qui reste. Si le ddans l'exemple précédent ont été remplacés par un D, par exemple, sedserait PRint à la fois un et deux .

Cette commande ne se reproduit que pour les lignes qui ne correspondent pas à l' s///instruction d'ubstitution. Étant donné que l' s///ubstitution supprime la ligne \nélectronique ajoutée avec N, il ne reste plus rien lorsqu'il sed Dsupprime l'espace de motif.

Des tests pourraient être effectués pour appliquer le Pet / ou de Dmanière sélective, mais il existe d'autres commandes qui correspondent mieux à cette stratégie. Parce que la récursivité est implémentée pour gérer des lignes consécutives qui ne correspondent qu'à une partie de la règle de remplacement, les séquences consécutives de lignes correspondant aux deux extrémités de l' s///ubstitution ne fonctionnent pas bien .:

Compte tenu de cette entrée:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... ça imprime ...

first other characters "line"
<second>other characters line and so on

Il gère cependant

first "line"
second "line"
<second>line

...ça va.

Deuxième commande

Cette commande est très similaire à la troisième. Les deux utilisent une étiquette :branch / test (comme cela est également démontré dans la réponse de Joeseph R. ici ) et y reviennent dans certaines conditions.

  • -e :n -e- les sedscripts portables délimiteront une :définition d'étiquette avec une ligne \nélectronique ou une nouvelle -einstruction d'exécution en ligne .
    • :n- définit une étiquette nommée n. Cela peut être retourné à tout moment avec bnou tn.
  • tn- la tcommande est retourne à une étiquette spécifiée (ou, si aucune n'est fournie, quitte le script pour le cycle de ligne en cours) si toute s///substitution depuis que l'étiquette a été définie ou depuis qu'elle a été appelée pour la dernière fois tests a réussi.

Dans cette commande, la récursivité se produit pour les lignes correspondantes. Si sedle modèle est remplacé avec succès par d' autres caractères , sedretourne à l' :nétiquette et réessaye. Si s///aucune substitution n'est effectuée, l' sedimpression automatique de l'espace de motif commence le cycle de ligne suivant.

Cela a tendance à mieux gérer les séquences consécutives. Là où le dernier a échoué, cela affiche:

first other characters other characters other characters line and so on

Troisième commande

Comme mentionné, la logique ici est très similaire à la dernière, mais le test est plus explicite.

  • /"$/bn- c'est sedle test. Parce que la bcommande ranch est fonction de cette adresse, sedne fera que brevenir ranch à la :nsuite d' un \newline est ajouté et la structure de l' espace se termine toujours avec un "guillemet.

Il y a aussi peu de choses à faire entre Net bque possible - de cette manière, vous sedpouvez très rapidement rassembler exactement autant de données que nécessaire pour vous assurer que la ligne suivante ne correspond pas à votre règle. L' s///ubstitution diffère ici en ce qu'elle utilise le gdrapeau lobal - et donc elle fera tous les remplacements nécessaires à la fois. Pour une entrée identique, cette commande est identique à la dernière.

mikeserv
la source
Désolé pour la question banale, mais quelle est la signification de DATAet comment recevez-vous la saisie de texte?
BowPark
@BowPark - Dans cet exemple, il <<\DATA\ntext input\nDATA\nest incrusté , mais ce n'est que du texte remis sedpar le shell dans un document ici . Cela fonctionnerait aussi bien comme sed 'script' filenameou process that writes to stdout | sed 'script'. Est ce que ça aide?
mikeserv
Oui, merci! Pourquoi sans Dchaque ligne modifiée est double? (Vous l'avez utilisé car il est nécessaire; peut-être que je ne sais pas sedtrès bien)
BowPark
1
@BowPark - vous obtenez des doublons en omettant le Dcar Dsinon vous Dsupprimez de la sortie ce que vous voyez maintenant doublé. Je viens de faire un montage - et je pourrai peut-être en parler aussi très bientôt.
mikeserv
1
@BowPark - ok, je l'ai mis à jour et fourni des options. Cela pourrait être un peu plus facile à lire / à comprendre maintenant. J'ai également abordé explicitement la Dchose.
mikeserv
7

Eh bien, je peux penser à quelques moyens simples, mais aucun n'implique grep(qui ne fait pas de substitution de toute façon) ou sed.

  1. Perl

    Pour remplacer chaque occurrence de "line"\n<second>avec other characters, utilisez:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Ou, pour traiter plusieurs occurrences consécutives "line"\n<second>comme une seule et les remplacer toutes par une seule other characters, utilisez:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Exemple:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    Le -00Perl de lire le fichier en mode « point » , qui signifie que « lignes » sont définies par la \n\nplace de \n, pour l' essentiel, chaque point est considéré comme une ligne. La substitution correspond donc à travers une nouvelle ligne.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    La même idée de base, nous avons défini le séparateur d'enregistrement ( RS) pour \n\nslurper le fichier entier, puis le séparateur d'enregistrement de sortie sur rien (sinon une nouvelle ligne supplémentaire est imprimée), puis utilisons la sub()fonction pour effectuer le remplacement.

terdon
la source
2
@mikeserv? Laquelle? Le second est censé le faire, le PO a déclaré vouloir «remplacer une ou plusieurs occurrences de», donc manger le paragraphe pourrait bien être ce à quoi il s'attend.
terdon
très bon point. J'imagine que je me suis concentré davantage sur et obtenir à chaque fois , mais je suppose que ce n'est pas clair si cela devrait être un remplacement par occurrence ou un remplacement par séquence d'occurrences ... @BowPark?
mikeserv
Il faut un remplacement par occurrence.
BowPark
@BowPark OK, alors la première approche perl ou awk devrait fonctionner. Ne vous donnent-ils pas la sortie souhaitée?
terdon
Cela fonctionne, merci, mais la troisième ligne avec awkdevrait l'être print;}' file. Je dois éviter Perl et utiliser de préférence sed, de toute façon vous avez suggéré de bonnes alternatives.
BowPark
6

lire l'intégralité du fichier et effectuer un remplacement global:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last
glenn jackman
la source
Oui. Cela fonctionne, mais que se passe-t-il si j'ai plusieurs occurrences?
BowPark
Huh, c'est vrai. Corrigé
glenn jackman
1
désolé de nitpick à nouveau, mais ${cmds}est spécifique à GNU - la plupart des autres sednécessitent une ligne \nélectronique ou une -epause entre pet }. Vous pouvez éviter les crochets tout à fait - et de manière portable - et même éviter d'insérer un \ncaractère ewline supplémentaire sur la première ligne comme:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv
Je l'ai testé et il ne semble pas portable. Il imprime une nouvelle ligne supplémentaire au début de la sortie, mais le résultat est correct sur GNU.
BowPark
Pour supprimer la nouvelle ligne principale: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- mais cela devient impossible à maintenir.
glenn jackman
3

Voici une variante de la réponse de glenn qui fonctionnera si vous avez plusieurs occurrences consécutives (fonctionne avec GNU seduniquement):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

Le :xest juste une étiquette de branchement. Fondamentalement, ce que cela fait, c'est qu'il vérifie la ligne après substitution et s'il correspond toujours "line", il se ramifie à l' :xétiquette (c'est ce qui bxfait) et ajoute une autre ligne au tampon et commence à la traiter.

Joseph R.
la source
@mikeserv Veuillez être précis sur ce que vous voulez dire. Ça a marché pour moi.
Joseph R.
@mikeserv Je suis désolé, je ne sais vraiment pas de quoi vous parlez. J'ai copié la ligne de code ci-dessus dans mon terminal et cela a fonctionné correctement.
Joseph R.
1
rétracté - cela fonctionne apparemment dans GNU sedqui prend sa gestion d'étiquette non-POSIX assez loin pour accepter un espace comme délimiteur pour la déclaration d'étiquette. Vous devez cependant noter que tout autre sedéchouera là-bas - et échouera pour N. GNU sedrompt les directives POSIX pour imprimer l'espace de motif avant de quitter sur une Nsur la dernière ligne, mais POSIX indique clairement que si une Ncommande est lue sur la dernière ligne, rien ne doit être imprimé.
mikeserv
Si vous modifiez le message pour spécifier GNU, j'inverserai mon vote et supprimerai ces commentaires. En outre, il pourrait être utile de se renseigner sur la vcommande de GNU qui se casse les uns les autres, sedmais est un no-op dans les versions 4 et supérieures de GNU.
mikeserv
1
dans ce cas , je vais offrir un de plus - cela peut être fait comme portably: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeserv