sed suppression de ligne en place sur le système de fichiers complet?

11

En raison d'un bug d'application non encore diagnostiqué, j'ai plusieurs centaines de serveurs avec un disque plein. Il y a un fichier qui a été rempli de lignes en double - pas un fichier journal, mais un fichier d'environnement utilisateur avec des définitions de variables (donc je ne peux pas simplement supprimer le fichier).

J'ai écrit une sedcommande simple pour vérifier les lignes ajoutées par erreur et les supprimer, et je l'ai testée sur une copie locale du fichier. Cela a fonctionné comme prévu.

Cependant, lorsque je l'ai essayé sur le serveur avec le disque plein, j'ai eu environ l'erreur suivante (c'est de la mémoire, pas de copier-coller):

sed: couldn't flush /path/to/file/sed8923ABC: No space left on deviceServerHostname

Bien sûr, je sais qu'il n'y a plus d'espace. C'est pourquoi j'essaie de supprimer des trucs! (La sedcommande que j'utilise réduira un fichier de plus de 4000 lignes à environ 90 lignes.)

Ma sedcommande est justesed -i '/myregex/d' /path/to/file/filename

Existe-t-il un moyen d'appliquer cette commande malgré le disque plein?

(Il doit être automatisé, car je dois l'appliquer à plusieurs centaines de serveurs comme solution rapide.)

(Évidemment, le bogue d'application doit être diagnostiqué, mais en attendant, les serveurs ne fonctionnent pas correctement ....)


Mise à jour: La situation à laquelle j'ai été confrontée a été résolue en supprimant quelque chose d'autre que j'ai découvert que je pouvais supprimer, mais j'aimerais toujours la réponse à cette question, qui serait utile à l'avenir et pour d'autres personnes.

/tmpest un non-go; c'est sur le même système de fichiers.

Avant de libérer de l'espace disque, j'ai testé et découvert que je pouvais supprimer les lignes en viouvrant le fichier et en l'exécutant :g/myregex/d, puis en enregistrant avec succès les modifications avec :wq. Il semble qu'il devrait être possible d'automatiser cela, sans avoir recours à un système de fichiers distinct pour contenir un fichier temporaire .... (?)

Caractère générique
la source
1
sed -icrée une copie temporaire pour fonctionner. Je soupçonne que ce edserait mieux pour cela, bien que je ne sois pas assez familier pour proscrire une solution réelle
Eric Renouf
2
Si edvous couriez: printf %s\\n g/myregex/d w q | ed -s infilemais gardez à l'esprit que certaines implémentations utilisent également des fichiers temporaires comme sed(vous pouvez essayer busybox ed - afaik ne crée pas de fichier temporaire)
don_crissti
1
@Wildcard - pas fiable avec echo. utiliser printf. et faites sedajouter quelques caractères que vous déposez à la dernière ligne pour éviter de perdre des blancs de fin. De plus, votre shell doit être capable de gérer l'intégralité du fichier sur une seule ligne de commande. c'est votre risque - testez d'abord. bashest particulièrement mauvais à cela (je pense que c'est à faire avec un espace de pile?) et peut vous fatiguer à tout moment. les deux sed'si recommandés utiliseraient au moins le tampon de pipe du noyau pour faire bon effet entre eux, mais la méthode est assez similaire. votre sous-commande commandera également filesi le sed w / in réussit ou non.
mikeserv
1
@Wildcard - essayez sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}et si cela fonctionne, lisez le reste de ma réponse.
mikeserv

Réponses:

10

L' -ioption n'écrase pas vraiment le fichier d'origine. Il crée un nouveau fichier avec la sortie, puis le renomme avec le nom de fichier d'origine. Comme vous n'avez pas de place sur le système de fichiers pour ce nouveau fichier, il échoue.

Vous devrez le faire vous-même dans votre script, mais créez le nouveau fichier sur un système de fichiers différent.

De plus, si vous supprimez simplement des lignes qui correspondent à une expression rationnelle, vous pouvez utiliser à la grepplace de sed.

