Cat x >> x neden döngü yapar?


17

Aşağıdaki bash komutları bir infinte döngüsüne girer:

$ echo hi > x
$ cat x >> x

Sanırım stdout'a yazmaya başladıktan sonra da catokumaya devam ediyor x. Bununla birlikte, kafa karıştırıcı olan şey, kedinin kendi test uygulamamın farklı davranışlar göstermesidir:

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

Eğer koşarsam:

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

It does not döngü. Davranışları catve daha stdoutönce temizlediğim gerçeği göz önüne alındığında, freadbu C kodunun bir döngüde okumaya ve yazmaya devam etmesini beklerdim.

Bu iki davranış nasıl tutarlıdır? catYukarıdaki mekanizma neden olmasa da hangi mekanizma neden döngüler açıklıyor ?


Benim için dönüyor. Strace / truss altında çalıştırmayı denediniz mi? Hangi sistemdesiniz?
Stéphane Chazelas

Görünüşe göre BSD kedisinin bu davranışı var ve böyle bir şey denediğimizde GNU kedisi bir hata bildirdi. Bu cevap aynı tartışıyor ve GNU kedim olduğundan ve test edildiğinde hata aldığından BSD kedisi kullandığınıza inanıyorum.
Ramesh

Darwin kullanıyorum. cat x >> xHataya neden olan fikri seviyorum ; ancak bu komut Kernighan ve Pike'ın Unix kitabında alıştırma olarak önerilmektedir.
Tyler

3
catbüyük olasılıkla stdio yerine sistem çağrıları kullanır. Stdio ile programınız EOFness'i önbelleğe alıyor olabilir. 4096 bayttan daha büyük bir dosyayla başlarsanız, sonsuz bir döngü elde edersiniz?
Mark Plotnick

@ MarkPlotnick, evet! Dosya 4 k'dan büyük olduğunda C kodu döngüye girer. Teşekkürler, belki de buradaki tüm fark budur.
Tyler

Yanıtlar:


12

Elimde eski bir RHEL sisteminde, /bin/catdoes not için döngü cat x >> x. cat"cat: x: girdi dosyası çıktı dosyası" hata iletisini verir. Bunu /bin/catyaparak kandırabilirim cat < x >> x. Yukarıdaki kod denemek, ben tarif "döngü" olsun. Ayrıca "cat" tabanlı bir sistem çağrısı yazdım:

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

Bu da döngüler. Burada sadece arabelleğe alma (stdio tabanlı "mycat" aksine) çekirdekte devam ediyor.

Ben ne oluyor dosya tanımlayıcı 3 (sonucu open(av[1])) 0 dosyasına bir mahsup olduğunu düşünüyorum. Dosyalı tanımlayıcı 1 (stdout) 3 ofset vardır, çünkü ">>" çağıran kabuk yapmak için nedenlseek() üzerinde dosya tanımlayıcısını catalt sürece teslim etmeden önce .

Bir Doing read()olsun bir stdion tampon içine, herhangi bir tür ya da düz char buf[]bir Doing dosya tanımlayıcı 3. pozisyonunu gelişmeler write()gelişmeler bu iki uzaklıklar farklı sayılardır dosya tanımlayıcı 1'in konumu. ">>" nedeniyle, dosya tanıtıcısı 1 her zaman dosya tanıtıcısı 3 telafisinden daha büyük veya ona eşit bir sapma değerine sahiptir. Kendi ara belleğini içeren bir FILE *(sembollerin türü stdoutve fkodunuzda) stdio uygulaması olabilir . fread()fiili read()dahili tamponu doldurmak için sistem çağrısı yapabilir f.stdout . Arayan fwrite()üzerindestdoutiçindeki herhangi bir şeyi değiştirebilir veya değiştirmeyebilir f. Yani stdio tabanlı bir "kedi" dönmeyebilir. Veya olabilir. Çok çirkin, çirkin libc kodu okumadan söylemek zor.

Ben straceRHEL üzerinde bir yaptım cat- sadece bir dizi read()ve write()sistem çağrıları yapar. Ama cata'nın bu şekilde çalışması gerekmez. Bu mümkün olacağını mmap()giriş dosyasının, sonra yapın write(1, mapped_address, input_file_size). Çekirdek bütün işi yapardı. Veya sendfile()Linux sistemlerinde giriş ve çıkış dosyası tanımlayıcıları arasında bir sistem çağrısı yapabilirsiniz. Eski SunOS 4.x sistemleri, bellek eşleme hile yapmak için söylentilere sahipti, ancak hiç kimsenin sendfile tabanlı bir kedi yaptığını bilmiyorum. Her iki durumda da, "döngü" her ikisi de olduğu gibi olmaz write()ve sendfile()aktarım uzunluğu parametresi gerektirir.


