Pourquoi la redirection de la sortie d'un fichier vers lui-même produit-elle un fichier vierge?

19

Pourquoi la redirection de la sortie d'un fichier vers lui-même produit-elle un fichier vierge?

Dit dans Bash, pourquoi

less foo.txt > foo.txt

et

fold foo.txt > foo.txt

produire un vide foo.txt? Puisqu'un ajout tel que less eggs.py >> eggs.pyproduit deux copies du texte dans eggs.py, on peut s'attendre à ce qu'un écrasement produise une copie du texte.

Remarque, je ne dis pas qu'il s'agit d'un bogue, il s'agit plutôt d'un pointeur vers quelque chose de profond à propos d'Unix.

seewalker
la source
Traitée dans la canonique U&L. Quels sont les opérateurs de contrôle et de redirection du shell? question.
Scott

Réponses:

20

Lorsque vous utilisez >, le fichier est ouvert en mode troncature de sorte que son contenu est supprimé avant que la commande ne tente de le lire.

Lorsque vous utilisez >>, le fichier est ouvert en mode ajout afin que les données existantes soient préservées. Il est cependant encore assez risqué d'utiliser le même fichier en entrée et en sortie dans ce cas. Si le fichier est suffisamment volumineux pour ne pas correspondre à la taille du tampon d'entrée en lecture, sa taille peut augmenter indéfiniment jusqu'à ce que le système de fichiers soit plein (ou que votre quota de disque soit atteint).

Si vous souhaitez utiliser un fichier à la fois en entrée et en sortie avec une commande qui ne prend pas en charge la modification sur place, vous pouvez utiliser quelques solutions:

  • Utilisez un fichier intermédiaire et écrasez celui d'origine une fois terminé et seulement si aucune erreur ne s'est produite lors de l'exécution de l'utilitaire (c'est le moyen le plus sûr et le plus courant).

    fold foo.txt > fold.txt.$$ && mv fold.txt.$$ foo.txt
  • Évitez le fichier intermédiaire au détriment d'une perte de données partielle ou complète potentielle en cas d'erreur ou d'interruption. Dans cet exemple, le contenu de foo.txtest transmis en entrée à un sous - shell (à l'intérieur des parenthèses) avant la suppression du fichier. L'inode précédent reste en vie pendant que le sous-shell le maintient ouvert pendant la lecture des données. Le fichier écrit par l'utilitaire interne (ici fold) avec le même nom (foo.txt) pointe vers un inode différent parce que l'ancienne entrée de répertoire a été supprimée donc techniquement, il y a deux "fichiers" différents avec le même nom pendant le processus. Lorsque le sous-shell se termine, l'ancien inode est libéré et ses données sont perdues. Veillez à vous assurer que vous disposez de suffisamment d'espace pour stocker temporairement à la fois l'ancien fichier et le nouveau, sinon vous perdrez des données.

    (rm foo.txt; fold > foo.txt) < foo.txt
jlliagre
la source
3
spongede moreutils peut également aider. fold foo.txt | sponge foo.txt- ou fold foo.txt | sponge !$devrait aussi faire.
slhck
@slhck En effet, l'éponge pourrait aussi faire le travail. Cependant, n'étant ni spécifié par POSIX ni mainstream sous Unix comme les OS, il est peu probable qu'il soit présent.
jlliagre
Ce n'est pas comme si cela ne pouvait pas être rendu présent;)
slhck
7

Le fichier est ouvert pour l'écriture par le shell avant que l'application ne puisse le lire. L'ouverture du fichier en écriture le tronque.

Ignacio Vazquez-Abrams
la source
0

En bash, l'opérateur de redirection de flux ... > foo.txtse vide foo.txt avant d'évaluer l'opérande gauche .

On peut utiliser la substitution de commandes et imprimer son résultat comme solution de contournement. Cette solution prend moins de caractères supplémentaires que dans les autres réponses:

printf "%s\n" "$(less foo.txt)" > foo.txt

Attention: cette commande ne conserve aucun retour à la ligne en fin de ligne foo.txt. Jetez un œil dans la section commentaire ci-dessous pour plus d'informations

Ici, le sous $(...)- shell est évalué avant l'opérateur de redirection de flux >, d'où la conservation des informations.

Louis-Jacob Lebel
la source
@KamilMaciorowski: En fait, il y en a tmp=$(cmd; printf q);  printf '%s' "${tmp%q}". Mais vous avez manqué un autre problème avec cette réponse: il dit "subshell" quand il signifie "substitution de commande". Oui, les substitutions de commandes sont généralement des sous-coquilles, mais pas l'inverse, et les sous-coquilles, en général, ne sont d'aucune aide pour ce problème.
Scott
@KamilMaciorowski Je me sens tellement mal d'avoir raté tout cela. Merci d'avoir pointé tout cela. Pour votre (4) e point: les guillemets inversés feraient-ils l'affaire, c'est-à-dire préservent-ils les sauts de ligne?
Louis-Jacob Lebel
@Scott merci pour votre réponse. J'ai changé "sous-shell" pour "substitution de commande". Au fait, je me demande quelle est la différence exacte entre les deux.
Louis-Jacob Lebel
Non, les guillemets (backticks) suppriment également les caractères de fin de ligne.
Kamil Maciorowski
Bon alors, j'ai ajouté un message d'avertissement pour l'instant. Je l'enlèverai si je trouve une solution.
Louis-Jacob Lebel