Bir komut satırı programı çıktısının yönlendirilmesini engelleyebilir mi?


49

Bunu yapmaya çok alışmıştım: someprogram >output.file

Bir programın bir dosyaya oluşturduğu çıktıyı kaydetmek istediğimde bunu yapıyorum. Bu GÇ yönlendirmesinin iki çeşidinin de farkındayım :

  • someprogram 2>output.of.stderr.file (stderr için)
  • someprogram &>output.stderr.and.stdout.file (her iki stdout + stderr için birleştirilmiş)

Bugün mümkün olmadığını düşündüğüm bir durumla karşılaştım. Aşağıdaki komutu kullanıyorum xinput test 10ve beklendiği gibi aşağıdaki çıktıya sahibim:

user @ hostname: ~ $ xinput testi 10
30 tuşuna basın 
anahtar açma 30 
tuşa basma 40 
tuş açıklaması 40 
tuşa basma 32 
tuş açıklaması 32 
tuşa basma 65 
anahtar açma 65 
tuşa basma 61 
tuş açıklaması 61 
tuşa basma 31 
^ Cı
kullanıcı @ hostname: ~ $ 

Bu çıkışın her zamanki gibi bir dosyaya kaydedilebileceğini tahmin ettim xinput test 10 > output.file. Ancak beklentime aykırı olduğunda output.file dosyası boş kalır. Bu aynı zamanda xinput test 10 &> output.filestdout veya stderr'deki bir şeyi kaçırmadığımdan emin olmak için de geçerlidir .

Gerçekten kafam karıştı ve bu nedenle xinputprogramın çıktısının yeniden yönlendirilmesini engellemenin bir yolu olup olmadığını soruyorum.

Güncelleme

Kaynağa baktım. Çıkışın bu kod tarafından üretildiği anlaşılıyor (aşağıdaki kod parçasına bakın). Bana göre çıktı sıradan bir printf tarafından üretilecek.

// test.c dosyasında

statik boşluk print_events (Ekran * dpy)
{
    XEvent Olayı;

    (1) {
    XNextEvent (dpy, ve Olayı);

    // [... burada diğer bazı etkinlik türleri burada gösteriliyor ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int döngü;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Olay;

        printf ("anahtar% s% d", (Event.type == key_release_type)? "yayın": "basın", tuş-> tuş kodu);

        için (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + döngü, key-> axis_data [loop]);
        }
        Printf ( "\ n");
    } 
    }
}

Kaynağı değiştirdim (aşağıdaki sonraki kod parçasına bakın), bu da stderr'deki çıktının bir kopyasını almama izin veriyor. Bu çıktı yönlendirebilirim:

 // test.c dosyasında

statik boşluk print_events (Ekran * dpy)
{
    XEvent Olayı;

    (1) {
    XNextEvent (dpy, ve Olayı);

    // [... burada diğer bazı etkinlik türleri burada gösteriliyor ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int döngü;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Olay;

        printf ("anahtar% s% d", (Event.type == key_release_type)? "yayın": "basın", tuş-> tuş kodu);
        fprintf (stderr, "anahtar% s% d", (Event.type == key_release_type)? "yayın": "basın", tuş-> tuş kodu);

        için (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + döngü, key-> axis_data [loop]);
        }
        Printf ( "\ n");
    } 
    }
}

Şu andaki fikrim, belki de yönlendirme yaparak, programın, tuşa basma tuş açma olaylarını izleme yeteneğini kaybettiğidir.

Yanıtlar:


55

Sadece stdout bir terminal olmadığında çıktı tamponlanır.

Ve tuşuna bastığınızda Ctrl-C, bu arabellek henüz yazılmamışsa / kaybolur.

Kullandığınız herhangi bir şeyle aynı davranışı elde edersiniz stdio. Örneğin deneyin:

grep . > file

Boş olmayan birkaç satır girin ve tuşuna basın Ctrl-C; dosyanın boş olduğunu göreceksiniz.

Diğer taraftan, şunu yazın:

xinput test 10 > file

Ve arabellek dolması için klavyeye yeterince yazın (en az 4k değerinde çıkış) ve bir kerede 4k'luk parçalarla dosyanın boyutunun arttığını göreceksiniz .

İle tamponunu temizledikten sonra zarifçe çıkmak için grepyazabilirsiniz . Çünkü böyle bir seçenek olduğunu sanmıyorum.Ctrl-Dgrepxinput

Varsayılan stderrolarak arabelleğe alınmadığını ve neden farklı bir davranış sergilendiğinizi açıklar.fprintf(stderr)

, Varsa xinput.c, bir ekleme signal(SIGINT, exit), söylemek olduğunu xinputaldığı incelikle zaman çıkmak için SIGINTgöreceğiniz, filene göz önünde bulundurun: Güvenli garanti edilmemektedir sinyal eylemcilerden kütüphane işlevlerini çağıran olarak (bu düşmezse varsayarak artık boş printf arabellek yazarken sinyal gelirse olabilir).

Varsa stdbuf, stdioarabelleğe alma davranışını değiştirmek için bu komutu kullanabilirsiniz :

stdbuf -oL xinput test 10 > file

Orada bu sitede birçok soru devre dışı bırakarak kapak stdion daha da alternatif çözümler bulacaksınız tip belleğe alma.


2
Vay :) bu hile yaptı. teşekkür ederim. Sonuçta, sorunla ilgili algım yanlıştı. Yeniden yönlendirmeyi engelleyecek hiçbir şey yoktu, veriler temizlenmeden önce Ctrl-C'nin durması basitti. teşekkür ederim
humanityANDpeace

