Ajoutez des lignes au début et à la fin de l'énorme fichier

23

J'ai le scénario où des lignes doivent être ajoutées au début et à la fin des fichiers énormes.

J'ai essayé comme indiqué ci-dessous.

  • pour la première ligne:

    sed -i '1i\'"$FirstLine" $Filename
  • pour la dernière ligne:

    sed -i '$ a\'"$Lastline" $Filename  

Mais le problème avec cette commande est qu'elle ajoute la première ligne du fichier et traverse le fichier entier. Pour la dernière ligne, il traverse à nouveau l'intégralité du fichier et ajoute une dernière ligne. Depuis son très gros fichier (14 Go), cela prend beaucoup de temps.

Comment puis-je ajouter une ligne au début et une autre à la fin d'un fichier en ne lisant le fichier qu'une seule fois?

UNIXbest
la source

Réponses:

20

sed -iutilise les fichiers temporaires comme détail d'implémentation, ce que vous vivez; cependant, ajouter des données au début d'un flux de données sans écraser le contenu existant nécessite de réécrire le fichier, il n'y a aucun moyen de contourner cela, même en évitant sed -i.

Si la réécriture du fichier n'est pas une option, vous pouvez envisager de le manipuler lors de sa lecture, par exemple:

{ echo some prepended text ; cat file ; } | command

De plus, sed sert à éditer des flux - un fichier n'est pas un flux. Utilisez un programme conçu à cet effet, comme ed ou ex. L' -ioption de sed n'est pas seulement non portable, elle cassera également tous les liens symboliques vers votre fichier, car elle le supprime essentiellement et le recrée, ce qui est inutile.

Vous pouvez le faire dans une seule commande avec edcomme ceci:

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

Notez que selon votre implémentation de ed, il peut utiliser un fichier d'échange, vous obligeant à avoir au moins autant d'espace disponible.

Chris Down
la source
Salut, la commande ed fournie par u fonctionne très bien pour les fichiers volumineux. Mais j'ai 3 fichiers énormes comme Test, Test1, Test 2. J'ai donné la commande comme ed -s Tes * << 'EOF' 0a ajoutez ces lignes au début. $ a ajoutez ces lignes à la fin. w EOF Mais il ne prend que le fichier de test et ajoute les premières / dernières lignes. Comment pouvons-nous apporter des modifications dans la même commande afin qu'elle doive faire ajouter la première et la dernière ligne dans tous les fichiers.
UNIXbest
@UNIXbest - Utilisez une forboucle:for file in Tes*; do [command]; done
Chris Down
Salut, j'ai utilisé la commande ci-dessous pour le fichier en Tes *; faire ed -s Tes * << 'EOF' 0a HEllO HDR. $ a Bonjour TLR. w EOF fait Mais il écrit toujours dans le premier fichier.
UNIXbest
Oui, parce que vous devez utiliser "$file", pas Tes*comme argument ed.
Chris Down
2
@UNIXbest Si votre problème a été résolu par cette réponse, vous devriez envisager de l'accepter.
Joseph R.
9

Notez que si vous voulez éviter d'allouer une copie entière du fichier sur le disque, vous pouvez faire:

sed '
1i\
begin
$a\
end' < file 1<> file

Cela utilise le fait que lorsque son stdin / stdout est un fichier, sed lit et écrit par bloc. Donc ici, c'est OK pour qu'il écrase le fichier qu'il lit tant que la première ligne que vous ajoutez est plus petite que sedla taille de bloc de (devrait être quelque chose comme 4k ou 8k).

Notez cependant que si pour une raison quelconque sedéchoue (tué, plantage de la machine ...), vous vous retrouverez avec le fichier à moitié traité, ce qui signifiera que certaines données de la taille de la première ligne manquent quelque part au milieu.

Notez également qu'à moins que vous ne soyez sedle GNU sed, cela ne fonctionnera pas pour les données binaires (mais puisque vous utilisez -i, vous utilisez GNU sed).

Stéphane Chazelas
la source
ces erreurs pour moi sur Ubuntu 16.04
Csaba Toth
4

Voici quelques choix (qui créeront tous une nouvelle copie du fichier, alors assurez-vous d'avoir suffisamment d'espace pour cela):

  • écho / chat simple

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk etc

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awket ses autres fichiers de lecture ligne par ligne. Le BEGIN{}bloc est exécuté avant la première ligne et le END{}bloc après la dernière ligne. Ainsi, la commande ci-dessus signifie print "first" at the beginning, then print every line in the file and print "last" at the end.

  • Perl

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    C'est essentiellement la même chose que le gawk ci-dessus qui vient d'être écrit en Perl.

terdon
la source
1
Notez que dans tous ces cas, vous aurez besoin d'au moins 14 Go d'espace supplémentaire pour le nouveau fichier.
Chris Down
@ChrisDown bon point, j'ai modifié ma réponse pour que ce soit clair. J'ai supposé que ce n'était pas un problème puisque l'OP utilisait sed -ice qui crée des fichiers temporaires.
terdon
3

Je préfère le plus simple:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

Cela transforme le fichier:

asdf
qwer

au fichier:

foo
asdf
qwer
bar
CommaToast
la source
2

