web-dev-qa-db-fra.com

Lecture et écriture d’un fichier: commande tee

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

cat filename | some_sed_command >filename

efface le fichier nomfichier, car la redirection de sortie, exécutée avant la commande, provoque la troncature de nomfichier.

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 passera-t-il si le fichier (et le résultat de la commande sed) est très volumineux? Comment le système d'exploitation peut-il éviter d'écraser du contenu qui n'est toujours pas lu? Je vois qu’il existe également une commande éponge qui devrait fonctionner dans tous les cas: est-ce "plus sûr" que le tee?

9
VeryHardCoder

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

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

Non .

Les chances que file soit tronqué, mais il n'y a aucune garantie que cat file | some_sed_command | tee file >/dev/null ne tronque pas file.

Tout dépend de la commande traitée en premier, par opposition à ce à quoi on peut s'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 sélectionnée en premier. Vous pouvez donc tout aussi bien penser qu'elle est sélectionnée au hasard et ne jamais compter sur le fait que Shell ne sélectionne pas la commande fautive.

Étant donné que les chances que la commande incriminée soit sélectionnée en premier entre trois commandes sont plus faibles que les chances pour que la commande incriminée soit sélectionnée en premier entre deux commandes, il est moins probable que file soit tronqué, mais ça va encore arriver .

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

Donc never utilise quelque chose comme cat file | some_sed_command | tee file >/dev/null. Utilisez sponge comme suggéré par Oli.

Au lieu de cela, pour les environnements plus exigeants et/ou les fichiers relativement petits, vous pouvez utiliser une chaîne here et une substitution de commande pour lire le fichier avant l'exécution d'une commande:

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

Pour sed spécifiquement, vous pouvez utiliser son argument in-situ -i. 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 fassiez plus que sed, vous pouvez tout tamponner avec sponge (du paquet moreutils) qui va "absorber" "tous les stdin avant d 'écrire dans le fichier. C'est comme tee mais avec moins de fonctionnalités. Pour une utilisation de base, il s’agit plutôt d’un remplacement immédiat:

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

Est-ce plus sûr? Absolument. Il a probablement des limites, donc si vous faites quelque chose de colossal (et que vous ne pouvez pas éditer sur place avec sed), vous voudrez peut-être faire vos modifications dans un second fichier, puis mv ce fichier à l'original. nom de fichier. Cela devrait être atomique (ainsi, tout ce qui dépend de ces fichiers ne sera pas cassé s’ils ont besoin d’un accès constant).

9
Oli

Oh, mais sponge n'est pas la seule option; vous ne devez pas obtenir moreutils pour 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 en tant que 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 canaux 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 ait eu la chance de commencer à s'exécuter.

La commande tee ne fonctionne pas, même si elle satisfait à la première condition, car elle ne satisfait pas à la seconde: elle crée toujours le fichier de sortie dès le démarrage, ce qui est aussi grave que la création d'un tuyau dans le fichier de sortie. (En réalité, il est 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, il ne fonctionne pas.)

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

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

La partie --random-source=/dev/zero joue des tours shuf pour faire ce qu'elle a à faire, sans rien mélanger, afin de mettre en mémoire tampon votre entrée sans la modifier.

0
Mike Nakis

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

0
Steven Penny