Stdout’un tamponlanmasını önlemenin bir yolu var mıydı?
insanlıkANDpeace

1
@Stephane Chazelas: ayrıntılı açıklama için çok teşekkür ederim. Söylediklerinize ek olarak, arabellek arabelleğe alınmamış olarak ayarlanabileceğini öğrendim setvbuf(stdout, (char *) NULL, _IONBF, NULL). Belki bu da ilgi çekicidir!
user1146332

4
@ user1146332, evet, bu ne olurdu stdbuf -o0yapar iken stdbug -oLgeri yüklemeler hat çıkışı bir terminale gittiğinde gibi tamponlama. Bir numara kullanarak stdbufuygulamayı çağırmaya zorlar . setvbufLD_PRELOAD
Stéphane Chazelas,

başka bir işkolda: unbuffer test 10 > file( aletlerin bir unbufferparçası expect)
Olivier Dulac

23

Bir komut, doğrudan /dev/ttyyönlendirmenin gerçekleşmesini engellemek için doğrudan yazabilir .

$ cat demo
#!/bin/ksh
LC_ALL=C TZ=Z date > /dev/tty
$ ./demo >demo.out 2>demo.err
Fri Dec 28 10:31:57  2012
$ ls -l demo*
-rwxr-xr-x 1 jlliagre jlliagre 41 2012-12-28 11:31 demo
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.err
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.out

Örneğin, point + soruyu cevaplıyor. Evet mümkün. Elbette "beklenmedik" ve programların bunu yapması pek de şaşırtıcı değil, bu da en azından böyle bir şeyi düşünmemem için beni kandırdı. User1146332 tarafından verilen cevap, yeniden yönlendirmeyi önlemenin ikna edici bir yoludur. Adil olmak gerekirse ve verilen cevapların her ikisi de eşit olarak mümkün olduğundan, komut satırı program çıktısının bir dosyaya yeniden yönlendirilmesinden kaçınmanın yollarını tahmin ediyorum, tahmin ettiğim cevaplardan hiçbirini seçemiyorum :(. Teşekkür ederim!
humanityANDpeace

1
FTR, /dev/ttybir Linux sistemine yazılan çıktıyı yakalamak istiyorsanız , script -c ./demo demo.log(from util-linux) kullanın .
ndim

Eğer bir tty'de koşmuyorsanız, ancak bir pty'de koşuyorsanız, bunu procfs (/ proc / $ PID / fd / 0 etc) 'ye bakarak bulabilirsiniz. Uygun pty'ye yazmak için ebeveyn işleminizin fd dizinine gidin ve bunun / dev / pts / [0-9] + ile bir bağlantı olup olmadığına bakın. Sonra o cihaza yazarsın (ya da pts değilse tekrarla).
dhasenan

9

xinputBir dosyanın çıktısını reddeder gibi görünür, ancak bir terminale çıkışı reddetmez. Bunu başarmak için muhtemelen xinputsistem çağrısını kullanın

int isatty(int fd)

Açılacak dosya tanıtıcısının bir terminali ifade edip etmediğini kontrol etmek için.

Bir süre önce aynı fenomene rastladım denilen bir programla dpic. Kaynağa ve bazı hata ayıklama işlemlerine baktıktan sonra ilgili satırları kaldırdım isattyve her şey tekrar beklendiği gibi çalıştı.

Ancak bu deneyimin çok rahatsız edici olduğu konusunda size katılıyorum;)


Gerçekten benim açıklamam olduğunu düşündüm. Ancak (1) kaynağa bakıldığında (xinput kaynak paketindeki test.c dosyası) isattyyapılan testlerin görünümü yoktur . Çıkış, printffonksiyon tarafından üretilir (bence standart C'dir). Bazılarını ekledim fprintf(stderr,"output")ve bu yönlendirmek mümkün + xinput durumunda tüm kodun gerçekten çalıştırıldığını ispatlıyor. Sonuçta öneri için teşekkür ederim, buradaki ilk izdi.
insanlıkANDpeace

0

Senin içinde test.cdosyanın kullanmakta tamponlu veri temizleme olabilir (void)fflush(stdout);sizin hemen sonra printfifadeleri.

    // in test.c
    printf("key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //fprintf(stderr,"key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //(void)fflush(NULL);
    (void)fflush(stdout);

Komut satırında, komut satırlı xinput test 10bir terminalde (pty) scriptkomut ile çalıştırarak satır tamponlu çıktısını etkinleştirebilirsiniz .

script -q /dev/null xinput test 10 > file      # FreeBSD, Mac OS X
script -c "xinput test 10" /dev/null > file    # Linux

-1

Evet. Pascal'de programladığımda bile DOS zamanlarda yaptım. Sanırım prensip hala geçerli:

  1. Stdout'u kapat
  2. Stdout'u konsol olarak yeniden açın
  3. Çıktıyı stdout'a yaz

Bu herhangi bir boruyu kırdı.


“Stdout'u Yeniden Aç”: stdout, dosya tanımlayıcı 1 olarak tanımlanır. Dosya tanımlayıcı 1'i yeniden açabilirsiniz, ancak hangi dosyayı açarsınız? Muhtemelen terminali açmayı kastediyorsunuz, bu durumda programın 1. fd'ye yazıp yazmaması önemli değil
kötülük olmayı

@ Gilles dosyası hatırladığım kadarıyla "con:" idi - ama evet, 2. yöne bu yönde rafine ettim.
Nils,

conunix'in çağırdığı şeyin adıdır /dev/tty, yani (kontrol eden) terminal.
Gilles 'SO- kötülük' dur '29
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.