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.)

Réponses:


9

Cela devrait fonctionner:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

Le script imprime toujours la ligne précédente au lieu de la ligne actuelle et la dernière ligne est traitée différemment.

Ce qu'il fait plus en détail:

  1. NR>1{print PREV} Imprimer la ligne précédente (sauf la première fois).
  2. {PREV=$0}Stocke la ligne actuelle dans une PREVvariable.
  3. END{printf("%s",$0)} Enfin, imprimez la dernière ligne sans saut de ligne.

Notez également que cela supprimerait au plus une ligne vide à la fin (pas de support pour la suppression "one\ntwo\n\n\n").


15

Vous pouvez utiliser perlsans chomp:

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

Mais pourquoi ne pas utiliser chomplui-même:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"

4

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:

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

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

Merci, @ StéphaneChazelas, corrigé. Pour une raison quelconque, ce commutateur me confond toujours !
terdon

0

J'ai accroché ça à un dépôt github quelque part, mais je ne trouve pas où

supprimer-trailing-blank-lines-sed

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'

0

abstrait

Imprimer des lignes sans nouvelle ligne, ajoutez une nouvelle ligne uniquement s'il y a une autre ligne à imprimer.

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

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:

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.

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

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:

awk 'NR == 1 {printf ("% s", $ 0); next}; {printf ("\ n% s", $ 0)} '

Ou, écrit d'une autre manière:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

Ou:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

Donc:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.