Чи може програма командного рядка запобігти перенаправленню її виводу?


49

Я вже так звик це робити: someprogram >output.file

Я роблю це, коли хочу зберегти вихідний файл, який програма генерує у файл. Я також знаю два варіанти цього перенаправлення IO :

  • someprogram 2>output.of.stderr.file (для stderr)
  • someprogram &>output.stderr.and.stdout.file (для обох stdout + stderr у поєднанні)

Сьогодні я зіткнувся з ситуацією, яку не вважав можливою. Я використовую таку команду xinput test 10і, як очікувалося, у мене є такий вихід:

user @ hostname: ~ $ xinput test 10
натисніть клавішу 30 
випуск ключа 30 
натисніть клавішу 40 
випуск ключа 40 
натисніть клавішу 32 
випуск ключа 32 
клавіша 65 
випуск ключа 65 
натисніть клавішу 61 
випуск ключа 61 
натисніть клавішу 31 
^ С
user @ hostname: ~ $ 

Я очікував, що цей вихід, як завжди, може бути збережений у такий файл, як використання xinput test 10 > output.file. Але коли протилежне моєму очікуванню, файл output.file залишається порожнім. Це також справедливо xinput test 10 &> output.fileлише для того, щоб я не пропустив щось на stdout або stderr.

Я дуже розгублений і тому запитую тут, чи xinputможе програма мати спосіб уникнути перенаправлення результатів?

оновлення

Я подивився на джерело. Здається, вихід генерується цим кодом (див. Фрагмент нижче). Мені здається, вихід буде створений звичайним printf

// у файлі test.c

статичний недійсний print_events (Відображення * dpy)
{
    XEvent подія;

    while (1) {
    XNextEvent (dpy та події);

    // [... тут введено деякі інші типи подій ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int петля;
        Клавіша XDeviceKeyEvent * = (XDeviceKeyEvent *) & подія;

        printf ("ключ% s% d", (Event.type == key_release_type)? "release": "натиснути", key-> keycode);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> axis_data [петля]);
        }
        printf ("\ n");
    } 
    }
}

Я змінив джерело на це (див. Наступний фрагмент нижче), що дозволяє мені мати копію виводу на stderr. Цей вихід я в змозі перенаправити:

 // у файлі test.c

статичний недійсний print_events (Відображення * dpy)
{
    XEvent подія;

    while (1) {
    XNextEvent (dpy та події);

    // [... тут введено деякі інші типи подій ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int петля;
        Клавіша XDeviceKeyEvent * = (XDeviceKeyEvent *) & подія;

        printf ("ключ% s% d", (Event.type == key_release_type)? "release": "натиснути", key-> keycode);
        fprintf (stderr, "key% s% d", (Event.type == key_release_type)? "release": "натиснути", key-> keycode);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> axis_data [петля]);
        }
        printf ("\ n");
    } 
    }
}

Моя ідея в даний час полягає в тому, що, можливо, перенаправлення програми втрачає здатність контролювати події випуску клавіш.

Відповіді:


55

Просто тоді, коли stdout не є терміналом, вихід буферний.

І коли ви натискаєте Ctrl-C, цей буфер втрачається як / якщо він ще не був записаний.

Ви отримуєте таку саму поведінку з будь-яким використанням stdio. Спробуйте, наприклад:

grep . > file

Введіть кілька порожніх рядків і натисніть Ctrl-C, і ви побачите, що файл порожній.

З іншого боку, введіть:

xinput test 10 > file

І наберіть на клавіатурі достатньо, щоб буфер повністю заповнився (щонайменше 4 кб коштує), і ви побачите, що розмір файлу збільшується одночасно шматками 4 к.

З grep, ви можете набрати текст Ctrl-Dдля grepвиходу граціозно після того, як змив його буфер. Бо xinputя не думаю, що існує така можливість.

Зауважте, що за замовчуванням stderrне буферизовано, що пояснює, чому ви маєте іншу поведінкуfprintf(stderr)

Якщо, якщо xinput.cви додасте signal(SIGINT, exit), те, що повідомляється xinputвийти вишукано, коли він отримає SIGINT, ви побачите, що fileвоно більше не порожнє (якщо припустити, що воно не завершиться, оскільки виклик бібліотечних функцій від обробників сигналів не гарантовано безпечно: врахуйте, що може статися, якщо сигнал надходить, поки printf пише в буфер).

Якщо вона доступна, ви можете використовувати stdbufкоманду для зміни stdioповедінки буфера:

stdbuf -oL xinput test 10 > file

На цьому сайті є багато питань, які стосуються відключення буферизації типу stdio, де ви знайдете ще більше альтернативних рішень.