Vous pouvez utiliser Vim en mode Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 sélectionner la première ligne

  2. i insérer du texte et une nouvelle ligne

  3. $ sélectionner la dernière ligne

  4. a ajouter du texte et une nouvelle ligne

  5. x sauver et fermer

Steven Penny
la source
Et si nous voulions faire cela sur plusieurs fichiers?
geoyws
1
@geoyws qui n'est pas vraiment dans la portée de cette question
Steven Penny
êtes-vous sûr que c'est $ a et non% a?
Carlos Robles
2

Il n'y a aucun moyen d'insérer des données au début d'un fichier¹, tout ce que vous pouvez faire est de créer un nouveau fichier, d'écrire les données supplémentaires et d'ajouter les anciennes données. Vous devrez donc réécrire tout le fichier au moins une fois pour insérer la première ligne. Vous pouvez cependant ajouter la dernière ligne sans réécrire le fichier.

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

Alternativement, vous pouvez combiner les deux commandes en une seule exécution de sed.

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -icrée un nouveau fichier de sortie, puis le déplace sur l'ancien fichier. Cela signifie que pendant que sed fonctionne, il existe une deuxième copie du fichier utilisant de l'espace. Vous pouvez éviter cela en écrasant le fichier en place , mais avec des restrictions majeures: la ligne que vous ajoutez doit être plus petite que le tampon de sed, et si votre système plante, vous vous retrouverez avec un fichier endommagé et du contenu perdu dans le milieu, donc je le déconseille fortement.

¹ Linux a un moyen d'insérer des données dans un fichier, mais il ne peut insérer qu'un nombre entier de blocs de système de fichiers, il ne peut pas insérer de chaînes de longueurs arbitraires. Il est utile pour certaines applications, telles que les bases de données et les machines virtuelles, mais il est inutile pour les fichiers texte.

Gilles 'SO- arrête d'être méchant'
la source
Pas vrai. Regardez fallocate()avec FALLOC_FL_INSERT_RANGEdisponible sur XFS et ext4 dans les noyaux modernes (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric
@Eric Cependant, vous ne pouvez insérer que des blocs entiers, pas des longueurs d'octets arbitraires, au moins à partir de Linux 4.15.0 avec ext4. Existe-t-il un système de fichiers qui peut insérer des longueurs d'octets arbitraires?
Gilles 'SO- arrête d'être méchant'
C'est vrai, mais cela ne rend toujours pas votre déclaration correcte. Vous avez écrit: "Il n'y a aucun moyen d'insérer des données au début d'un fichier". Ce n'est toujours pas vrai: il existe un mécanisme pour insérer des extensions au début d'un fichier. Il est livré avec des mises en garde, bien sûr, mais il convient de le mentionner car certains utilisateurs peuvent ne pas se soucier des restrictions de taille de bloc en remplissant des espaces ou des retours chariot.
Eric
0
$ (echo "Some Text" ; cat file1) > file2
Koushik Karmakar
la source
4
Seule la réponse par code n'est pas acceptable, veuillez améliorer votre réponse
Networker
Envisagez d'élargir votre réponse pour inclure une explication de votre suggestion ou des liens vers la documentation qui prend en charge votre solution.
HalosGhost
-1

Les noyaux Linux modernes (supérieurs à 4.1 ou 4.2) prennent en charge l'insertion de données au début d'un fichier via l' fallocate()appel système avec FALLOC_FL_INSERT_RANGEles systèmes de fichiers ext4 et xfs. Il s'agit essentiellement d'une opération de décalage logique: les données sont logiquement déplacées à un décalage plus élevé.

Il existe une contrainte concernant la granularité de la plage que vous souhaitez insérer au début du fichier. Mais pour les fichiers texte, vous pouvez probablement allouer un peu plus que nécessaire (jusqu'à la limite de granularité) et remplir avec des espaces ou des retours chariot, mais cela dépend de votre application

Je ne connais aucun utilitaire Linux facilement disponible qui manipule les extensions de fichiers mais ce n'est pas difficile à écrire: obtenez un descripteur de fichier et appelez fallocate()avec les arguments appropriés. Pour plus de détails, reportez-vous à la page de manuel de l' fallocateappel système: http://man7.org/linux/man-pages/man2/fallocate.2.html

Eric
la source
Un utilitaire n'est pas le problème (en supposant un Linux non intégré): util-linux contient un fallocateutilitaire. Le problème est qu'une granularité de blocs entiers rend cela inutile pour la plupart des fichiers texte. Un autre problème est que l'allocation des plages et les modifications ultérieures ne sont pas atomiques. Donc, cela ne résout pas vraiment le problème ici.
Gilles 'SO- arrête d'être méchant'
La granularité est une mise en garde que j'ai déjà mentionnée et non, cela ne la rend pas inutile, cela dépend de l'application. Où avez-vous vu dans la question que l'atomicité est importante? Je ne vois que le problème des performances. Même si cet appel système semble être atomique: elixir.bootlin.com/linux/latest/source/fs/open.c#L228 et si l'atomicité devient importante (ce n'est pas le cas, mais disons que c'est à des fins d'argument), alors utilisez simplement le verrouillage de fichier. (pointez-moi vers l'endroit dans le code du noyau où l' fallocateatomicité est cassée, je suis curieux)
Eric