web-dev-qa-db-fra.com

Sed peut-il remplacer les nouveaux caractères de ligne?

Y a-t-il un problème avec sed et le nouveau caractère de ligne?
J'ai un fichier test.txt avec le contenu suivant

aaaaa  
bbbbb  
ccccc  
ddddd  

Ce qui suit ne fonctionne pas:
sed -r -i 's/\n/,/g' test.txt

Je sais que je peux utiliser tr pour cela mais ma question est pourquoi cela ne semble pas possible avec sed.

S'il s'agit d'un effet secondaire du traitement du fichier ligne par ligne, j'aimerais savoir pourquoi cela se produit. Je pense que grep supprime les nouvelles lignes. Sed fait-il de même?

47
Jim

Avec GNU sed et fourni POSIXLY_CORRECT n'est pas dans l'environnement (pour une entrée sur une seule ligne):

sed -i ':a;N;$!ba;s/\n/,/g' test.txt

De https://stackoverflow.com/questions/1251999/sed-how-can-i-replace-a-newline-n :

  1. créer une étiquette via :a
  2. ajouter la ligne actuelle et suivante à l'espace de motif via N
  3. si nous sommes avant la dernière ligne, branchez-vous sur l'étiquette créée $!ba ($! signifie ne pas le faire sur la dernière ligne (car il devrait y avoir une dernière ligne finale)).
  4. enfin, la substitution remplace chaque nouvelle ligne par une virgule sur l'espace de motif (qui est le fichier entier).
55
Anthon

Cela fonctionne avec GNU sed:

sed -z 's/\n/,/g' 

-z est inclus depuis 4.2.2

NB. -z change le délimiteur en caractères nuls (\0). Si votre entrée ne contient aucun caractère nul, l'entrée entière est traitée comme une seule ligne. Cela peut venir avec ses limitations .

Pour éviter de remplacer la nouvelle ligne de la dernière ligne, vous pouvez la modifier à nouveau:

sed -z 's/\n/,/g;s/,$/\n/'

(Ce qui est à nouveau la syntaxe GNU sed, mais cela n'a pas d'importance car le tout est GNU uniquement))

26
Hielke Walinga

sed supprime toujours la fin de ligne \n juste avant de remplir l'espace de motif, puis en ajoute une avant d'écrire les résultats de son script. Une ligne électronique \n Peut être utilisée dans l'espace de motifs de différentes manières - mais jamais si elle n'est pas le résultat d'une modification. Ceci est important - les lignes électroniques de \n Dans l'espace modèle de sed reflètent toujours un changement et ne se produisent jamais dans le flux d'entrée. \n Les lignes électroniques sont le seul délimiteur sur lequel un sedder peut compter avec une entrée inconnue.

Si vous souhaitez remplacer tous les ewlines \n Par des virgules et que votre fichier n'est pas très volumineux, vous pouvez faire:

sed 'H;1h;$!d;x;y/\n/,/'

Cela ajoute chaque ligne d'entrée à hold espace - sauf la première, qui remplace à la place hold espace - à la suite d'un caractère de ligne électronique \n. Ensuite, deletes chaque ligne et non le $! Dernier de la sortie. Sur la dernière ligne, Hold et les espaces de motif sont e xchangé et tous les caractères de ligne $ \n Sont y/// Traduits en virgules.

Pour les fichiers volumineux, ce genre de chose est susceptible de causer des problèmes - le tampon de sed sur les limites de ligne, qui peut facilement être débordé avec des actions de ce type.

9
mikeserv

Depuis le site Web d'Oracle:

L'utilitaire sed fonctionne en lisant séquentiellement un fichier, ligne par ligne, dans la mémoire. Il effectue ensuite toutes les actions spécifiées pour la ligne et remet la ligne en mémoire pour la transférer vers le terminal avec les modifications demandées. Une fois que toutes les actions ont été effectuées sur cette seule ligne, il lit la ligne suivante du fichier et répète le processus jusqu'à ce qu'il soit terminé avec le fichier.

Fondamentalement, cela signifie que parce que sed lit ligne par ligne, le caractère de nouvelle ligne n'est pas mis en correspondance.

La solution de https://stackoverflow.com/questions/1251999/sed-how-can-i-replace-a-newline-n est:

sed ':a;N;$!ba;s/\n/,/g'

ou, dans une version portable (sans ; concaténation après les étiquettes de marque de saut)

sed -e ':a' -e 'N;$!ba' -e 's/\n/,/g'

Une explication sur la façon dont cela fonctionne est fournie sur cette page.

8
user204992

Il y a en fait deux questions sur votre message:

Sed peut-il remplacer les nouveaux caractères de ligne?

Oui. Absolument oui. Tout sed pourrait faire:

s/\n/,/g

ou

y/\n/,/