2
WOW :) що зробив трюк. спасибі. Тож врешті-решт моє сприйняття проблеми було неправильним. Нічого не було на місці, щоб стримувати перенаправлення, просто Ctrl-C зупинило його до того, як дані були стерті. дякую
людствоANDpeace

Чи був би спосіб запобігти буферизацію stdout?
людствоANDpeace

1
@Stephane Chazelas: велике спасибі за ваше детальне пояснення. На додаток до того, що ви вже говорили, я дізнався, що можна встановити буфер на небуферований setvbuf(stdout, (char *) NULL, _IONBF, NULL). Можливо, це теж цікавить !?
користувач1146332

4
@ user1146332, так, це було б stdbuf -o0, але при цьому stdbug -oLвідновлюється буферизація рядків, як, коли вихід надходить до терміналу. stdbufзмушує програму зателефонувати setvbufза допомогою LD_PRELOADтрюку.
Стефан Шазелас

ще один робочий пункт: unbuffer test 10 > file( unbufferє частиною expectінструментів)
Олів'є Дулак

23

Команда може безпосередньо записати, щоб не /dev/ttyдопустити регулярного перенаправлення.

$ 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

Ваш приклад робить точку + відповідає на питання. Так, це можливо. Звичайно, це є "несподіваним" і незручним для програм, які роблять це, що, принаймні, обдурило мене, не вважаючи таке можливим. Відповідь користувача1146332 також здається переконливим способом уникнути перенаправлення. Якщо бути справедливим, і оскільки обидва дані відповіді є однаково можливими способами уникнути перенаправлення виводу програми командного рядка у файл, я не можу вибрати жоден відповідь, який я думаю :(. Мені потрібно дозволити вибрати два відповіді правильно. Чудова робота, Дякую!
людствоANDpeace

1
FTR, якщо ви хочете отримати результат, записаний /dev/ttyна систему Linux, використовуйте script -c ./demo demo.log(від util-linux).
ndim

Якщо ви працюєте не в tty, а натомість у pty, ви можете це зрозуміти, переглянувши procfs (/ proc / $ PID / fd / 0 тощо). Щоб записати у відповідний pty, перейдіть до каталогу fd вашого батьківського процесу та перевірте, чи це симпосилання на / dev / pts / [0-9] +. Потім ви записуєте на цей пристрій (або повторно, якщо це не очко).
dhasenan

9

Схоже, xinputвідхиляє вихід у файл, але не відхиляє вихід у термінал. Для цього, ймовірно, xinputвикористовуйте системний виклик

int isatty(int fd)

щоб перевірити, чи відноситься поданий сценарій до терміналу чи ні.

Я натрапив на те саме явище деякий час тому з програмою під назвою dpic. Після того, як я заглянув у джерело та деякі налагодження, я видалив рядки, пов’язані із цим, isattyі все працювало так, як очікувалося знову.

Але я згоден з вами, що цей досвід дуже тривожний;)


Я справді думав, що маю своє пояснення. Але (1), дивлячись на джерело (файл test.c у пакеті джерела xinput), isattyтестування не виявлено . Вихід генерується printfфункцією (я думаю, що це стандартний C). Я додав деякі, fprintf(stderr,"output")і це можливо для переадресації + доводить, що весь код дійсно запускається у випадку xinput. Дякую за пропозицію, адже це був перший слід тут.
людствоANDpeace

0

У вашому test.cфайлі ви можете видалити завантажені дані (void)fflush(stdout);безпосередньо за допомогою своїх printfзаяв.

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

У командному рядку ви можете увімкнути буферний висновок, запустивши xinput test 10в псевдотерміналі (pty) scriptкоманду.

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

-1

Так. Я навіть робив це в DOS-часи, коли я програмував на паскалі. Я думаю, що принцип все-таки дотримується:

  1. Закрити виступ
  2. Повторно відкрити stdout як консоль
  3. Напишіть вихід у stdout

Це зламало будь-які труби.


“Re-Open stdout”: stdout визначається як дескриптор файлу 1. Ви можете знову відкрити дескриптор файлу 1, але який би файл ви відкрили? Ви, мабуть, маєте на увазі відкрити термінал, і в цьому випадку не має значення, чи програма пише на fd 1.
Жил "SO-перестаньте бути злим"

@Gilles файл був "con:" наскільки я пам'ятаю - але так, я уточнив пункт 2 у цьому напрямку.
Нілс

con- ім'я DOS для того, що викликає unix /dev/tty, тобто (керуючий) термінал.
Жил 'ТАК - перестань бути злим'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.