Comment tronquer le fichier au nombre maximum de caractères (pas d'octets)


13

Comment tronquer un fichier texte (encodé UTF-8) en un nombre donné de caractères? Je ne me soucie pas de la longueur des lignes et la coupe peut être au milieu du mot.

  • cut semble fonctionner sur les lignes, mais je veux un fichier entier.
  • head -c utilise des octets, pas des caractères.

Notez que l'implémentation GNU de cutstill ne prend pas en charge les caractères multi-octets. Si c'était le cas, vous pourriez le faire cut -zc-1234 | tr -d '\0'.
Stéphane Chazelas

Comment voulez-vous gérer les emojis? Certains sont plus d'un personnage ... stackoverflow.com/questions/51502486/…
phuzi

2
Qu'est-ce qu'un personnage? certains symboles utilisent plusieurs points de code,
Jasen

Réponses:


14

Certains systèmes ont une truncatecommande qui tronque les fichiers en un certain nombre d' octets (pas de caractères).

Je n'en connais aucun qui soit tronqué à un certain nombre de caractères, bien que vous puissiez recourir à celui perlqui est installé par défaut sur la plupart des systèmes:

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • Avec -Mopen=locale, nous utilisons la notion locale de ce que sont les caractères (donc dans les locales utilisant le jeu de caractères UTF-8, il s'agit de caractères encodés UTF-8). Remplacez par -CSsi vous voulez que les E / S soient décodées / encodées en UTF-8 quel que soit le jeu de caractères des paramètres régionaux.

  • $/ = \1234: nous définissons le séparateur d'enregistrement sur une référence à un entier qui est un moyen de spécifier des enregistrements de longueur fixe (en nombre de caractères ).

  • puis à la lecture du premier enregistrement, nous tronquons stdin en place (donc à la fin du premier enregistrement) et sortons.

GNU sed

Avec GNU sed, vous pourriez faire (en supposant que le fichier ne contient pas de caractères NUL ou des séquences d'octets qui ne forment pas des caractères valides - les deux devraient être vrais pour les fichiers texte):

sed -Ez -i -- 's/^(.{1234}).*/\1/' "$file"

Mais c'est beaucoup moins efficace, car il lit le fichier dans son intégralité et le stocke intégralement en mémoire, et écrit une nouvelle copie.

GNU awk

Même chose avec GNU awk:

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file" étant un moyen de passer des noms de fichiers arbitraires à gawk
  • RS='^$': mode slurp .

Coques intégrées

Avec ksh93, bashou zsh(avec des shells autres que zsh, en supposant que le contenu ne contient pas d'octets NUL):

content=$(cat < "$file" && echo .) &&
  content=${content%.} &&
  printf %s "${content:0:1234}" > "$file"

Avec zsh:

read -k1234 -u0 s < $file &&
  printf %s $s > $file

Ou:

zmodload zsh/mapfile
mapfile[$file]=${mapfile[$file][1,1234]}

Avec ksh93ou bash(attention c'est faux pour les caractères multi-octets dans plusieurs versions debash ):

IFS= read -rN1234 s < "$file" &&
  printf %s "$s" > "$file"

ksh93peut également tronquer le fichier en place au lieu de le réécrire avec son <>;opérateur de redirection:

IFS= read -rN1234 0<>; "$file"

iconv + tête

Pour imprimer les 1234 premiers caractères, une autre option pourrait être de convertir en un codage avec un nombre fixe d'octets par caractère comme UTF32BE/ UCS-4:

iconv -t UCS-4 < "$file" | head -c "$((1234 * 4))" | iconv -f UCS-4

head -cn'est pas standard, mais assez courant. Un équivalent standard serait dd bs=1 count="$((1234 * 4))"mais serait moins efficace, car il lirait l'entrée et écrirait la sortie un octet à la fois¹. iconvest une commande standard mais les noms d'encodage ne sont pas standardisés, vous pouvez donc trouver des systèmes sansUCS-4

Remarques

Dans tous les cas, bien que la sortie contienne au plus 1234 caractères, elle peut finir par ne pas être du texte valide, car elle se terminerait éventuellement par une ligne non délimitée.

Notez également que même si ces solutions ne couperaient pas le texte au milieu d'un caractère, elles pourraient le casser au milieu d'un graphème , comme un éexprimé sous la forme U + 0065 U + 0301 (un esuivi d'un accent aigu combiné), ou graphèmes syllabes Hangul dans leurs formes décomposées.


¹ et sur l'entrée de tuyau, vous ne pouvez pas utiliser des bsvaleurs autres que 1 de manière fiable, sauf si vous utilisez l' iflag=fullblockextension GNU, comme cela ddpourrait faire de courtes lectures si elle lit le tuyau plus rapidement qu'il ne le iconvremplit


pourrait fairedd bs=1234 count=4
Jasen

2
@Jasen, ce ne serait pas fiable. Voir modifier.
Stéphane Chazelas

Hou la la! ce serait pratique d'avoir à proximité! Je pensais que je connaissais beaucoup de commandes Unix pratiques, mais c'est une liste incroyable d'excellentes options.
Mark Stewart

5

Si vous savez que le fichier texte contient Unicode encodé en UTF-8, vous devez d'abord décoder l'UTF-8 pour obtenir une séquence d'entités de caractères Unicode et les diviser.

Je choisirais Python 3.x pour le travail.

Avec Python 3.x, la fonction open () a un argument mot-clé supplémentaire encoding=pour lire les fichiers texte . La description de la méthode io.TextIOBase.read () semble prometteuse.

Donc, en utilisant Python 3, cela ressemblerait à ceci:

truncated = open('/path/to/file.txt', 'rt', encoding='utf-8').read(1000)

De toute évidence, un véritable outil ajouterait des arguments de ligne de commande, la gestion des erreurs, etc.

Avec Python 2.x, vous pouvez implémenter votre propre objet de type fichier et décoder le fichier d'entrée ligne par ligne.


Ouais, je pourrais faire ça. Mais c'est pour les machines de construction de CI, donc j'aimerais à nouveau utiliser une commande Linux standard.
Pitel

5
Tout ce que "Linux standard" signifie sur votre saveur Linux ...
Michael Ströder

1
En effet, Python, une version de toute façon, est assez standard de nos jours.
muru

J'ai déjà modifié ma réponse avec un extrait pour Python 3 qui peut traiter explicitement les fichiers texte.
Michael Ströder

0

Je voudrais ajouter une autre approche. Probablement pas la meilleure performance, et beaucoup plus longue, mais facile à comprendre:

#!/bin/bash

chars="$1"
ifile="$2"
result=$(cat "$ifile")
rcount=$(echo -n "$result" | wc -m)

while [ $rcount -ne $chars ]; do
        result=${result::-1}
        rcount=$(echo -n "$result" | wc -m)
done

echo "$result"

Invoquez-le avec $ ./scriptname <desired chars> <input file>.

Cela supprime le dernier caractère un par un jusqu'à ce que l'objectif soit atteint, ce qui semble vraiment mauvais en termes de performances, en particulier pour les fichiers plus gros. Je voulais juste présenter cela comme une idée pour montrer plus de possibilités.


Ouais, c'est vraiment horrible pour la performance. Pour un fichier de longueur n, wccompte sur l'ordre de O (n ^ 2) octets totaux pour un point cible à mi-chemin dans le fichier. Il devrait être possible d'effectuer une recherche binaire au lieu d'une recherche linéaire en utilisant une variable que vous augmentez ou diminuez, comme echo -n "${result::-$chop}" | wc -mou quelque chose. (Et pendant que vous y êtes, sécurisez-le même si le contenu du fichier commence par -eou quelque chose, peut-être en utilisant printf). Mais vous ne battrez toujours pas les méthodes qui ne regardent qu'une seule fois chaque caractère saisi, donc cela n'en vaut probablement pas la peine.
Peter Cordes

Vous avez certainement raison, plus d'une réponse technique que d'une réponse pratique. Vous pouvez également l'inverser pour ajouter char par char jusqu'à $resultce qu'il corresponde à la longueur souhaitée, mais si la longueur souhaitée est un nombre élevé, elle est tout aussi inefficace.
confettis

1
Vous pouvez commencer près du bon endroit en commençant par des $desired_charsoctets à l'extrémité inférieure, ou peut-être 4*$desired_charsà l'extrémité supérieure. Mais je pense quand même qu'il vaut mieux utiliser autre chose.
Peter Cordes
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.