grep -v 'myregex' /path/to/filename > /tmp/filename && mv /tmp/filename /path/to/filename

En général, il est rarement possible pour les programmes d'utiliser le même fichier en entrée et en sortie - dès qu'il commence à écrire dans le fichier, la partie du programme qui lit le fichier ne verra plus le contenu d'origine. Il doit donc d'abord copier le fichier d'origine quelque part, ou écrire dans un nouveau fichier et le renommer une fois terminé.

Si vous ne souhaitez pas utiliser de fichier temporaire, vous pouvez essayer de mettre en cache le contenu du fichier en mémoire:

file=$(< /path/to/filename)
echo "$file" | grep -v 'myregex' > /path/to/filename
Barmar
la source
1
A-t-il préservé les autorisations, la propriété et les horodatages? Peut-être rsync -a --no-owner --no-group --remove-source-files "$backupfile" "$destination"d' ici
Hastur
@Hastur - voulez-vous dire que sed -icela préserve ce genre de choses?
mikeserv
2
@Hastur sed -ine conserve aucune de ces choses. Je viens de l'essayer avec un fichier que je ne possède pas, mais situé dans un répertoire que je possède, et cela m'a permis de remplacer le fichier. Le remplacement appartient à moi, pas au propriétaire d'origine.
Barmar
1
@ RalphRönnquist Pour être sûr, vous devez le faire en deux étapes:var=$(< FILE); echo "$FILE" | grep '^"' > FILE
Barmar
1
@Barmar - vous ne fonctionne pas - vous ne savez même pas que vous avez réussi à ouvrir l'entrée. Le très moins que vous pourriez faire est , v=$(<file)&& printf %s\\n "$v" >filemais vous ne l' utilisez pas même &&. Le demandeur parle de l'exécuter dans un script - automatiser l'écrasement d'un fichier avec une partie de lui-même. vous devez au moins valider que vous pouvez ouvrir avec succès l'entrée et la sortie. De plus, le shell pourrait exploser.
mikeserv
4

Voilà comment ça sedmarche. Si utilisé avec -i(modifier sur place) sedcrée un fichier temporaire avec le nouveau contenu du fichier traité. Une fois terminé sed, remplace le fichier de travail actuel par le fichier temporaire. L'utilitaire ne modifie pas le fichier sur place . C'est exactement le comportement de chaque éditeur.

C'est comme si vous exécutiez la tâche suivante dans un shell:

sed 'whatever' file >tmp_file
mv tmp_file file

À ce stade sed, essaie de vider les données mises en mémoire tampon dans le fichier mentionné dans le message d'erreur avec l' fflush()appel système:

Pour les flux de sortie, fflush()force l'écriture de toutes les données tamponnées de l'espace utilisateur pour le flux de sortie ou de mise à jour donné via la fonction d'écriture sous-jacente du flux.


Pour votre problème, je vois une solution dans le montage d'un système de fichiers séparé (par exemple tmpfs, si vous avez suffisamment de mémoire ou un périphérique de stockage externe) et déplacer certains fichiers là-bas, les traiter là-bas et les reculer.

le chaos
la source
3

Depuis la publication de cette question, j'ai appris qu'il exs'agit d'un programme compatible POSIX. Il est presque universellement lié à vim, mais de toute façon, ce qui suit est (je pense) un point clé exà propos des systèmes de fichiers (extrait de la spécification POSIX):

Cette section utilise le terme tampon d'édition pour décrire le texte de travail actuel. Aucune implémentation spécifique n'est impliquée par ce terme. Toutes les modifications d'édition sont effectuées sur le tampon d'édition, et aucune modification ne doit affecter un fichier jusqu'à ce qu'une commande de l'éditeur écrive le fichier.

"... affectera n'importe quel fichier ..." Je crois que mettre quelque chose sur le système de fichiers (même un fichier temporaire) compterait comme "affectant n'importe quel fichier". Peut être?*

