Может ли программа командной строки предотвратить перенаправление вывода?


49

Я так привык делать это: someprogram >output.file

Я делаю это всякий раз, когда хочу сохранить вывод, сгенерированный программой, в файл. Мне также известны два варианта этого перенаправления ввода-вывода :

  • 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 
^ C
пользователь @ имя хоста: ~ $ 

Я ожидал, что этот вывод, как обычно, может быть сохранен в файл, как с помощью xinput test 10 > output.file. Но когда, вопреки моим ожиданиям, файл output.file остается пустым. Это также верно xinput test 10 &> output.fileтолько для того, чтобы я не пропустил что-то на stdout или stderr.

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

Обновить

Я посмотрел на источник. Кажется, результат генерируется этим кодом (см. Фрагмент ниже). Мне кажется, что вывод будет сгенерирован обычным printf

// в файле test.c

static void print_events (Display * dpy)
{
    XEvent Event;

    while (1) {
    XNextEvent (dpy, & Event);

    // [... некоторые другие типы событий здесь опущены ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        цикл int;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Event;

        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 [loop]);
        }
        Е ( "\ п");
    } 
    }
}

Я изменил исходный код для этого (см. Следующий фрагмент ниже), который позволяет мне иметь копию вывода на stderr. Этот вывод я могу перенаправить:

 // в файле test.c

static void print_events (Display * dpy)
{
    XEvent Event;

    while (1) {
    XNextEvent (dpy, & Event);

    // [... некоторые другие типы событий здесь опущены ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        цикл int;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Event;

        printf («ключ% s% d», (Event.type == key_release_type)? «release»: «нажать», key-> keycode);
        fprintf (stderr, "ключ% 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 [loop]);
        }
        Е ( "\ п");
    } 
    }
}

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

Ответы:


55

Просто когда stdout не терминал, вывод буферизуется.

И когда вы нажимаете Ctrl-C, этот буфер теряется как / если он еще не был записан.

Вы получаете то же самое поведение с любым использованием stdio. Попробуйте, например:

grep . > file

Введите несколько непустых строк и нажмите Ctrl-C, и вы увидите, что файл пуст.

С другой стороны, введите:

xinput test 10 > file

И наберите достаточно на клавиатуре, чтобы заполнить буфер (по крайней мере, 4 тыс. Выходных данных), и вы увидите, что размер файла увеличивается на 4K за раз.

С помощью grepвы можете набрать Ctrl-Dдля grepкорректного выхода после очистки его буфера. Для xinput, я не думаю , что есть такой вариант.

Обратите внимание, что по умолчанию stderrне буферизируется, что объясняет, почему вы получаете другое поведение сfprintf(stderr)

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

Если он доступен, вы можете использовать stdbufкоманду для изменения stdioрежима буферизации:

stdbuf -oL xinput test 10 > file

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


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

Был бы способ предотвратить буферизацию стандартного вывода?
человечествоANDpeace

1
@Stephane Chazelas: большое спасибо за ваше подробное объяснение. В дополнение к тому, что вы уже сказали, я обнаружил, что можно установить буфер с небуферизованным setvbuf(stdout, (char *) NULL, _IONBF, NULL). Может быть, это тоже интересно!
user1146332 28.12.12

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

Ваш пример делает точку + отвечает на вопрос. Да, это возможно. Это, конечно, «неожиданно» и необычно для программ, которые, по крайней мере, одурачили меня тем, что я не считаю такую ​​возможность возможной. Ответ пользователя user1146332 также кажется убедительным способом избежать перенаправления. Чтобы быть справедливым и поскольку оба приведенных ответа являются одинаково возможными способами избежать перенаправления вывода программы командной строки в файл, я не могу выбрать ни один из ответов, я думаю :(. Мне нужно было бы разрешить выбрать два правильных ответа. Спасибо!
человечествоANDpeace

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

Если вы работаете не в tty, а в pty, вы можете найти это, посмотрев на procfs (/ proc / $ PID / fd / 0 и т. Д.). Чтобы записать в соответствующий pty, перейдите в каталог fd вашего родительского процесса и посмотрите, является ли это символической ссылкой на / dev / pts / [0-9] +. Затем вы пишете на это устройство (или повторяете, если это не оч).
дхасенан

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. Повторно открыть стандартный вывод в качестве консоли
  3. Записать вывод в стандартный вывод

Это сломало любые трубы.


«Re-Open stdout»: stdout определяется как дескриптор файла 1. Вы можете снова открыть дескриптор файла 1, но какой файл вы бы открыли? Вы, вероятно, имеете в виду открыть терминал, и в этом случае не имеет значения, пишет ли программа на fd 1.
Жиль "ТАК - перестать быть злым"

@ Жиль, файл был "con:", насколько я помню - но да, я уточнил пункт 2 в этом направлении.
Нильс

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