Lecture et écriture d'un fichier: commande tee

10

Il est bien connu qu'une commande comme celle-ci:

cat filename | some_sed_command >filename

efface le nom de fichier, car la redirection de sortie, exécutée avant la commande, entraîne la troncature du nom de fichier.

On pourrait résoudre le problème de la manière suivante:

cat file | some_sed_command | tee file >/dev/null

mais je ne suis pas sûr que cela fonctionnerait dans tous les cas: que se passe-t-il si le fichier (et le résultat de la commande sed) est très gros? Comment le système d'exploitation peut-il éviter d'écraser du contenu qui n'est toujours pas lu? Je vois qu'il y a aussi une commande éponge qui devrait fonctionner dans tous les cas: est-ce "plus sûr" que tee?

VeryHardCoder
la source
Quel est votre objectif principal? (en termes simples)
Sergiy Kolodyazhnyy
@Serg comprend simplement comment les choses fonctionnent ... La réponse écrite par kos clarifie la question
VeryHardCoder

Réponses:

10

On pourrait résoudre le problème de la manière suivante:

cat file | some_sed_command | tee file >/dev/null

Non .

Les chances fileseront tronquées, mais aucune garantie cat file | some_sed_command | tee file >/dev/nullne sera tronquée file.

Tout dépend de la commande qui est traitée en premier, contrairement à ce que l'on peut attendre, les commandes d'un canal ne sont pas traitées de gauche à droite . Il n'y a aucune garantie quant à la commande qui sera choisie en premier, donc on pourrait tout aussi bien la considérer comme choisie au hasard et ne jamais compter sur le shell qui ne choisit pas la faute.

Étant donné que les chances que la commande incriminée soit choisie en premier entre trois commandes sont plus faibles que les chances que la commande incriminée soit choisie en premier entre deux commandes, il est moins probable qu'elle filesoit tronquée, mais cela se produira toujours .

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Alors n'utilisez jamais quelque chose comme ça cat file | some_sed_command | tee file >/dev/null. Utilisez spongecomme Oli l'a suggéré.

Comme alternative, pour des environnements plus légers et / ou des fichiers relativement petits, on peut utiliser une chaîne ici et une substitution de commande pour lire le fichier avant d'exécuter une commande:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
kos
la source
9

Plus sedprécisément, vous pouvez utiliser son -iargument in situ. Il enregistre simplement dans le fichier qu'il a ouvert, par exemple:

sed -i 's/ /-/g' filename

Si vous voulez faire quelque chose de plus costaud, en supposant que vous en fassiez plus sed, oui, vous pouvez mettre le tout en mémoire tampon sponge(à partir du moreutilspaquet) qui "absorbera" tout le stdin avant d'écrire dans le fichier. C'est comme teemais avec moins de fonctionnalités. Cependant, pour une utilisation de base, c'est à peu près un remplacement sans rendez-vous:

cat file | some_sed_command | sponge file >/dev/null

Est-ce que c'est plus sûr? Absolument. Il a probablement des limites, donc si vous faites quelque chose de colossal (et ne pouvez pas éditer sur place avec sed), vous voudrez peut-être apporter vos modifications à un deuxième fichier, puis mvce fichier au nom de fichier d'origine. Cela devrait être atomique (donc tout ce qui dépend de ces fichiers ne se cassera pas s'ils ont besoin d'un accès constant).

Oli
la source
0

Vous pouvez utiliser Vim en mode Ex:

ex -sc '%!some_sed_command' -cx filename
  1. % sélectionner toutes les lignes

  2. ! Exécuter la commande

  3. x Sauvegarder et quitter

Steven Penny
la source
0

Oh, mais ce spongen'est pas la seule option; vous n'avez pas besoin de l'obtenir moreutilspour que cela fonctionne correctement. Tout mécanisme fonctionnera tant qu'il répond aux deux exigences suivantes:

  1. Il accepte le nom du fichier de sortie comme paramètre.
  2. Il crée uniquement le fichier de sortie une fois que toutes les entrées ont été traitées.

Vous voyez, le problème bien connu auquel l'OP fait référence est que le shell créera tous les fichiers nécessaires au fonctionnement des tuyaux avant même de commencer à exécuter les commandes dans le pipeline, c'est donc le shell qui tronque réellement le fichier de sortie (qui est malheureusement aussi le fichier d'entrée) avant même que l'une des commandes n'ait même eu la chance de commencer à s'exécuter.

La teecommande ne fonctionne pas, même si elle satisfait la première exigence, car elle ne satisfait pas la deuxième exigence: elle créera toujours le fichier de sortie immédiatement au démarrage, elle est donc essentiellement aussi mauvaise que la création d'un tuyau directement dans le fichier de sortie. (C'est en fait pire, car son utilisation introduit un délai aléatoire non déterministe avant que le fichier de sortie ne soit tronqué, vous pourriez donc penser que cela fonctionne, alors qu'en fait ce n'est pas le cas.)

Donc, tout ce dont nous avons besoin pour résoudre ce problème est une commande qui tamponnera toutes ses entrées avant de produire une sortie, et qui est capable d'accepter le nom de fichier de sortie en tant que paramètre, de sorte que nous n'avons pas à diriger sa sortie dans le fichier de sortie. Une telle commande est shuf. Ainsi, ce qui suit accomplira la même chose sponge:

    shuf --output=file --random-source=/dev/zero 

La --random-source=/dev/zeropartie astuces shufpour faire son travail sans mélanger du tout, donc elle tamponnera votre entrée sans la modifier.

Mike Nakis
la source