Une étude attentive des spécifications POSIX pourex indiquer certains "pièges" sur son utilisation portable prévue par rapport aux utilisations scriptées courantes de la exrecherche en ligne (qui sont jonchées de vimcommandes spécifiques).

  1. L'implémentation +cmdest facultative selon POSIX.
  2. Autoriser plusieurs -coptions est également facultatif.
  3. La commande globale :g"mange" tout jusqu'au prochain saut de ligne non échappé (et l'exécute donc après chaque correspondance trouvée pour l'expression régulière plutôt qu'une fois à la fin). Alors -c 'g/regex/d | x'supprime seulement une instance, puis quitte le fichier.

Donc, selon ce que j'ai recherché, la méthode compatible POSIX pour éditer sur place un fichier sur un système de fichiers complet pour supprimer toutes les lignes correspondant à une expression rationnelle spécifique, est:

ex -sc 'g/myregex/d
x' /path/to/file/filename

Cela devrait fonctionner si vous disposez de suffisamment de mémoire pour charger le fichier dans un tampon.

* Si vous trouvez quelque chose qui indique le contraire, veuillez le mentionner dans les commentaires.

Caractère générique
la source
2
mais ex écrit dans tmpfiles ... toujours. son spec'd écrire périodiquement ses tampons sur le disque. il existe même des commandes spécifiques pour localiser les tampons de fichiers tmp sur le disque.
mikeserv
@Wildcard Merci pour le partage, je suis revenu sur un poste similaire à SO . Je suppose qu'il ex +g/match/d -scx fileest également compatible POSIX?
kenorb
@kenorb, pas tout à fait, selon ma lecture des spécifications — voir mon point 1 dans la réponse ci-dessus. La citation exacte de POSIX est "L'utilitaire ex doit être conforme aux directives de syntaxe de l'utilitaire XBD, à l'exception de l'utilisation non spécifiée de" - ", et que " + " peut être reconnu comme délimiteur d'option ainsi que" - "."
Wildcard
1
Je ne peux pas le prouver, sauf en faisant appel au bon sens, mais je crois que vous lisez plus dans cette déclaration de la spécification qu'il n'y en a vraiment. Je suggère que l'interprétation la plus sûre est qu'aucune modification du tampon d'édition n'affectera un fichier qui existait avant le début de la session d'édition, ou que l'utilisateur a nommé. Voir aussi mes commentaires sur ma réponse.
G-Man dit `` Réintègre Monica '' le
@ G-Man, je pense que tu as raison; mon interprétation initiale était probablement un vœu pieux. Cependant, depuis que l'édition du fichier a vi fonctionné sur un système de fichiers complet, je pense que dans la plupart des cas, cela fonctionnerait exégalement - mais peut-être pas pour un fichier ginormous. sed -ine fonctionne pas sur un système de fichiers complet quelle que soit sa taille.
Wildcard du
2

Utilisez le tuyau, Luke!

Lire le fichier | filtre | répondre

sed 's/PATTERN//' BIGFILE | dd of=BIGFILE conv=notrunc

dans ce cas, sedne crée pas de nouveau fichier et envoie simplement une sortie redirigée vers ddlaquelle ouvre le même fichier . Bien sûr, on peut utiliser grepdans un cas particulier

grep -v 'PATTERN' BIGFILE | dd of=BIGFILE conv=notrunc

puis tronquez le reste.

dd if=/dev/null of=BIGFILE seek=1 bs=BYTES_OF_SED_OUTPUT
Leben Gleben
la source
1
Avez-vous remarqué la partie "système de fichiers complet" de la question?
Wildcard
1
@Wildcard, utilise-t-il sedtoujours des fichiers temporaires? grepde toute façon pas
Leben Gleben
Cela semble une alternative à la spongecommande. Oui, sedavec -itoujours crée des fichiers lilke "seduyUdmw" avec 000 droits.
Pablo A
1

Comme indiqué dans d'autres réponses, sed -ifonctionne en copiant le fichier dans un nouveau fichier dans le même répertoire , en apportant des modifications au processus, puis en déplaçant le nouveau fichier sur l'original. Voilà pourquoi cela ne fonctionne pas.  ed(l'éditeur de ligne d'origine) fonctionne d'une manière quelque peu similaire, mais, la dernière fois que j'ai vérifié, il utilise /tmple fichier de travail. Si votre /tmpsystème de fichiers est différent de celui qui est plein, il edpeut faire le travail pour vous.

