Comment puis-je supprimer une nouvelle ligne de fin dans bash?
10
Je cherche quelque chose qui se comporte comme Perl chomp. Je recherche une commande qui imprime simplement son entrée, moins le dernier caractère s'il s'agit d'une nouvelle ligne:
$ printf "one\ntwo\n"| COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo"| COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
(La substitution de commandes dans Bash et Zsh supprime toutes les nouvelles lignes de fin, mais je cherche quelque chose qui supprime au plus une nouvelle ligne de fin.)
Si vous voulez un équivalent exact de chomp, la première méthode qui me vient à l'esprit est la solution awk que LatinSuD a déjà publiée . J'ajouterai d'autres méthodes qui n'implémentent pas chompmais implémentent des tâches courantes qui chompsont souvent utilisées pour.
Lorsque vous insérez du texte dans une variable, tous les sauts de ligne à la fin sont supprimés. Donc, toutes ces commandes produisent la même sortie sur une seule ligne:
Si vous souhaitez ajouter du texte à la dernière ligne d'un fichier ou de la sortie d'une commande, cela sedpeut être pratique. Avec GNU sed et la plupart des autres implémentations modernes, cela fonctionne même si l'entrée ne se termine pas par une nouvelle ligne¹; cependant, cela n'ajoutera pas de nouvelle ligne s'il n'y en avait pas déjà une.
sed '$ s/$/ done/'
¹ Cependant, cela ne fonctionne pas avec toutes les implémentations de sed: sed est un outil de traitement de texte, et un fichier qui n'est pas vide et ne se termine pas par un caractère de nouvelle ligne n'est pas un fichier texte.
Ce n'est pas exactement équivalent à chomp, car chompseulement supprime au plus une nouvelle ligne de fin.
Flimm
@Flimm Oui, l'équivalent exact le plus évident chompserait la solution awk déjà publiée par LatinSuD. Mais dans de nombreux cas, ce chompn'est qu'un outil pour faire un travail, et je propose des moyens d'effectuer certaines tâches courantes. Permettez-moi de mettre à jour ma réponse pour clarifier cela.
Gilles 'SO- arrête d'être méchant'
1
Une autre perlapproche. Celui-ci lit l'intégralité de l'entrée en mémoire, donc ce n'est peut-être pas une bonne idée pour de grandes quantités de données (utilisez cuonglm ou l' awkapproche pour cela):
$ printf "one\ntwo\n"| perl -0777pe's/\n$//'; echo " done"
one
two done
C'est une solution rapide car il ne doit lire qu'un seul caractère du fichier, puis le supprimer directement ( truncate) sans lire le fichier entier.
Cependant, lorsque vous travaillez avec des données de stdin (un flux), les données doivent être lues en entier. Et, il est "consommé" dès qu'il est lu. Pas de retour en arrière (comme avec tronquer). Pour trouver la fin d'un flux, nous devons lire jusqu'à la fin du flux. À ce stade, il n'y a aucun moyen de revenir sur le flux d'entrée, les données ont déjà été "consommées". Cela signifie que les données doivent être stockées dans une forme de tampon jusqu'à ce que nous correspondions à la fin du flux, puis que nous fassions quelque chose avec les données dans le tampon.
La solution la plus évidente consiste à convertir le flux en fichier et à traiter ce fichier. Mais la question demande une sorte de filtre du flux. Pas sur l'utilisation de fichiers supplémentaires.
variable
La solution naïve serait de capturer la totalité de l'entrée dans une variable:
FilterOne(){ filecontents=$(cat; echo "x");# capture the whole input
filecontents=${filecontents%x};# Remove the "x" added above.
nl=$'\n';# use a variable for newline.
printf '%s'"${filecontents%"$nl"}";# Remove newline (if it exists).}
printf 'one\ntwo'|FilterOne; echo 1done
printf 'one\ntwo\n'|FilterOne; echo 2done
printf 'one\ntwo\n\n'|FilterOne; echo 3done
Mémoire
Il est possible de charger un fichier entier en mémoire avec sed. Dans sed, il est impossible d'éviter la nouvelle ligne de fin sur la dernière ligne. GNU sed peut éviter d'imprimer une nouvelle ligne de fin, mais uniquement si le fichier source le manque déjà. Donc, non, simple sed ne peut pas aider.
Sauf sur GNU awk avec l' -zoption:
sed -z 's/\(.*\)\n$/\1/'
Avec awk (n'importe quel awk), slurpez tout le flux, et printfce sans la nouvelle ligne de fin.
Le chargement d'un fichier entier en mémoire n'est peut-être pas une bonne idée, il peut consommer beaucoup de mémoire.
Deux lignes en mémoire
Dans awk, nous pouvons traiter deux lignes par boucle en stockant la ligne précédente dans une variable et en imprimant la présente:
awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'
Traitement direct
Mais nous pourrions faire mieux.
Si nous imprimons la ligne actuelle sans nouvelle ligne et n'imprimons une nouvelle ligne que lorsqu'une ligne suivante existe, nous traitons une ligne à la fois et la dernière ligne n'aura pas de nouvelle ligne de fin:
chomp
, carchomp
seulement supprime au plus une nouvelle ligne de fin.chomp
serait la solution awk déjà publiée par LatinSuD. Mais dans de nombreux cas, cechomp
n'est qu'un outil pour faire un travail, et je propose des moyens d'effectuer certaines tâches courantes. Permettez-moi de mettre à jour ma réponse pour clarifier cela.Une autre
perl
approche. Celui-ci lit l'intégralité de l'entrée en mémoire, donc ce n'est peut-être pas une bonne idée pour de grandes quantités de données (utilisez cuonglm ou l'awk
approche pour cela):la source
J'ai accroché ça à un dépôt github quelque part, mais je ne trouve pas où
supprimer-trailing-blank-lines-sed
la source
abstrait
Imprimer des lignes sans nouvelle ligne, ajoutez une nouvelle ligne uniquement s'il y a une autre ligne à imprimer.
Autres solutions
Si nous travaillions avec un fichier, nous pouvons simplement en tronquer un caractère (s'il se termine sur une nouvelle ligne):
removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || tronquer -s-1 "$ 1"; }
C'est une solution rapide car il ne doit lire qu'un seul caractère du fichier, puis le supprimer directement (
truncate
) sans lire le fichier entier.Cependant, lorsque vous travaillez avec des données de stdin (un flux), les données doivent être lues en entier. Et, il est "consommé" dès qu'il est lu. Pas de retour en arrière (comme avec tronquer). Pour trouver la fin d'un flux, nous devons lire jusqu'à la fin du flux. À ce stade, il n'y a aucun moyen de revenir sur le flux d'entrée, les données ont déjà été "consommées". Cela signifie que les données doivent être stockées dans une forme de tampon jusqu'à ce que nous correspondions à la fin du flux, puis que nous fassions quelque chose avec les données dans le tampon.
La solution la plus évidente consiste à convertir le flux en fichier et à traiter ce fichier. Mais la question demande une sorte de filtre du flux. Pas sur l'utilisation de fichiers supplémentaires.
variable
La solution naïve serait de capturer la totalité de l'entrée dans une variable:
Mémoire
Il est possible de charger un fichier entier en mémoire avec sed. Dans sed, il est impossible d'éviter la nouvelle ligne de fin sur la dernière ligne. GNU sed peut éviter d'imprimer une nouvelle ligne de fin, mais uniquement si le fichier source le manque déjà. Donc, non, simple sed ne peut pas aider.
Sauf sur GNU awk avec l'
-z
option:Avec awk (n'importe quel awk), slurpez tout le flux, et
printf
ce sans la nouvelle ligne de fin.Le chargement d'un fichier entier en mémoire n'est peut-être pas une bonne idée, il peut consommer beaucoup de mémoire.
Deux lignes en mémoire
Dans awk, nous pouvons traiter deux lignes par boucle en stockant la ligne précédente dans une variable et en imprimant la présente:
Traitement direct
Mais nous pourrions faire mieux.
Si nous imprimons la ligne actuelle sans nouvelle ligne et n'imprimons une nouvelle ligne que lorsqu'une ligne suivante existe, nous traitons une ligne à la fois et la dernière ligne n'aura pas de nouvelle ligne de fin:
awk 'NR == 1 {printf ("% s", $ 0); next}; {printf ("\ n% s", $ 0)} '
Ou, écrit d'une autre manière:
Ou:
Donc:
la source