web-dev-qa-db-fra.com

Comment puis-je supprimer une nouvelle ligne s'il s'agit du dernier caractère d'un fichier?

Je souhaite supprimer certains fichiers de la dernière nouvelle ligne s'il s'agit du dernier caractère d'un fichier. od -c m'indique que la commande que j'exécute écrit le fichier avec une nouvelle ligne:

0013600   n   t  >  \n

J'ai essayé quelques astuces avec sed, mais le mieux que je puisse penser n'est pas le truc:

sed -e '$s/\(.*\)\n$/\1/' abc

Des idees pour faire cela?

142
Perl -pe 'chomp if eof' filename >filename2

ou, pour éditer le fichier en place:

Perl -pi -e 'chomp if eof' filename

[Note de l'éditeur: -pi -e était à l'origine -pie, mais, comme l'ont noté plusieurs commentateurs et expliqué par @hvd, ce dernier ne fonctionne pas.]

Cela a été décrit comme un «blasphème Perl» sur le site Web awk que j'ai vu.

Mais, dans un test, cela a fonctionné.

201
pavium

Vous pouvez tirer parti du fait que Shell substitutions de commandes supprime les caractères de fin de ligne :

Formulaire simple qui fonctionne en bash, ksh, zsh:

printf %s "$(< in.txt)" > out.txt

Alternative portable (compatible POSIX) (légèrement moins efficace):

printf %s "$(cat in.txt)" > out.txt

Remarque:

  • Si in.txt se termine par , plusieurs caractères de nouvelle ligne, la substitution de commande supprime tout d'entre eux - merci, @Sparhawk. (Cela ne supprime pas les caractères d'espacement autres que les retours à la ligne.)
  • Comme cette approche lit l'intégralité du fichier d'entrée en mémoire , elle n'est recommandée que pour les fichiers plus petits.
  • printf %s garantit qu'aucune nouvelle ligne n'est ajoutée à la sortie (il s'agit de l'alternative compatible POSIX à la méthode non standard echo -n; voir http://pubs.opengroup.org/onlinepubs/009696799/ utilities/echo.html et https://unix.stackexchange.com/a/65819 )

Un guide pour les autres réponses :

  • Si Perl est disponible, optez pour le réponse acceptée - il est simple et en mémoire -efficient (ne lit pas tout le fichier d'entrée en une fois).

  • Sinon, considérons réponse de ghostdog74 Awk - c'est obscur, mais aussi économe en mémoire ; un équivalent plus lisible (compatible POSIX) est:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • L'impression est retardée d'une ligne afin que la dernière ligne puisse être traitée dans le bloc END, où elle est imprimée sans un \n final, en raison de la définition du séparateur d'enregistrement de sortie (OFS). à une chaîne vide.
  • Si vous voulez une solution détaillée, mais rapide et robuste, que édite réellement sur place (par opposition à pour créer un fichier temporaire qui remplace ensuite le fichier d'origine), considérons le script Perl de jrockway .

53
mklement0

Vous pouvez le faire avec head de GNU coreutils, cela supporte les arguments relatifs à la fin du fichier. Donc, pour ne pas utiliser le dernier octet utilisé:

head -c -1

Pour tester une fin de ligne, vous pouvez utiliser tail et wc. L'exemple suivant enregistre le résultat dans un fichier temporaire et remplace par la suite l'original:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi

Vous pouvez également utiliser sponge à partir de moreutils pour effectuer une édition "sur place":

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

Vous pouvez également créer une fonction générale réutilisable en l'insérant dans votre fichier .bashrc:

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
    local file=$(mktemp)
    cat > $file
    if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
        head -c -1 $file > $file.tmp
        mv $file.tmp $file
    fi
    cat $file
}

Mettre à jour

Comme indiqué par KarlWilbur dans les commentaires et utilisé dans Sorentar answer , truncate --size=-1 peut remplacer head -c-1 et prend en charge la modification sur place.

43
Thor
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Edit 2: 

Voici une version awk (corrigée) qui n'accumule pas un tableau potentiellement énorme:

awk '{if (line) print line; line = $ 0} END {printf $ 0} 'abc

16
Dennis Williamson

rester bouche bée

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
10
ghostdog74

Une méthode très simple pour les fichiers d'une seule ligne, nécessitant un écho GNU de coreutils:

/bin/echo -n $(cat $file)
8
anotheral

Si vous voulez le faire correctement, vous avez besoin de quelque chose comme ceci:

use autodie qw(open sysseek sysread truncate);

my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';

if($buf eq "\n"){
    truncate $fh, $pos - 1;
}

Nous ouvrons le fichier pour lecture et ajout; ouvrir pour ajouter signifie que nous sommes déjà seeked à la fin du fichier. Nous obtenons alors la position numérique de la fin du fichier avec tell. Nous utilisons ce numéro pour rechercher un caractère, puis nous le lisons. S'il s'agit d'une nouvelle ligne, nous tronquons le fichier au caractère précédant cette nouvelle ligne, sinon nous ne faisons rien.

Cela s'exécute en temps constant et en espace constant pour toutes les entrées, et ne nécessite pas davantage d'espace disque.

8
jrockway

Voici une solution agréable et ordonnée de Python. Je n'ai pas essayé d'être concis ici.

Cela modifie le fichier sur place, plutôt que de faire une copie du fichier et de supprimer la nouvelle ligne de la dernière ligne de la copie. Si le fichier est volumineux, cela sera beaucoup plus rapide que la solution Perl choisie comme meilleure réponse.

Il tronque un fichier de deux octets si les deux derniers octets sont CR/LF ou d'un octet si le dernier octet est LF. Il ne tente pas de modifier le fichier si le ou les derniers octets ne sont pas (CR) LF. Il gère les erreurs. Testé en Python 2.6.

Placez ceci dans un fichier appelé "striplast" et chmod +x striplast.

#!/usr/bin/python

# strip newline from last line of a file


import sys

def trunc(filename, new_len):
    try:
        # open with mode "append" so we have permission to modify
        # cannot open with mode "write" because that clobbers the file!
        f = open(filename, "ab")
        f.truncate(new_len)
        f.close()
    except IOError:
        print "cannot write to file:", filename
        sys.exit(2)

# get input argument
if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = "--help"  # wrong number of arguments so print help

if filename == "--help" or filename == "-h" or filename == "/?":
    print "Usage: %s <filename>" % sys.argv[0]
    print "Strips a newline off the last line of a file."
    sys.exit(1)


try:
    # must have mode "b" (binary) to allow f.seek() with negative offset
    f = open(filename, "rb")
except IOError:
    print "file does not exist:", filename
    sys.exit(2)


SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file

end_pos = f.tell()

line = f.read()
f.close()

if line.endswith("\r\n"):
    trunc(filename, end_pos)
Elif line.endswith("\n"):
    trunc(filename, end_pos + 1)

P.S. Dans l’esprit du "golf Perl", voici ma solution Python la plus courte. Il insère tout le fichier de l'entrée standard dans la mémoire, supprime toutes les nouvelles lignes et écrit le résultat dans une sortie standard. Pas aussi concis que le Perl; vous ne pouvez tout simplement pas battre Perl pour des choses aussi difficiles que celle-ci.

Supprimez le "\ n" de l'appel à .rstrip() et tous les espaces blancs seront supprimés à la fin du fichier, y compris plusieurs lignes vides.

Mettez ceci dans "Slurp_and_chomp.py" puis exécutez python Slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))
5
steveha