Cela transformera toute nouvelle ligne (qui est entrée dans l'espace de motif) en virgules.

Y a-t-il un problème avec sed et le nouveau caractère de ligne?

Oui, il y a plusieurs problèmes avec le caractère de nouvelle ligne dans sed:

  • Par défaut, sed place dans l'espace de motif une ligne valide . Certains seds ont des limites sur la longueur d'une ligne et sur l'acceptation d'octets NUL. Une ligne se termine sur une nouvelle ligne. Ainsi, dès qu'une nouvelle ligne est trouvée sur l'entrée, l'entrée est divisée, puis sed supprime la nouvelle ligne et place ce qui reste dans l'espace de motif . Ainsi, la plupart du temps, aucune nouvelle ligne ne pénètre dans l'espace de motif.
  • Ce n'est que par une modification de l'espace de motif qu'une nouvelle ligne est ajoutée/insérée/modifiée dans.
  • Presque toujours, une nouvelle ligne est ajoutée à chaque sortie consécutive de sed.
  • Le GNU sed est capable d'éviter d'imprimer une nouvelle ligne de fin si la dernière ligne de l'entrée ne contient pas la nouvelle ligne.
  • Seul GNU sed est capable d'utiliser un autre délimiteur au lieu de la nouvelle ligne (à savoir les octets NUL avec l'option -z).

Tous les points ci-dessus rendent difficile la "conversion de nouvelles lignes" en quelque chose.
Et, si les sauts de ligne sont remplacés par un autre caractère de texte, sed doit contenir le fichier texte entier en mémoire (quel que soit le processus utilisé pour y arriver).

Voici quelques solutions qui capturent l'intégralité du fichier en mémoire dans sed:

sed 'H;1h;$!d;x;y/\n/,/'   file      # most seds. [1]
sed ':a;N;$!ba;s/\n/,/g'   file      # GNU sed.   
sed -z 's/\n/,/g;s/,$/\n/' file      # GNU sed.

Voici quelques solutions rapides qui n'utilisent pas beaucoup de mémoire:

tr '\n' ',' file ; echo
awk '{printf("%s%s",NR==1?"":",",$0)}END{print ""}' file

1À partir des solutions sed: pour chaque ligne, H ajoute la ligne à l'espace d'attente (sauf que la première ligne remplace complètement l'espace d'attente (évitez une nouvelle ligne)), puis l'espace de motif est effacé par $!d (sauf sur la dernière ligne). Sur cette dernière ligne, qui n'a pas été effacée, le reste des commandes est exécuté. Tout d'abord, récupérez toutes les lignes capturées dans l'espace d'attente avec x, puis remplacez toutes les nouvelles lignes par une virgule avec y/\n/,/.

2
Isaac

Alternativement, vous pouvez utiliser une syntaxe légèrement plus simple:

sed ':a;N;s/\n/,/g;ba'

... juste changer l'ordre des séquences.

2
Rodec

Il y a une très belle magie sed ici. Et quelques bons points soulevés au sujet du débordement de l'espace de motif. J'adore utiliser sed même quand ce n'est pas le moyen le plus simple, car il est si compact et puissant. Cependant, il a ses limites, et pour de grandes quantités de données, l'espace de modèle devrait être mahoosif.

GNU dit ceci:

Pour ceux qui souhaitent écrire des scripts sed portables, sachez que certaines implémentations sont connues pour limiter les longueurs de ligne (pour le modèle et les espaces d'attente) à pas plus de 4000 octets. La norme posix spécifie que les implémentations sed conformes doivent prendre en charge des longueurs de ligne d'au moins 8192 octets. GNU sed n'a pas de limite intégrée sur la longueur des lignes; tant qu'il peut malloc () plus de mémoire (virtuelle), vous pouvez alimenter ou construire des lignes aussi longtemps que vous le souhaitez.
Cependant, la récursivité est utilisée pour gérer les sous-modèles et la répétition indéfinie. Cela signifie que l'espace de pile disponible peut limiter la taille du tampon qui peut être traité par certains modèles.

Je n'ai pas grand-chose à ajouter, mais je voudrais vous diriger vers mon guide de référence pour sed . C'est excellent. http://www.grymoire.com/Unix/Sed.html

et voici ma solution:

for i in $(cat test.txt); do echo -n $i','; done; echo '' >> somewhere

ça marche

1
xeuari

Supposons que vous souhaitiez remplacer les sauts de ligne par \n. Je voulais le faire, alors voici ce que j'ai fait:

(echo foo; echo bar; echo baz) | sed -r '$!s/$/\\n/' | tr -d '\n' 
# Output: foo\nbar\nbaz

Voici ce qu'il fait: pour toutes les lignes sauf la dernière, ajoutez \n. Ensuite, supprimez les sauts de ligne avec tr.

0
Camilo Martin