Essayez ceci (à l'invite de votre shell interactif):

$ ed / chemin / vers / fichier / nom de fichier
P
g / myregex / d
w
q

Le P(qui est un P majuscule ) n'est pas strictement nécessaire. Il active les invites; sans cela, vous travaillez dans le noir, et certaines personnes trouvent cela déconcertant. Les wet qsont w rite et q uit.

edest connu pour les diagnostics cryptiques. Si à un moment donné, il affiche autre chose que l'invite (qui est *) ou quelque chose qui est clairement une confirmation de la réussite de l'opération ( surtout s'il contient un ?), n'écrivez pas le fichier (avec w). Quittez ( q). Si cela ne vous laisse pas sortir, essayez de qrépéter.

Si votre /tmprépertoire se trouve sur le système de fichiers qui est plein (ou si son système de fichiers est également plein), essayez de trouver de l'espace quelque part. le chaos a mentionné le montage d'un tmpfs ou d'un périphérique de stockage externe (par exemple, un lecteur flash); mais, si vous avez plusieurs systèmes de fichiers et qu'ils ne sont pas tous pleins, vous pouvez simplement utiliser l'un des autres systèmes existants. chaos suggère de copier le (s) fichier (s) vers l'autre système de fichiers, de les éditer là (avec sed), puis de les recopier. À ce stade, c'est peut-être la solution la plus simple. Mais une alternative serait de créer un répertoire accessible en écriture sur un système de fichiers qui a de l'espace libre, de définir une variable d'environnement TMPDIRpour pointer vers ce répertoire, puis de l'exécuter ed. (Divulgation: je ne sais pas si cela fonctionnera, mais cela ne peut pas faire de mal.)

Une fois que vous commencez à edtravailler, vous pouvez automatiser cela en faisant

ed nom de fichier << EOF
g / myregex / d
w
q
EOF

dans un script. Ou , comme l'a suggéré don_crissti.printf '%s\n' 'g/myregex/d' w q | ed -s filename

G-Man dit «Réintègre Monica»
la source
Hmmm. Peut-on faire la même chose (avec edou avec ex) de telle sorte que la mémoire soit utilisée plutôt qu'un système de fichiers séparé? C'est ce que j'allais vraiment faire (et la raison pour laquelle je n'ai pas accepté de réponse.)
Wildcard
Hmm. Cela peut être plus compliqué que je ne le pensais. J'ai étudié la source il y a de ednombreuses années. Il y avait encore des choses comme les ordinateurs 16 bits, sur lesquels les processus étaient limités à un espace d'adressage de 64 Ko (!), Donc l'idée d'un éditeur lisant le fichier entier en mémoire n'était pas un démarreur. Depuis lors, bien sûr, la mémoire est devenue plus grande - mais les disques et les fichiers aussi. Étant donné que les disques sont si gros, les gens ne ressentent pas le besoin de faire face à la contingence de /tmpmanquer d'espace. Je viens de jeter un coup d'œil au code source d'une version récente de ed, et il semble toujours… (suite)
G-Man dit 'Reinstate Monica'
(Suite)… pour implémenter le «tampon d'édition» en tant que fichier temporaire, sans condition - et je ne trouve aucune indication qu'une version de ed(ou exou vi) offre une option pour garder le tampon en mémoire.  D'un autre côté, l' édition de texte avec ed et vi - Chapitre 11: Traitement de texte - Partie II: Exploration de Red Hat Linux - Secrets professionnels de Red Hat Linux 9 - Les systèmes Linux disent que edle tampon d'édition réside dans la mémoire,… (suite) )
G-Man dit `` Réintègre Monica '' le
(Suite)… et Traitement et composition des documents UNIX par Balasubramaniam Srinivasan dit la même chose vi(qui est le même programme que ex). Je crois qu'ils utilisent simplement une formulation bâclée et imprécise - mais, si c'est sur Internet (ou sur papier), cela doit être vrai, non? Vous payez votre argent et vous faites votre choix.
G-Man dit `` Réintègre Monica '' le
Mais de toute façon, j'ai ajouté une nouvelle réponse.
G-Man dit `` Réintègre Monica '' le
1

Vous pouvez tronquer le fichier assez facilement si vous pouvez obtenir le nombre d'octets à votre décalage et vos lignes se produisent d'un point de départ jusqu'à la fin.

o=$(sed -ne'/regex/q;p' <file|wc -c)
dd if=/dev/null of=file bs="$o" seek=1

Ou bien si votre ${TMPDIR:-/tmp}est sur un autre système de fichiers peut-être:

{   cut -c2- | sed "$script" >file
} <file <<FILE
$(paste /dev/null -)
FILE

Parce que (la plupart) des coquilles y placent leurs documents ici dans un fichier temporaire supprimé. Il est parfaitement sûr tant que le <<FILEdescripteur est maintenu du début à la fin et ${TMPDIR:-/tmp}dispose d'autant d'espace que nécessaire.

Les shells qui n'utilisent pas de fichiers temporaires utilisent des tuyaux, et ne sont donc pas sûrs à utiliser de cette façon. Ces coquilles sont généralement ashdérivés comme busybox, dash, BSD sh- zsh, bash, kshet le shell Bourne, cependant, tous les fichiers temporaires d'utilisation.

apparemment, j'ai écrit un petit programme shell en juillet dernier pour faire quelque chose de très similaire


Si ce /tmpn'est pas viable, tant que vous pouvez mettre le fichier en mémoire quelque chose comme ...

sed 'H;$!d;x' <file | { read v &&
sed "$script" >file;}

... comme un cas général garantirait au moins que le fichier a été entièrement tamponné par le premier sedprocessus avant d'essayer de tronquer le fichier d'entrée / sortie.

Une solution plus ciblée - et efficace - pourrait être:

sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}

