J'ai un fichier assez volumineux (35 Go), et j'aimerais filtrer ce fichier in situ (c'est-à-dire que je n'ai pas assez d'espace disque pour un autre fichier), en particulier je veux grep et ignorer certains modèles - faire cela sans utiliser un autre fichier?
Disons que je veux filtrer toutes les lignes contenant foo:
par exemple ...
Réponses:
Au niveau des appels système, cela devrait être possible. Un programme peut ouvrir votre fichier cible en écriture sans le tronquer et commencer à écrire ce qu'il lit à partir de stdin. Lors de la lecture de EOF, le fichier de sortie peut être tronqué.
Puisque vous filtrez les lignes depuis l'entrée, la position d'écriture du fichier de sortie doit toujours être inférieure à la position de lecture. Cela signifie que vous ne devriez pas corrompre votre entrée avec la nouvelle sortie.
Cependant, trouver un programme qui fait cela est le problème.
dd(1)
a l'optionconv=notrunc
qui ne tronque pas le fichier de sortie à l'ouverture, mais ne tronque pas non plus à la fin, laissant le contenu du fichier d'origine après le contenu de grep (avec une commande similairegrep pattern bigfile | dd of=bigfile conv=notrunc
)Comme il est très simple du point de vue des appels système, j’ai écrit un petit programme et l’ai testé sur un petit système de fichiers en boucle (1 Mo). Il a fait ce que vous vouliez, mais vous voulez vraiment le tester d'abord avec d'autres fichiers. Il y aura toujours des risques à écraser un fichier.
écraser.c
Vous l'utiliseriez comme:
Je poste surtout ceci pour que les autres puissent commenter avant de l'essayer. Peut-être que quelqu'un d'autre connait un programme qui fait quelque chose de similaire qui est plus testé.
la source
grep
que ne produira pas plus de données qu'il n'en lit, la position d'écriture doit toujours se situer derrière la position de lecture. Même si vous écrivez au même rythme que la lecture, tout ira bien. Essayez rot13 avec ceci au lieu de grep, puis de nouveau. md5sum l’avant et l’après et vous verrez que c’est la même chose.dd
, mais c'est lourd.Vous pouvez utiliser
sed
pour éditer des fichiers sur place (mais cela crée un fichier temporaire intermédiaire):Pour supprimer toutes les lignes contenant
foo
:Pour garder toutes les lignes contenant
foo
:la source
$HOME
serez en écriture, mais/tmp
en lecture seule (par défaut). Par exemple, si vous avez Ubuntu et que vous avez démarré dans la console de récupération, c'est généralement le cas. En outre, l'opérateur here-document<<<
n'y travaillera pas non plus, car il nécessite/tmp
d'être r / w car il y écrit également un fichier temporaire. (voir cette question avec unestrace
sortie)Je suppose que votre commande de filtre est ce que j'appellerai un filtre de réduction de préfixe , qui a la propriété que l'octet N dans la sortie n'est jamais écrit avant d'avoir lu au moins N octets d'entrée.
grep
a cette propriété (tant qu’il ne fait que filtrer et ne pas faire d’autres choses comme ajouter des numéros de ligne pour les correspondances). Avec un tel filtre, vous pouvez écraser l’entrée au fur et à mesure. Bien sûr, vous devez vous assurer de ne commettre aucune erreur, car la partie écrasée au début du fichier sera perdue à jamais.La plupart des outils Unix permettent seulement d'ajouter ou de tronquer un fichier, sans possibilité de l'écraser. La seule exception dans la boîte à outils standard est celle à
dd
laquelle on peut dire de ne pas tronquer son fichier de sortie. Le plan consiste donc à filtrer la commande endd conv=notrunc
. Cela ne change pas la taille du fichier, nous allons donc aussi saisir la longueur du nouveau contenu et tronquer le fichier à cette longueur (à nouveau avecdd
). Notez que cette tâche est intrinsèquement non robuste - si une erreur se produit, vous êtes seul.Vous pouvez écrire en gros équivalent Perl. Voici une mise en œuvre rapide qui n'essaie pas d'être efficace. Bien sûr, vous pouvez également effectuer votre filtrage initial directement dans cette langue.
la source
Avec n'importe quel shell Bourne-like:
Pour une raison quelconque, il semble que les gens ont tendance à oublier cet opérateur de redirection lecture + écriture de 40 ans¹ et standard .
Nous ouvrons
bigfile
en lecture en mode écriture + et (ce qui est le plus important ici) sans troncature surstdout
toutbigfile
est ouvert (séparément) surcat
« sstdin
. Aprèsgrep
a été terminé, et s'il a supprimé certaines lignes et qu'ilstdout
pointe maintenant quelque part à l'intérieurbigfile
, nous devons nous débarrasser de ce qui est au-delà de ce point. D'où laperl
commande qui tronque le fichier (truncate STDOUT
) à la position actuelle (telle que retournée partell STDOUT
).(
cat
c'est pour GNUgrep
qui se plaint sinon si stdin et stdout pointent sur le même fichier).¹ Eh bien, bien qu’il
<>
soit dans le shell Bourne depuis le début à la fin des années 70, il était initialement non documenté et n’a pas été correctement mis en œuvre . Ce n’était pas dans l’implémentation initialeash
de 1989 et, bien qu’il s’agisse d’unsh
opérateur de redirection POSIX (depuis le début des années 90, POSIXsh
étant basé surksh88
qui l’a toujours eu), il n’a pas été ajouté à FreeBSD,sh
par exemple, avant 2000, donc de manière transférable . vieux est probablement plus précis. Notez également que le descripteur de fichier par défaut, lorsqu'il n'est pas spécifié, se trouve<>
dans tous les shells, sauf qu'ilksh93
est passé de 0 à 1 dans ksh93t + en 2010 (rupture de la compatibilité avec les versions antérieures et de la conformité POSIX).la source
perl -e 'truncate STDOUT, tell STDOUT'
? Cela fonctionne pour moi sans inclure cela. Tout moyen de réaliser la même chose sans utiliser Perl?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
ce qui est un indice.Même s’il s’agit d’une question ancienne, il me semble que c’est une question éternelle et qu’une solution plus générale et plus claire que celle suggérée jusqu’à présent est disponible. Le crédit est dû: Je ne suis pas sûr que je l'aurais trouvé sans tenir compte de la mention de Stéphane Chazelas sur l'
<>
opérateur de mise à jour.L’ouverture d’un fichier à mettre à jour dans un shell Bourne est d’une utilité limitée. Le shell ne vous donne aucun moyen de rechercher sur un fichier, ni de définir sa nouvelle longueur (si elle est plus courte que l’ancien). Mais on y résout facilement, si facilement que je suis surpris que ce ne soit pas un utilitaire standard de Windows
/usr/bin
.Cela marche:
De même que cela (astuce à Stéphane):
(J'utilise GNU grep. Peut-être que quelque chose a changé depuis qu'il a écrit sa réponse.)
Sauf que vous n'avez pas / usr / bin / ftruncate . Pour une douzaine de lignes de C, vous pouvez, voir ci-dessous. Cet utilitaire ftruncate tronque un descripteur de fichier arbitraire à une longueur arbitraire, par défaut à la sortie standard et à la position actuelle.
La commande ci-dessus (1er exemple)
T
pour la mise à jour. Comme avec open (2), ouvrir le fichier de cette façon positionne le décalage actuel à 0.T
normalement et le shell redirige sa sortieT
via le descripteur 4.Le sous-shell se ferme alors, fermant le descripteur 4. Voici ftruncate :
NB, ftruncate (2) est non-portable lorsqu'il est utilisé de cette façon. Pour une généralité absolue, lisez le dernier octet écrit, rouvrez le fichier O_WRONLY, recherchez, écrivez l'octet et fermez-le.
Étant donné que la question a 5 ans, je vais dire que cette solution est non évidente. Il profite d’ exec pour ouvrir un nouveau descripteur et l’
<>
opérateur, qui sont tous les deux arcanes. Je ne peux pas penser à un utilitaire standard qui manipule un inode par descripteur de fichier. (La syntaxe pourrait êtreftruncate >&4
, mais je ne suis pas sûr que ce soit une amélioration.) Elle est considérablement plus courte que la réponse compétente et exploratoire de camh. C'est un peu plus clair que celui de Stéphane, OMI, à moins que vous n'aimiez plus Perl que moi. J'espère que quelqu'un le trouvera utile.Une autre façon de faire la même chose serait une version exécutable de lseek (2) qui rapporte le décalage actuel; la sortie pourrait être utilisée pour / usr / bin / truncate , fourni par certains Linuxi.
la source
ed
est probablement le bon choix pour éditer un fichier sur place:la source
ed
versions ne se comportent différemment ..... c'est deman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
n'est pas une solution gool pour l'édition de fichiers de 35 Go puisque le fichier est lu dans un tampon.!
), de sorte qu'il peut avoir quelques trucs plus intéressants dans sa manche.ed
tronque le fichier et le réécrit. Cela ne modifiera donc pas les données stockées sur le disque, comme le souhaite le PO. En outre, cela ne peut pas fonctionner si le fichier est trop gros pour être chargé en mémoire.Vous pouvez utiliser un descripteur de fichier bash en lecture / écriture pour ouvrir votre fichier (pour l'écraser in situ), puis
sed
ettruncate
... mais bien sûr, ne laissez jamais vos modifications dépasser le nombre de données lues jusqu'à présent. .Voici le script (utilise: bash variable $ BASHPID)
Voici la sortie de test
la source
Je mapperais le fichier en mémoire, je mettrais tout en place en utilisant des pointeurs sur la mémoire nue, puis remapperais le fichier et le tronquer.
la source
Pas exactement in situ mais - cela pourrait être utile dans des circonstances similaires.
Si l'espace disque pose problème, commencez par compresser le fichier (car il s'agit de texte, cela donnera une réduction considérable), puis utilisez sed (ou grep, ou autre chose) de la manière habituelle au milieu d'un pipeline de décompression / compression.
la source
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Pour aider ceux qui recherchent cette question dans Google, la bonne solution consiste à cesser de chercher des fonctionnalités de shell obscures qui risquent de corrompre votre fichier pour un gain de performances négligeable, et d'utiliser plutôt une variante de ce modèle:
Ce n'est que dans la situation extrêmement rare où, pour une raison quelconque, que cela n'est pas réalisable, que vous considériez sérieusement l'une des autres réponses de cette page (bien qu'elles soient certainement intéressantes à lire). Je concéderai que le casse-tête de l'OP qui consiste à ne pas disposer d'espace disque pour créer un deuxième fichier est exactement une telle situation. Même dans ce cas, il existe d’autres options, telles que fournies par @Ed Randall et @Basile Starynkevitch.
la source
echo -e "$(grep pattern bigfile)" >bigfile
la source
grepped
données dépassent la longueur autorisée par la ligne de commande. les données sont ensuite corrompues