Comment tronquer un fichier par lignes?

13

J'ai un grand nombre de fichiers, dont certains sont très longs. Je voudrais les tronquer à une certaine taille s'ils sont plus grands en supprimant la fin du fichier. Mais je veux seulement supprimer des lignes entières. Comment puis-je faire ceci? Cela ressemble au genre de chose qui serait gérée par la chaîne d'outils Linux, mais je ne connais pas la bonne commande.

Par exemple, disons que j'ai un fichier de 120 000 octets avec des lignes de 300 octets et que j'essaie de le tronquer à 10 000 octets. Les 33 premières lignes doivent rester (9900 octets) et les autres doivent être coupées. Je ne veux pas couper à 10 000 octets exactement, car cela laisserait une ligne partielle.

Bien sûr, les fichiers sont de longueurs différentes et les lignes ne sont pas toutes de la même longueur.

Idéalement, les fichiers résultants seraient rendus légèrement plus courts plutôt que légèrement plus longs (si le point d'arrêt est sur une longue ligne) mais ce n'est pas trop important, cela pourrait être un peu plus long si cela était plus facile. Je voudrais que les modifications soient apportées directement aux fichiers (enfin, peut-être le nouveau fichier copié ailleurs, l'original supprimé et le nouveau fichier déplacé, mais c'est la même chose du POV de l'utilisateur). Une solution qui redirige les données vers un tas d'endroits puis revient invite à la possibilité de corrompre le fichier et j'aimerais éviter cela ...

Charles
la source
Supprimé ma réponse… Je suppose que la taille du fichier en octets n'était pas trop claire, désolé. Peut-être pourriez-vous modifier votre question et clarifier cette partie (par exemple avec un exemple)?
slhck
@slhck: Désolé de vous voir perdre votre représentant juste parce que je n'étais pas clair ... laissez-moi voir si je peux résoudre ce problème.
Charles
Pas de soucis, j'aurais juste dû demander, désolé :)
slhck

Réponses:

1

La sed/ wccomplexité peut être évitée dans les réponses précédentes si elle awkest utilisée. En utilisant l'exemple fourni par OP (montrant les lignes complètes avant 10000 octets):

awk '{i += (length() + 1); if (i <= 10000) print $ALL}' myfile.txt

Affiche également la ligne complète contenant 10000e octet si cet octet n'est pas en fin de ligne:

awk '{i += (length() + 1); print $ALL; if (i >= 10000) exit}' myfile.txt

La réponse ci-dessus suppose:

  1. Les fichiers texte sont de terminaison de ligne Unix ( \n). Pour les fichiers texte Dos / Windows ( \r\n), passez length() + 1àlength() + 2
  2. Le fichier texte contient uniquement un caractère sur un octet. S'il y a un caractère multi-octets (comme dans un environnement unicode), définissez l'environnement LC_CTYPE=Cpour forcer l'interprétation au niveau des octets.
Abel Cheung
la source
14

L' sedapproche est bonne, mais faire une boucle sur toutes les lignes ne l'est pas. Si vous savez combien de lignes vous souhaitez conserver (pour avoir un exemple, j'utilise 99 ici), vous pouvez le faire comme ceci:

sed -i '100,$ d' myfile.txt

Explication: sedest un processeur d'expression régulière. Avec l'option -idonnée, il traite un fichier directement ("en ligne") - au lieu de simplement le lire et écrire les résultats sur la sortie standard. 100,$signifie simplement "de la ligne 100 à la fin du fichier" - et est suivi de la commande d, que vous avez probablement deviné correctement pour "supprimer". Donc, en bref, la commande signifie: "Supprimer toutes les lignes de la ligne 100 jusqu'à la fin du fichier de monfichier.txt". 100 est la première ligne à supprimer, car vous souhaitez conserver 99 lignes.

Edit: Si, d'autre part, il y a des fichiers journaux où vous souhaitez conserver par exemple les 100 dernières lignes:

[ $(wc -l myfile.txt) -gt 100 ] && sed -i "1,$(($(wc -l myfile.txt|awk '{print $1}') - 100)) d" myfile.txt

Qu'est-ce qui se passe ici:

  • [ $(wc -l myfile.txt) -gt 100 ]: procédez comme suit uniquement si le fichier contient plus de 100 lignes
  • $((100 - $(wc -l myfile.txt|awk '{print $1}'))): calculer le nombre de lignes à supprimer (c'est-à-dire toutes les lignes du fichier sauf les (dernières) 100 à conserver)
  • 1, $((..)) d: supprime toutes les lignes de la première à la ligne calculée

EDIT: comme la question vient d'être modifiée pour donner plus de détails, je vais également inclure cette information supplémentaire avec ma réponse. Les faits ajoutés sont:

  • une taille spécifique doit rester avec le fichier (10 000 octets)
  • chaque ligne a une taille spécifique en octets (300 octets dans l'exemple)

A partir de ces données, il est possible de calculer le nombre de lignes à conserver comme "/", ce qui avec l'exemple signifierait 33 lignes. Le terme shell pour le calcul: $((size_to_remain / linesize))(au moins sous Linux utilisant Bash, le résultat est un entier). La commande ajustée se lirait maintenant:

# keep the start of the file (OPs question)
sed -i '34,$ d' myfile.txt
# keep the end of the file (my second example)
[ $(wc -l myfile.txt) -gt 33 ] && sed -i "1,33 d" myfile.txt

Comme les tailles sont connues à l'avance, il n'y a plus besoin de calcul intégré à la sedcommande. Mais pour plus de flexibilité, à l'intérieur de certains scripts shell, on peut utiliser des variables.

Pour un traitement conditionnel basé sur la taille du fichier, on peut utiliser la structure "test" suivante:

[ "$(ls -lk $file | awk ' {print $5}')" -gt 100 ] &&

ce qui signifie: "si la taille de $filedépasse 100 Ko, faites ..." ( ls -lkrépertorie la taille du fichier en Ko à la position 5, donc awkest utilisé pour extraire exactement cela).

Izzy
la source
L'OP veut couper le fichier en fonction d'une certaine taille d'octets - pas seulement de la longueur en termes de lignes. J'ai supprimé ma réponse impliquant head -n.
slhck
@slhck Merci pour la notification. Oui, le PO vient de modifier sa question pour clarifier l'intention. Comme il a les moyens de calculer le nombre d'octets de chaque ligne, ma réponse reste valable en principe - car il peut calculer le nombre de lignes restantes, puis utiliser mon approche pour gérer les fichiers. Je ferai peut-être une brève remarque à ce sujet dans ma réponse.
Izzy
Non - les tailles ne sont pas connues à l'avance. C'était un exemple. Chaque fichier aura une taille différente et les lignes sont de longueur irrégulière. Certains fichiers n'ont pas du tout besoin d'être tronqués.
Charles
Oh, encore une fois ... Eh bien, certaines choses sont difficiles à expliquer clairement (trop de facettes). Quant aux fichiers qui n'ont pas besoin d'être tronqués, cela est probablement basé sur la taille du fichier? Cela peut être couvert. Mais s'il n'y a même pas de taille de ligne moyenne connue, cette partie devient difficile - je ne peux pas penser à une solution facile (sans trop de surcharge) pour le moment.
Izzy
Tout ce que je peux trouver actuellement impliquerait, par exemple, d'obtenir les n premières lignes, de calculer une longueur moyenne en fonction de celles-ci et d'utiliser cette valeur. Cela vous aiderait-il?
Izzy
0

A défaut de trouver une commande pour ce faire, j'ai écrit un script rapide (non testé):

#!/bin/sh

# Usage: $0 glob.* 25000
# where glob.* is a wildcard pattern and 25000 is the maximum number of bytes.

limit=20000
tmp=/tmp/trim
[[ "$2" == +([0-9]) ]] || limit=$2
limit=`expr $len + 1`
for file in $1;
do
    [[ `wc -c $file` -lt $limit ]] && continue
    head -c $file > $tmp
    sed '$d' $tmp
    $tmp > $file
done
Charles
la source
-1

Vous pouvez utiliser la commande linux sed pour supprimer des lignes d'un fichier. La commande suivante supprime la dernière ligne de filename.txt:

sed '$d' filename.txt

Avec awk ou find, vous pouvez rechercher un motif correspondant à votre commande sed. D'abord, vous recherchez avec awk ou recherchez les fichiers que vous souhaitez raccourcir, puis vous pouvez supprimer les lignes avec sed.

kockiren
la source
-1

J'ai fait quelque chose de similaire avec la queue. Pour ne conserver que les 10 000 dernières lignes dans ce cas:

TMP=$(tail -n 10000 /path/to/some/file 2>/dev/null) && echo "${TMP}" > /path/to/some/file
Bill M
la source