Teşekkürler. Darwin'de, freadçağrı Mark Plotnick'in önerdiği gibi bir EOF bayrağını önbelleğe almış gibi görünüyor . Kanıt: [1] Darwin kedisi okumayı değil, okumayı kullanır; ve [2] Darwin'in korkusu fp->_flags |= __SEOF;, bazı durumlarda ortaya çıkan __srefill'i çağırır . [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…
Tyler

1
Bu harika - dün bunu ilk kez ben oyladım. O olabilir belirtmekte yarar olabilir sadece için POSIX tanımlı anahtar catolduğunu cat -u- u için tamponsuz .
mikeserv

Aslında >>birlikte) (açık arayarak uygulanacak gerektiğini O_APPENDneden olan bayrak, her dosyanın geçerli ucuna (atomik) yazma yazma işlemi ne olursa olsun dosya tanıtıcı pozisyonu okuma öncesinde oldu. Bu davranış, foo >> logfile & bar >> logfileörneğin düzgün çalışması için gereklidir - kendi son yazmanızın sonundan sonraki konumun hala dosyanın sonu olduğunu varsayamazsınız.
hmakholm Monica

1

Modern bir kedi uygulaması (sunos-4.0 1988) tüm dosyayı eşlemek için mmap () kullanır ve sonra bu alan için 1x write () öğesini çağırır. Sanal bellek tüm dosyayı eşlemeye izin verdiği sürece böyle bir uygulama döngü yapmaz.

Diğer uygulamalar için, dosyanın G / Ç arabelleğinden daha büyük olup olmamasına bağlıdır.


Birçok catuygulama çıktılarını arabelleğe almaz ( -uzımni). Bunlar her zaman dönecek.
Stéphane Chazelas

Solaris 11 (SunOS-5.11), küçük dosyalar için mmap () kullanmıyor gibi görünüyor (yalnızca 32769 bayt büyük veya üzerindeki dosyalar için başvuruyor gibi görünüyor).
Stéphane Chazelas

Doğru -u genellikle varsayılan değerdir. Bir uygulama tüm dosya boyutunu okuyabildiğinden ve bu buf ile sadece bir yazma yapabildiğinden bu bir döngü anlamına gelmez.
schily

Dosya boyutu> olan maksimum Mapsize veya eğer Solaris kedi sadece döngüler ilk fileoffset ise = 0.!
Schily

Solaris 11 ile ne gözlemledim? Eğer başlangıç ​​ofseti! = 0 ise veya dosya boyutu 0 ve 32768 arasındaysa, bir read () döngüsü yapar. PiB dosyaları için bile (seyrek dosyalar üzerinde test edilmiş) read () döngülerini okumuş gibi görünüyor.
Stéphane Chazelas

0

Yazılı olarak Bash tuzaklar , aynı boru hattında buna bir dosya ve yazma okuma yapamıyor.

Boru hattınızın ne yaptığına bağlı olarak, dosya gizlenebilir (0 bayta veya muhtemelen işletim sisteminizin boru hattı arabelleğinin boyutuna eşit sayıda bayta kadar) veya kullanılabilir disk alanını doldurana veya işletim sisteminizin dosya boyutu sınırlaması veya kotanız vb.

Çözüm, metin düzenleyici veya geçici değişken kullanmaktır.


-1

İkisi arasında bir çeşit yarış durumu var x. Bazı uygulamalar cat(örn. Coreutils 8.23) şunları yasaklamaktadır:

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

Bu algılanmazsa, davranış açıkça uygulamaya (arabellek boyutu vb.) Bağlıdır.

Kodunuzda, dosya sonu göstergesi ayarlanmışsa bir sonraki hatanın hata vermesi durumunda, clearerr(f);arkasından bir tane eklemeyi deneyebilirsiniz .fflushfread


İyi bir işletim sisteminin, aynı okuma / yazma komutlarını çalıştıran tek bir iş parçacığı ile tek bir işlem için deterministik davranışa sahip olacağı görülmektedir. Her durumda, davranış benim için belirleyicidir ve ben esas olarak tutarsızlığı soruyorum.
Tyler

@Tyler IMHO, bu durumda net bir spesifikasyon olmadan, yukarıdaki komut bir anlam ifade etmiyor ve determinizm gerçekten önemli değil (buradaki en iyi davranış olan bir hata hariç). Bu biraz C'nin i = i++;tanımlanmamış davranışına benzer, dolayısıyla tutarsızlık.
vinc17

1
Hayır, burada ırk koşulu yok, davranış iyi tanımlanmış. Ancak, dosyanın göreli boyutuna ve tarafından kullanılan arabelleğe bağlı olarak uygulama tanımlıdır cat.
Gilles 'SO- kötü olmayı bırak'

@Gilles Davranışın iyi tanımlanmış / uygulama tanımlanmış olduğunu nereden görüyorsunuz? Biraz referans verebilir misiniz? POSIX kedi belirtimi şöyle der: "-u seçeneği belirtilmezse cat yardımcı programının çıktıyı arabelleğe alıp almayacağı uygulama tarafından tanımlanır." Ancak, bir arabellek kullanıldığında, uygulamanın nasıl kullanılacağını tanımlaması gerekmez; deterministik olmayabilir, örneğin bir tampon rasgele zamanda yıkanır.
vinc17

@ vinc17 Lütfen önceki yorumuma “pratikte” ekleyin. Evet, bu teorik olarak mümkün ve POSIX uyumlu, ancak kimse bunu yapmıyor.
Gilles 'SO- kötü olmayı bırak'
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.