Есть ли способ перенаправить stderr в файл, который работает в bash, csh и dash? - PullRequest
4 голосов
/ 09 июля 2019

Как мне перенаправить stderr (или stdout + stderr) в файл, если я не знаю, какая оболочка (bash, csh, dash) интерпретирует мою команду?

Мой код C, работающий в Linux / FreeBSD / OSX, должен вызывать внешнюю программу через функцию system(), которая будет использовать /bin/sh для интерпретации предоставленной командной строки.Я хотел бы захватить сообщения, напечатанные этой внешней программой, в stderr и сохранить их в файл.Проблема в том, что в разных системах /bin/sh указывает на разные оболочки, имеющие разный синтаксис для перенаправления потока stderr в файл.

Самое близкое, что я обнаружил, это то, что bash действительно понимает csh -синтаксис стиля для перенаправления stderr + stdout в файл:

some_program >& output.txt

, но dash, который является оболочкой по умолчанию в Ubuntu (то есть очень распространена), не понимает этот синтаксис.

Существует ли синтаксис для перенаправления stderr, который будет правильно интерпретироваться всеми распространенными оболочками?В качестве альтернативы, есть ли способ указать system() (или какой-либо другой подобной функции C?) Использовать /usr/bin/env bash вместо /bin/sh для интерпретации предоставленной командной строки?

Ответы [ 5 ]

6 голосов
/ 09 июля 2019

Вы ошибочно предполагаете, что /bin/sh может быть "альтернативной" оболочкой, такой как csh, которая несовместима со стандартным синтаксисом оболочки.Если бы у вас была такая система, она была бы неправильно сломана;никакие сценарии оболочки не будут работать.Практически все современные системы пытаются соответствовать, по крайней мере поверхностно, стандарту POSIX, где команда sh обрабатывает язык команд оболочки, указанный в POSIX, который примерно эквивалентен исторической оболочке Bourne и который bash, dash, ash и т. Д. (Оболочки, которые обычно устанавливаются как /bin/sh), совместимы на 99,9% с

. Вы можете полностью игнорировать csh и т. П.Они никогда не устанавливаются как sh, и только тем людям, которые действительно хотят их использовать или которые застряли, используют их в качестве своей интерактивной оболочки, потому что какой-то злой сисадмин, устанавливающий параметры оболочки по умолчанию таким образом, всегда заботится о них.

4 голосов
/ 09 июля 2019

Как перенаправить stderr (или stdout + stderr) в файл, если я не знаю, какая оболочка (bash, csh, dash) интерпретирует мою команду?

Вынет.Оболочки семейства Bourne и оболочки csh имеют разный несовместимый синтаксис для перенаправления stderr.На самом деле, csh и tcsh вообще не имеют синтаксиса для перенаправления только stderr - они могут перенаправлять его только вместе с stdout.

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

Если вы можете рассчитывать на выполнение хотя бы простых команд, то вы можете выполнить известную оболочку в неизвестной для обработки вашей команды, но это не должно быть необходимо дляслучай, который, кажется, вас интересует.

В качестве альтернативы, есть ли способ сказать system () (или какой-либо другой подобной функции C?) использовать / usr / bin / env bash вместо / bin /sh, чтобы интерпретировать предоставленную командную строку?

Не в системе, соответствующей POSIX.POSIX явно указывает, что функция system() выполняет команду с использованием /bin/sh -c [the_command].Но это не должно вызывать беспокойство, так как /bin/sh должно быть соответствующей оболочкой POSIX или, по крайней мере, довольно близко к ней.Определенно, это должна быть оболочка семейства Bourne, которой являются и bash, и dash, но tcsh, безусловно, нет.

Способ перенаправления стандартного потока ошибок в оболочке POSIX заключается виспользуйте оператор перенаправления 2> (который является частным случаем более общей функции перенаправления, применимой к дескриптору файла any ).Какая бы оболочка /bin/sh на самом деле ни была, должна распознавать этот синтаксис, и в частности bash и dash оба делают:

some_program 2> output.txt
3 голосов
/ 09 июля 2019

В любой POSIX-подобной системе вы можете использовать

system("some_program > output.txt 2>&1");

Это потому, что POSIX system эквивалентен вызову sh и POSIX sh поддерживает такой тип перенаправления .Это работает независимо от того, увидит ли пользователь, открывающий терминал в системе, приглашение Csh.

1 голос
/ 11 июля 2019

Если вы не знаете оболочку .... конечно, вы не знаете, как с нее перенаправить, несмотря на то, что вы можете видеть, какое значение имеет $SHELL, и действовать в результате:

char *shell = getenv("SHELL");
if (*shell) { /* no SHELL variable defined */
    /* ... */
} else if (!strcmp(shell, "/bin/sh")) { /* bourne shell */
    /* ... */
} /* ... more shells */

Несмотря на то, что вы говорите в своем вопросе, довольно необычно переименовать /bin/sh в другую оболочку, так как сценарии оболочки используют синтаксис, который зависит от этого. Единственный известный мне случай с bash(1), и я видел это только в Linux (и, что примечательно, в последних версиях Solaris), но синтаксис bash(1) является расширенным набором синтаксиса sh(1), что делает возможным запускать сценарии оболочки, созданные для sh(1). Например, переименование /bin/sh в perl сделает вашу систему полностью непригодной для использования, так как многие системные инструменты зависят от /bin/sh как оболочки, совместимой с bourne.

Кстати, библиотечная функция system(3) всегда вызывает sh(1) в качестве интерпретатора команд, поэтому не должно быть никаких проблем с его использованием, но не существует решения для захвата вывода и его обработки родительским процессом (на самом деле родительский процесс - это sh(1), system(3) fork(2) s)

Еще одна вещь, которую вы можете сделать, - это popen(3) процесс. Этот вызов дает вам FILE указатель на канал процесса. Вы открываете его ввод, если вы popen(3) записываете его, и вы выводите его вывод, если хотите, или читаете его вывод. Посмотрите подробности в руководстве, так как я не знаю, перенаправляет ли он только свой стандартный вывод или перенаправляет также стандартную ошибку (я думаю, что только перенаправляет стандартный вывод по причинам, обсуждаемым ниже, и только если вы popen(3) его с "r" флаг).

FILE *f_in = popen("ps aux", "r");
/* read standard output of 'ps aux' command. */
pclose(f_in);  /* closes the descriptor and waits for the child to finish */

Еще одна вещь, которую вы можете сделать, - это перенаправить себя после fork(2) звонка ребенку и перед вызовом exec(2) (таким образом, вы можете решить, хотите ли вы только stdout или вы также хотите stderr перенаправить обратно Вам):

int fd[2];
int res = pipe(fd);
if (res < 0) {
    perror("pipe");
    exit(EXIT_FAILURE);
}
if ((res = fork()) < 0) {
    perror("fork");
    exit(EXIT_FAILURE);
} else if (res == 0) { /* child process */
    dup2(fd[1], 1); /* redirect pipe to stdout */
    dup2(fd[1], 2); /* redirect pipe also to stderr */
    close(fd[1]); close(fd[0]); /* we don't need these */
    execvp(program, argv);
    perror("execvp");
    exit(EXIT_FAILURE);
} else { /* parent process */
    close(fd[1]); /* we are not going to write in the pipe */
    FILE *f_in = fdopen(fd[0]);
    /* read standard output and standard error from program from f_in FILE descriptor */
    fclose(f_in);
    wait(NULL); /* wait for child to finish */
}

Вы можете увидеть полный пример этого (не читая стандартную ошибку, но ее легко добавить - вам нужно только добавить второй вызов dup2 () сверху) здесь . Программа многократно выполняет команду, которую вы передаете ей в командной строке. Он должен получить доступ к выходным данным подпроцесса для подсчета строк, так как между вызовами программа поднимается на столько строк, сколько выводится программой, чтобы сделать следующий вызов, чтобы перекрыть вывод последнего вызова. Вы можете попробовать и поиграть, внося изменения по своему усмотрению.

Примечание

В вашем примере перенаправления, когда вы используете >&, вам нужно добавить число после амперсанда, чтобы указать, какой дескриптор вы используете dup(). Поскольку число перед > является необязательным, то после & является обязательным. Итак, если вы не использовали его, подготовьтесь к получению ошибки (которую, вероятно, вы не увидите, если перенаправляете stderr). Идея иметь два отдельных выходных дескриптора состоит в том, чтобы позволить вам перенаправить stdout и на в то же время сохраните канал для размещения сообщений об ошибках.

1 голос
/ 10 июля 2019

Я думаю, есть еще одна возможность, о которой стоит упомянуть: вы можете открыть файл, который вы хотите перенаправить на stderr, в вашем c-коде до вызова system().Вы можете сначала dup() исходный stderr, а затем восстановить его.

fflush(stderr); // Flush pending output
int saved_stderr = dup(fileno(stderr));
int fd = open("output.txt", O_RDWR|O_CREAT|O_TRUNC, 0600);
dup2(fd, fileno(stderr));
close(fd);
system("some_program");
dup2(saved_stderr, fileno(stderr));
close(saved_stderr);

Это должно выполнить перенаправление вывода, как вам нужно.

...