... car cela ne dérangerait pas les lignes de mise en mémoire tampon que vous vouliez supprimer de toute façon.

Un test du cas général:

{   nums=/tmp/nums
    seq 1000000 >$nums
    ls -lh "$nums"
    wc -l  "$nums"
    sed 'H;$!d;x' <$nums | { read script &&  ### read always gets a blank
    sed "$script" >$nums;}
    wc -l  "$nums"
    ls -lh "$nums"
}

-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1000000 /tmp/nums
1000000 /tmp/nums
-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
mikeserv
la source
J'avoue que je n'avais pas lu votre réponse en détail auparavant, car elle commence par des solutions inapplicables (pour moi) qui impliquent le nombre d'octets (différent parmi chacun des nombreux serveurs) et /tmpqui se trouvent sur le même système de fichiers. J'aime ta double sedversion. Je pense qu'une combinaison de Barmar et de votre réponse serait probablement la meilleure, quelque chose comme: myvar="$(sed '/myregex/d' < file)" && [ -n "$myvar" ] && echo "$myvar" > file ; unset myvar (Dans ce cas, je ne me soucie pas de préserver les nouvelles lignes de fin.)
Wildcard
2
@Wildcard - c'est possible. mais vous ne devriez pas utiliser le shell comme une base de données. le sed| catLa chose ci-dessus n'ouvre jamais la sortie à moins qu'elle sedn'ait déjà mis en mémoire tampon le fichier entier et qu'elle soit prête à commencer à l'écrire entièrement en sortie. S'il essaie de mettre le fichier en mémoire tampon et échoue - échoue readcar il trouve EOF sur le |canal avant de lire sa première nouvelle ligne et cat >out ne se produit donc jamais jusqu'à l'heure de l'écrire entièrement de la mémoire. un débordement ou quelque chose comme ça échoue. également le pipeline entier retourne le succès ou l'échec à chaque fois. le stocker dans une var est juste plus risqué.
mikeserv
@Wildcard - si je le voulais vraiment dans une variable aussi, je pense que je le ferais comme ça: file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shitedonc le fichier de sortie et le var seraient écrits simultanément, ce qui ferait soit une sauvegarde efficace , soit la seule raison pour laquelle vous voudriez compliquer les choses plus que ce dont vous auriez besoin.
mikeserv
@mikeserv: Je traite maintenant le même problème que l'OP et je trouve votre solution vraiment utile. Mais je ne comprends pas l'utilisation de read scriptet read vdans votre réponse. Si vous pouvez en dire plus, je serai très apprécié, merci!
sylye
1
@sylye - $scriptest le sedscript que vous utiliseriez pour cibler la partie de votre fichier que vous souhaitez; c'est le script qui vous donne le résultat final que vous souhaitez dans le flux. vest juste un espace réservé pour une ligne vide. dans un bashshell, ce n'est pas nécessaire car bashil utilisera automatiquement la $REPLYvariable shell à sa place si vous n'en spécifiez pas, mais POSIXly vous devriez toujours le faire. je suis content que vous le trouviez utile, au fait. Bonne chance. im mikeserv @ gmail si vous avez besoin de quelque chose en profondeur. je devrais avoir un ordinateur à nouveau dans quelques jours
mikeserv
0