Encore un autre Perl WTDI:

Perl -i -p0777we's/\n\z//' filename
4
ysth
 $ Perl -e 'local $ /; $ _ = <>; s/\ n $ //; print 'a-text-file.txt 

Voir aussi Faites correspondre n'importe quel caractère (y compris les nouvelles lignes) dans sed .

3
Sinan Ünür
Perl -pi -e 's/\n$// if(eof)' your_file
2
Vijay

En utilisant dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
    printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
    #printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1
2
cpit

En supposant que le type de fichier Unix et que vous voulez que la dernière nouvelle ligne cela fonctionne.

sed -e '${/^$/d}'

Cela ne fonctionnera pas sur plusieurs nouvelles lignes ...

* Ne fonctionne que si la dernière ligne est une ligne vide.

2
LoranceStinson

Encore une autre réponse FTR (et ma préférée!): Écho/Cat la chose que vous voulez dépouiller et capturer la sortie par des backticks La nouvelle ligne finale sera supprimée. Par exemple:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'

# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"

# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline
1
Nicholas Wilson

POSIX SED:

'$ {/ ^ $/d}'

$ - match last line


{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.
1
Oleg Mazko

Une solution rapide utilise l'utilitaire gnu tronqué:

[ -z $(tail -c1 file) ] && truncate -s-1

Le test sera vrai si le fichier a une nouvelle ligne de fin.

La suppression est très rapide, vraiment en place, aucun nouveau fichier n’est nécessaire et la recherche lit également à la fin un octet (tail -c1).

1
sorontar

J'avais un problème similaire, mais je travaillais avec un fichier Windows et je devais conserver ces CRLF - ma solution sur Linux

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked
0
cadrian

Rubis:

Ruby -ne 'print $stdin.eof ? $_.strip : $_'

ou:

Ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
0
peak

La seule fois où j'ai voulu faire cela, c'est pour le code de golf. Je viens de copier mon code du fichier et de le coller dans une instruction echo -n 'content'>file.

0
dlamblin
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
0
ghostdog74

Ceci est une bonne solution si vous avez besoin de travailler avec des redirections/pipes au lieu de lire/sortir à partir ou vers un fichier. Cela fonctionne avec une ou plusieurs lignes. Cela fonctionne qu'il y ait ou non une nouvelle ligne.

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1

# read from a file
sed '$s/$//' myfile.txt | head -c -1

Détails:

  • head -c -1 tronque le dernier caractère de la chaîne, quel que soit son caractère. Donc, si la chaîne ne se termine pas par une nouvelle ligne, vous perdriez un caractère.
  • Donc, pour résoudre ce problème, nous ajoutons une autre commande qui ajoutera un retour à la ligne s'il n'y en a pas: sed '$s/$//'. Le premier $ signifie uniquement appliquer la commande à la dernière ligne. s/$// signifie remplacer "la fin de la ligne" par "rien", ce qui revient à ne rien faire. Mais il a pour effet secondaire d’ajouter un retour à la ligne s'il n’en existe pas.

Remarque: La variable head par défaut de Mac ne prend pas en charge l'option -c. Vous pouvez faire brew install coreutils et utiliser ghead à la place.

0
wisbucky
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Devrait supprimer toute dernière occurrence de\n dans le fichier. Ne fonctionne pas sur un fichier volumineux (en raison de la limitation de la mémoire tampon sed)

0
NeronLeVelu