Почему cat x >> x loop?

Следующие команды bash переходят в цикл infinte:

$ echo hi > x $ cat x >> x 

Я могу догадаться, что cat продолжает читать от x после того, как он начал писать в stdout. Однако смущает то, что моя собственная тестовая реализация кошки демонстрирует различное поведение:

 // 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; } 

Если я запустил:

 $ make mycat $ echo hi > x $ ./mycat x >> x 

Он не зацикливается. Учитывая поведение cat и тот факт, что я сбрасываю stdout до того, как fread снова вызван, я ожидаю, что этот C-код продолжит чтение и запись в цикле.

Как эти два поведения согласуются? Какой механизм объясняет, почему cat петли, а код выше?

На более старой системе RHEL у меня есть /bin/cat не петля для cat x >> x . cat дает сообщение об ошибке «cat: x: input file is output file». Я могу обмануть /bin/cat , выполнив это: cat < x >> x . Когда я пробую ваш код выше, я получаю «цикл», который вы описываете. Я также написал системный вызов, основанный на «cat»:

 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int ac, char **av) { char buf[4906]; int fd, cc; fd = open(av[1], O_RDONLY); while ((cc = read(fd, buf, sizeof(buf))) > 0) if (cc > 0) write(1, buf, cc); close(fd); return 0; } 

Это тоже. Единственная буферизация здесь (в отличие от «mycat» на основе stdio) – это то, что происходит в ядре.

Я думаю, что происходит то, что файловый дескриптор 3 (результат open(av[1]) ) имеет смещение в файле 0. Поданный дескриптор 1 (stdout) имеет смещение 3, потому что «>>» вызывает вызывая оболочку для выполнения lseek() в дескрипторе файла, прежде чем передать ее дочернему процессу cat .

Выполнение read() любого типа, будь то в буфере stdio или в обычном char buf[] продвигает положение дескриптора файла 3. Выполнение write() продвигает позицию дескриптора файла 1. Эти два смещения являются разными числами. Из-за «>>» файловый дескриптор 1 всегда имеет смещение, большее или равное смещению дескриптора файла 3. Таким образом, любая «кошачья» программа будет зацикливаться, если только она не выполняет некоторую внутреннюю буферизацию. Возможно, возможно даже, что реализация stdio FILE * (который является типом символов stdout и f в вашем коде), который включает в себя собственный буфер. fread() может фактически выполнить системный вызов read() чтобы заполнить внутренний буфер fo f . Это может или не может что-либо изменить во внутренних документах stdout . Вызов fwrite() на stdout может или не может изменить что-либо внутри f . Таким образом, «кошка», основанная на stdio, может не зацикливаться. Или это может быть. Трудно сказать, не прочитав много уродливого, уродливого кода libc.

Я сделал strace на cat RHEL – он просто выполняет последовательность системных вызовов read() и write() . Но cat не нужно работать таким образом. Возможно было бы mmap() входной файл, а затем write(1, mapped_address, input_file_size) . Ядро выполнило бы всю работу. Или вы можете сделать системный вызов sendfile() между дескрипторами ввода и вывода файлов в системах Linux. Старые системы SunOS 4.x, по слухам, делали трюк с картографией памяти, но я не знаю, когда-либо делал кошку на основе sendfile. В любом случае «цикл» не будет выполняться, так как для write() и sendfile() требуется параметр length-to-transfer.

Современная реализация cat (sunos-4.0 1988) использует mmap () для отображения всего файла, а затем вызывает 1x write () для этого пространства. Такая реализация не будет зацикливаться, пока виртуальная память позволяет отобразить весь файл.

Для других реализаций это зависит от того, больше ли файл, чем буфер ввода-вывода.

Как написано в ловушках Баша , вы не можете читать из файла и писать ему в том же конвейере.

В зависимости от того, что делает ваш конвейер, файл может быть заблокирован (до 0 байтов или, возможно, до нескольких байтов, равных размеру буфера конвейера вашей операционной системы), или он может расти до тех пор, пока он не заполнит доступное дисковое пространство или не достигнет ограничение размера файла вашей операционной системы или ваша квота и т. д.

Решение заключается в использовании текстового редактора или временной переменной.

У вас есть какое-то состояние гонки между обоими x . Некоторые реализации cat (например, coreutils 8.23) запрещают:

 $ cat x >> x cat: x: input file is output file 

Если это не обнаружено, поведение, очевидно, будет зависеть от реализации (размер буфера и т. Д.).

В коде вы можете попытаться добавить clearerr(f); после fflush , если следующий fread вернет ошибку, если установлен индикатор конца файла.