Następujące polecenia bash przechodzą w nieskończoną pętlę:
$ echo hi > x
$ cat x >> x
Mogę zgadywać, że cat
kontynuuje czytanie x
po rozpoczęciu pisania na standardowe wyjście. Mylące jest jednak to, że moja własna testowa implementacja kota wykazuje inne zachowanie:
// 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;
}
Jeśli uruchomię:
$ make mycat
$ echo hi > x
$ ./mycat x >> x
Czyni nie pętla. Ze względu na zachowanie cat
oraz fakt, że mam do płukania stdout
przed fread
nazywa się ponownie, chciałbym się spodziewać ten kod C do dalszego czytania i pisania w cyklu.
Jak spójne są te dwa zachowania? Jaki mechanizm wyjaśnia, dlaczego cat
pętle, podczas gdy powyższy kod nie działa?
shell
files
io-redirection
cat
Tyler
źródło
źródło
cat x >> x
powoduje błąd; jednak polecenie to jest sugerowane w książce Kernighan and Pike's Unix jako ćwiczenie.cat
najprawdopodobniej używa wywołań systemowych zamiast standardowego. W stdio twój program może buforować EOFness. Jeśli zaczynasz od pliku większego niż 4096 bajtów, czy otrzymujesz nieskończoną pętlę?Odpowiedzi:
Na starszego systemu RHEL mam,
/bin/cat
czy nie pętli dlacat x >> x
.cat
wyświetla komunikat o błędzie „cat: x: plik wejściowy to plik wyjściowy”. Mogę oszukać/bin/cat
w ten sposób:cat < x >> x
. Gdy wypróbuję powyższy kod, pojawia się opisany „zapętlenie”. Napisałem też „cat” oparty na wywołaniach systemowych:To również pętle. Jedyne buforowanie tutaj (w przeciwieństwie do „mycat” na stdio) jest tym, co dzieje się w jądrze.
Myślę, że dzieje się tak, że deskryptor pliku 3 (wynik
open(av[1])
) ma przesunięcie do pliku 0. Przesunięty deskryptor 1 (stdout) ma przesunięcie 3, ponieważ „>>” powoduje, że powłoka wywołująca wykonujelseek()
polecenie deskryptor pliku przed przekazaniem go docat
procesu potomnego.Robienie
read()
dowolnego rodzaju, czy to w buforze standardowym, czy zwykłym,char buf[]
przesuwa pozycję deskryptora pliku 3. Robieniewrite()
przesunięcia pozycji deskryptora pliku 1. Te dwa przesunięcia są różnymi liczbami. Z powodu „>>” deskryptor pliku 1 zawsze ma przesunięcie większe lub równe przesunięciu deskryptora pliku 3. Tak więc każdy program „podobny do kota” zapętli się, chyba że wykona jakieś buforowanie wewnętrzne. Możliwe, a może nawet prawdopodobne, że implementacja standarduFILE *
(który jest typem symbolistdout
if
kodu), która zawiera własny bufor.fread()
może faktycznie wykonać wywołanie systemowe,read()
aby wypełnić wewnętrzny bufor fof
. To może, ale nie musi, zmienić niczego w środkustdout
. dzwoniącfwrite()
nastdout
może, ale nie musi, zmieniać niczego w środkuf
. Dlatego „kot” oparty na standardzie może nie zapętlić się. Lub może. Trudno powiedzieć bez przeczytania dużo brzydkiego, brzydkiego kodu libc.Zrobiłem
strace
na RHELcat
- po prostu robi kolejneread()
iwrite()
systemowe wywołania. Alecat
nie musi tak działać.mmap()
Plik wejściowy byłby wtedy możliwywrite(1, mapped_address, input_file_size)
. Jądro wykona całą pracę. Lub możesz wykonaćsendfile()
wywołanie systemowe między deskryptorami plików wejściowych i wyjściowych w systemach Linux. Mówi się, że stare systemy SunOS 4.x wykonują sztuczkę mapowania pamięci, ale nie wiem, czy ktokolwiek kiedykolwiek stworzył kota opartego na plikach send. W obu przypadkach „zapętlenie” nie stało, jak zarównowrite()
isendfile()
wymaga parametru długości do transferu.źródło
fread
wezwanie buforowało flagę EOF, jak sugerował Mark Plotnick. Dowody: [1] Kot Darwina używa czytać, a nie bać się; i [2] wywołania fread Darwina __srefill, które ustawiająfp->_flags |= __SEOF;
w niektórych przypadkach. [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
jestcat -u
- u dla niebuforowana .>>
należy go zaimplementować, wywołującO_APPEND
flagę open () , co powoduje każdy operacja zapisu zapisuje (atomowo) na bieżącym końcu pliku, bez względu na to, jaka była pozycja deskryptora pliku przed odczytem. Takie zachowanie jest konieczne na przykładfoo >> logfile & bar >> logfile
do prawidłowego działania - nie możesz sobie pozwolić na założenie, że pozycja po zakończeniu ostatniego zapisu jest nadal końcem pliku.Nowoczesna implementacja cat (sunos-4.0 1988) używa mmap () do mapowania całego pliku, a następnie wywołuje 1x write () dla tego miejsca. Taka implementacja nie będzie zapętlała się, dopóki pamięć wirtualna pozwoli zmapować cały plik.
W przypadku innych implementacji zależy to od tego, czy plik jest większy niż bufor We / Wy.
źródło
cat
implementacji nie buforuje wyników (-u
domyślnie). Te zawsze będą się zapętlać.Jak napisano w pułapkach Bash , nie możesz czytać z pliku i pisać do niego w tym samym potoku.
Rozwiązaniem jest użycie edytora tekstu lub zmiennej tymczasowej.
źródło
Między nimi jest jakiś warunek wyścigu
x
. Niektóre implementacjecat
(np. Coreutils 8.23) zabraniają:Jeśli nie zostanie to wykryte, zachowanie będzie oczywiście zależeć od implementacji (rozmiar bufora itp.).
W swoim kodzie możesz spróbować dodać znak „
clearerr(f);
po”fflush
, na wypadek gdyby następnyfread
zwrócił błąd, jeśli ustawiony jest wskaźnik końca pliku.źródło
i = i++;
niezdefiniowane zachowanie C , stąd rozbieżność.cat
.