La redirection avec «>>» est-elle équivalente à «>» lorsque le fichier cible n'existe pas encore?

80

Considérons un shell comme Bash ou sh. La différence fondamentale entre >et se >>manifeste dans un cas où le fichier cible existe:

  • > tronque le fichier à une taille nulle, puis écrit;
  • >> ne tronque pas, il écrit (ajoute) à la fin du fichier.

Si le fichier n'existe pas, il est créé avec une taille nulle. puis écrit à. Ceci est vrai pour les deux opérateurs. Il peut sembler que les opérateurs sont équivalents lorsque le fichier cible n'existe pas encore.

Sont-ils vraiment?

Kamil Maciorowski
la source

Réponses:

107

tl; dr

No. >>est essentiellement "toujours rechercher la fin du fichier" tout en >maintenant un pointeur sur le dernier emplacement écrit.


Réponse complète

(Remarque: tous mes tests ont été effectués sur Debian GNU / Linux 9).

Une autre différence

Non, ils ne sont pas équivalents. Il y a une autre différence. Il peut se manifester indépendamment du fait que le fichier cible existait auparavant ou non.

Pour l'observer, exécutez un processus qui génère des données et redirigez-le vers un fichier avec >ou >>(par exemple pv -L 10k /dev/urandom > blob). Laissez-le fonctionner et changez la taille du fichier (par exemple avec truncate). Vous verrez que >son décalage (croissant) se maintient tout >>en se terminant à la fin.

  • Si vous tronquez le fichier à une taille plus petite (la taille peut être nulle)
    • >s'en foutra, il écrira à l'offset voulu comme si de rien n'était; juste après que la troncature du décalage dépasse la fin du fichier, le fichier retrouvera son ancienne taille et s'agrandira davantage, les données manquantes seront remplies de zéros (de manière réduite, si possible);
    • >> sera ajouté à la nouvelle fin, le fichier passera de sa taille tronquée.
  • Si vous agrandissez le fichier
    • >s'en foutra, il écrira à l'offset voulu comme si de rien n'était; juste après avoir changé la taille de l'offset se trouvant quelque part dans le fichier, le fichier cessera de croître pendant un certain temps, jusqu'à ce que l'offset atteigne la nouvelle fin, puis le fichier s'agrandira normalement;
    • >> sera ajouté à la nouvelle fin, le fichier passera de sa taille agrandie.

Un autre exemple consiste à ajouter (avec un >>élément séparé ) quelque chose de plus lorsque le processus de génération de données est en cours d'exécution et en écriture dans le fichier. Ceci est similaire à l’agrandissement du fichier.

  • Le processus de génération >écrit avec le décalage souhaité et écrase les données supplémentaires.
  • Le processus de génération avec >>sautera les nouvelles données et les ajoutera au-delà (des conditions de concurrence peuvent se produire, les deux flux peuvent être imbriqués, mais aucune donnée ne doit être remplacée).

Exemple

Est-ce important dans la pratique? Il y a cette question :

J'exécute un processus qui produit beaucoup de sortie sur stdout. Tout envoyer dans un fichier [...] Puis-je utiliser un type de programme de rotation des journaux?

Cette réponse dit que la solution est logrotateavec l' copytruncateoption qui agit comme ceci:

Tronquez le fichier journal d'origine en place après la création d'une copie, au lieu de déplacer l'ancien fichier journal et éventuellement d'en créer un nouveau.

D'après ce que j'ai écrit ci-dessus, la redirection avec >rendra le journal tronqué volumineux en un rien de temps. La rareté permettra de sauver la journée, aucun espace disque important ne doit être gaspillé. Néanmoins, chaque journal consécutif comportera de plus en plus de zéros non nécessaires.

Mais si vous logrotatecréez des copies sans préserver l’éparpillement, ces zéros non hiérarchiques auront besoin de plus en plus d’espace disque à chaque copie. Je n'ai pas étudié le comportement de l'outil, il est peut-être assez intelligent en cas d'écartement ou de compression à la volée (si la compression est activée). Néanmoins, les zéros ne peuvent que causer des problèmes ou être au mieux neutres; rien de bon en eux.

Dans ce cas, utiliser à la >>place de >est nettement préférable, même si le fichier cible est sur le point d'être créé.


Performance

Comme on peut le constater, les deux opérateurs agissent différemment non seulement au début mais aussi plus tard. Cela peut entraîner des différences de performances (subtiles?). Pour l'instant, je n'ai aucun résultat de test significatif pour le confirmer ou le réfuter, mais je pense que vous ne devriez pas automatiquement présumer que leurs performances sont identiques en général.

Kamil Maciorowski
la source
9
Donc, >>est essentiellement "toujours chercher à la fin du fichier" tout en >maintenant un pointeur sur le dernier emplacement écrit. On dirait qu'il pourrait y avoir une différence de performance subtile dans la façon dont ils fonctionnent aussi ...
Mokubai
10
Au niveau de l'appel système, >>utilise l' O_APPENDindicateur àopen() . Et en fait, >utilise O_TRUNC, tandis >>que non. La combinaison de O_TRUNC | O_APPENDserait également possible, le langage shell ne fournit tout simplement pas cette fonctionnalité.
ilkkachu
3
@jjmontes, la source standard serait POSIX: pubs.opengroup.org/onlinepubs/9699919799.20edition/utilities/… mais bien sûr, le manuel de Bash contient également des descriptions sur les opérateurs de redirection, y compris les opérateurs non standard pris en charge: gnu.org/ logiciel / bash / manuel / html_node / Redirections.html
ilkkachu
2
@ilkkachu J'ai trouvé cela intéressant, car cela explique des détails sur O_APPEND que je m'interrogeais après votre commentaire :): stackoverflow.com/questions/1154446/…
jjmontes
1
@Mokubai, tout système d'exploitation sain aurait la longueur du fichier sous la main lorsqu'il serait ouvert. La vérification d'un drapeau et le déplacement du décalage à la fin devraient disparaître dans toute la comptabilité. Essayer d'émuler O_APPENDavec un lseek()avant chaque write()serait différent, il y aurait une surcharge d'appels système supplémentaire. (Et bien sûr, cela ne fonctionnerait pas, car un autre processus pourrait write()
s'interposer