Cette réponse emprunte des idées à cette autre réponse et à cette autre réponse, mais s'appuie sur elles, créant une réponse plus généralement applicable:

num_bytes = $ (sed '/ myregex / d' / chemin / vers / fichier / nom de fichier | wc -c)
sed '/ myregex / d' / chemin / vers / fichier / nom de fichier 1 <> / chemin / vers / fichier / nom de fichier 
dd if = / dev / null of = / chemin / vers / fichier / nom de fichier bs = "$ num_bytes" = 1

La première ligne exécute la sedcommande avec une sortie écrite sur la sortie standard (et non dans un fichier); spécifiquement, à un tuyau wcpour compter les caractères. La deuxième ligne exécute également la sedcommande avec une sortie écrite sur la sortie standard, qui, dans ce cas, est redirigée vers le fichier d'entrée en mode de lecture / écriture par écrasement (pas de troncature), qui est abordé ici . C'est une chose quelque peu dangereuse à faire; il n'est sûr que lorsque la commande de filtre n'augmente jamais la quantité de données (texte); c'est-à-dire que pour chaque n octet qu'il lit, il écrit n octets ou moins. C'est, bien sûr, vrai pour la sed '/myregex/d'commande; pour chaque ligne qu'il lit, il écrit exactement la même ligne, ou rien. (Autres exemples:s/foo/fu/ou s/foo/bar/serait en sécurité, mais s/fu/foo/et s/foo/foobar/ne serait pas.)

Par exemple:

$ cat filename
It was
a dark and stormy night.
$ sed '/was/d' filename 1<> filename
$ cat filename
a dark and stormy night.
night.

parce que ces 32 octets de données:

I  t     w  a  s \n  a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

a été remplacé par ces 25 caractères:

a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

laissant les sept octets night.\nà la fin.

Enfin, la ddcommande recherche la fin des nouvelles données nettoyées (octet 25 dans cet exemple) et supprime le reste du fichier; c'est-à-dire qu'il tronque le fichier à ce point.


Si, pour une raison quelconque, l' 1<>astuce ne fonctionne pas, vous pouvez le faire

sed '/ myregex / d' / chemin / vers / fichier / nom de fichier | jj de = / chemin / vers / fichier / nom de fichier conv = notrunc

Notez également que tant que vous ne faites que supprimer des lignes, tout ce dont vous avez besoin est grep -v myregex(comme l'a souligné Barmar ).

G-Man dit «Réintègre Monica»
la source
-3

sed -i 'd' / chemin / vers / fichier / nom de fichier

Chiranjeeb
la source
1
Salut! Il serait préférable d'expliquer le plus en détail possible le fonctionnement de votre solution et de répondre à la question.
dhag
2
C'est une terrible non-réponse. (a) Il échouera sur un système de fichiers complet, tout comme ma commande d'origine; (b) Si cela réussissait, cela viderait le fichier ENTIER, plutôt que juste les lignes correspondant à mon expression régulière.
Wildcard