Следующие команды bash входят в цикл infinte:
$ echo hi > x
$ cat x >> x
Я могу догадаться, что cat
продолжает читать x
после того, как он начал писать на стандартный вывод. Что сбивает с толку, так это то, что моя собственная тестовая реализация cat демонстрирует другое поведение:
// 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
циклы, а вышеприведенный код нет?
shell
files
io-redirection
cat
Тайлер
источник
источник
cat x >> x
вызывает ошибку; однако, эта команда предлагается в книге Unix Кернигана и Пайка в качестве упражнения.cat
скорее всего использует системные вызовы вместо stdio. С помощью stdio ваша программа может кэшировать EOFness. Если вы начинаете с файла размером более 4096 байт, получается ли бесконечный цикл?Ответы:
В более старой системе RHEL, которая у меня есть, цикл не
/bin/cat
выполняется . выдает сообщение об ошибке «cat: x: input file is output file». Я могу обмануть , делая это . Когда я пробую ваш код выше, я получаю "зацикливание", которое вы описываете. Я также написал системный вызов на основе «кота»:cat x >> x
cat
/bin/cat
cat < x >> x
Это петли тоже. Единственная буферизация здесь (в отличие от "mycat" на основе stdio) - это то, что происходит в ядре.
Я думаю, что происходит то, что файловый дескриптор 3 (результат
open(av[1])
) имеет смещение в файл 0. Файловый дескриптор 1 (stdout) имеет смещение 3, потому что ">>" заставляет вызывающую оболочку делатьlseek()
на дескриптор файла перед передачей егоcat
дочернему процессу.Выполнение
read()
любого вида, будь то в stdio-буфере или обычномchar buf[]
улучшении позиции файлового дескриптора 3. Выполнениеwrite()
улучшении позиции файлового дескриптора 1. Эти два смещения - это разные числа. Из-за «>>» файловый дескриптор 1 всегда имеет смещение, большее или равное смещению файлового дескриптора 3. Таким образом, любая «подобная кошке» программа будет зацикливаться, если она не выполняет некоторую внутреннюю буферизацию. Возможно, возможно даже вероятно, что реализация stdioFILE *
(которая является типом символовstdout
иf
в вашем коде), которая включает в себя свой собственный буфер.fread()
может на самом деле сделать системный вызовread()
для заполнения внутреннего буфера fof
.stdout
. Вызовfwrite()
наstdout
может или не может изменить что-либо внутриf
, Так что основанный на stdio «кот» может не зацикливаться. Или это может. Трудно сказать, не читая много уродливого, уродливого кода libc.Я сделал
strace
на RHELcat
- он просто выполняет последовательностьread()
иwrite()
системные вызовы. Ноcat
не нужно работать таким образом. Это было бы возможно дляmmap()
входного файла, а затем сделатьwrite(1, mapped_address, input_file_size)
. Ядро сделало бы всю работу. Или вы можете сделатьsendfile()
системный вызов между дескрипторами входного и выходного файлов в системах Linux. По слухам, старые системы SunOS 4.x делали трюк с отображением памяти, но я не знаю, делал ли кто-нибудь когда-нибудь кошку на основе sendfile. В любом случае , «зацикливание» бы не произошло, так как обаwrite()
иsendfile()
требуют параметра длины к передаче.источник
fread
вызов кэшировал флаг EOF, как предложил Марк Плотник. Доказательства: [1] кот Дарвина читает, а не читает; и [2] Фред Дарвина вызывает __srefill, который устанавливаетсяfp->_flags |= __SEOF;
в некоторых случаях. [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
являетсяcat -u
- у для небуферизован .>>
должно быть реализовано путем вызова open () сO_APPEND
флагом, который заставляет каждую операцию записи (атомарно) записывать в текущий конец файла независимо от того, какая позиция дескриптора файла была до чтения. Такое поведение необходимоfoo >> logfile & bar >> logfile
, например, для правильной работы - вы не можете позволить себе предположить, что позиция после окончания вашей последней записи по-прежнему остается концом файла.Современная реализация cat (sunos-4.0 1988) использует mmap () для отображения всего файла, а затем вызывает 1x write () для этого пространства. Такая реализация не будет зацикливаться, пока виртуальная память позволяет отобразить весь файл.
Для других реализаций это зависит от того, больше ли файл, чем буфер ввода-вывода.
источник
cat
реализации не буферизуют свой вывод (-u
подразумевается). Те всегда будут петлей.Как написано в подводных камнях Bash , вы не можете читать из файла и записывать в него в том же конвейере.
Решением является либо использование текстового редактора, либо временная переменная.
источник
У вас есть какое-то состояние гонки между ними
x
. Некоторые реализацииcat
(например, coreutils 8.23) запрещают следующее:Если это не обнаружено, поведение, очевидно, будет зависеть от реализации (размер буфера и т. Д.).
В вашем коде вы можете попытаться добавить a
clearerr(f);
послеfflush
, если nextfread
вернет ошибку, если установлен индикатор конца файла.источник
i = i++;
неопределенное поведение C , отсюда и расхождение.cat
.