Les commandes bash suivantes entrent dans une boucle infinie:
$ echo hi > x
$ cat x >> x
Je peux deviner que la cat
lecture continue x
après avoir commencé à écrire sur stdout. Ce qui est déroutant, cependant, c'est que ma propre implémentation de test de cat présente un comportement différent:
// mycat.c
#include <stdio.h>
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "rb");
char buf[4096];
int num_read;
while ((num_read = fread(buf, 1, 4096, f))) {
fwrite(buf, 1, num_read, stdout);
fflush(stdout);
}
return 0;
}
Si je cours:
$ make mycat
$ echo hi > x
$ ./mycat x >> x
Il ne boucle pas . Étant donné le comportement de cat
et le fait que je vidais stdout
avant fread
est appelé à nouveau, je m'attendrais à ce que ce code C continue à lire et à écrire dans un cycle.
Comment ces deux comportements sont-ils cohérents? Quel mécanisme explique pourquoi les cat
boucles alors que le code ci-dessus ne fonctionne pas?
shell
files
io-redirection
cat
Tyler
la source
la source
cat x >> x
cause une erreur; cependant, cette commande est suggérée dans le livre Unix de Kernighan et Pike comme exercice.cat
utilise très probablement des appels système au lieu de stdio. Avec stdio, votre programme peut mettre en cache EOFness. Si vous commencez avec un fichier de plus de 4096 octets, obtenez-vous une boucle infinie?Réponses:
Sur un ancien système RHEL que j'ai,
/bin/cat
ne fait pas de bouclecat x >> x
.cat
donne le message d'erreur "cat: x: le fichier d'entrée est le fichier de sortie". Je peux tromper/bin/cat
en faisant ceci:cat < x >> x
. Lorsque j'essaie votre code ci-dessus, j'obtiens le "bouclage" que vous décrivez. J'ai également écrit un "chat" basé sur les appels système:Cela boucle aussi. La seule mise en mémoire tampon ici (contrairement à "mycat" basé sur stdio) est ce qui se passe dans le noyau.
Je pense que ce qui se passe est que le descripteur de fichier 3 (le résultat de
open(av[1])
) a un décalage dans le fichier de 0. Le descripteur de fichier 1 (stdout) a un décalage de 3, car le ">>" fait que le shell appelant fait unlseek()
sur le descripteur de fichier avant de le transmettre aucat
processus enfant.Faire un
read()
de n'importe quelle sorte, que ce soit dans un tampon stdio ou un simple,char buf[]
avance la position du descripteur de fichier 3. Faire unwrite()
avance la position du descripteur de fichier 1. Ces deux décalages sont des nombres différents. En raison du ">>", le descripteur de fichier 1 a toujours un décalage supérieur ou égal à l'offset du descripteur de fichier 3. Ainsi, tout programme "semblable à un chat" bouclera, à moins qu'il ne fasse un tampon interne. Il est possible, voire probable, qu'une implémentation stdio d'unFILE *
(qui est le type des symbolesstdout
etf
dans votre code) qui inclut son propre tampon.fread()
peut en fait faire un appel systèmeread()
pour remplir le tampon interne fof
. Cela peut ou ne peut rien changer à l'intérieur destdout
. Appelfwrite()
surstdout
peut ou ne peut rien changer à l'intérieur def
. Un "chat" basé sur stdio peut donc ne pas boucler. Ou peut-être. Difficile à dire sans lire beaucoup de code libc laid et laid.Je l' ai fait un
strace
sur la RHELcat
- il fait juste une succession deread()
etwrite()
appels système. Mais uncat
ne doit pas fonctionner de cette façon. Il serait possible pourmmap()
le fichier d'entrée, alors faiteswrite(1, mapped_address, input_file_size)
. Le noyau ferait tout le travail. Ou vous pouvez faire unsendfile()
appel système entre les descripteurs de fichiers d'entrée et de sortie sur les systèmes Linux. Les vieux systèmes SunOS 4.x étaient censés faire l'affaire de mappage de la mémoire, mais je ne sais pas si quelqu'un a déjà fait un chat basé sur sendfile. Dans les deux cas, le "bouclage" ne se produirait pas, car les deuxwrite()
etsendfile()
nécessitent un paramètre de longueur à transférer.la source
fread
appel ait mis en cache un indicateur EOF comme l'a suggéré Mark Plotnick. Preuve: [1] Le chat Darwin utilise la lecture, pas la peur; et [2] la frayeur de Darwin appelle __srefill qui définitfp->_flags |= __SEOF;
dans certains cas. [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
estcat -u
- u pour non tamponné .>>
doit être implémenté en appelant open () avec l'O_APPEND
indicateur, ce qui fait que chaque opération d'écriture écrit (atomiquement) à la fin actuelle du fichier, quelle que soit la position du descripteur de fichier avant la lecture. Ce comportement est nécessaire pourfoo >> logfile & bar >> logfile
fonctionner correctement, par exemple - vous ne pouvez pas vous permettre de supposer que la position après la fin de votre dernière écriture est toujours la fin du fichier.Une implémentation de chat moderne (sunos-4.0 1988) utilise mmap () pour mapper le fichier entier, puis appelle 1x write () pour cet espace. Une telle implémentation ne boucle pas tant que la mémoire virtuelle permet de mapper le fichier entier.
Pour les autres implémentations, cela dépend si le fichier est plus grand que le tampon d'E / S.
la source
cat
implémentations ne tamponnent pas leur sortie (-u
implicite). Ceux-ci seront toujours en boucle.Comme écrit dans les pièges de Bash , vous ne pouvez pas lire à partir d'un fichier et y écrire dans le même pipeline.
La solution consiste à utiliser l'éditeur de texte ou une variable temporaire.
la source
Vous avez une sorte de condition de concurrence entre les deux
x
. Certaines implémentations decat
(par exemple coreutils 8.23) interdisent que:Si cela n'est pas détecté, le comportement dépendra évidemment de l'implémentation (taille du buffer, etc.).
Dans votre code, vous pouvez essayer d'ajouter un
clearerr(f);
après lefflush
, au cas où le suivantfread
retournerait une erreur si l'indicateur de fin de fichier est défini.la source
i = i++;
comportement indéfini de C , d'où la divergence.cat
.