Скопируйте stdout и stderr в файл журнала, покажите на консоли только вывод stderr и запишите сообщения stderr в другой файл

В Unix расширения файлов просто используются в качестве визуальной подсказки, чтобы людям было легче определить, какое приложение использовать для данного типа файла с расширением.xxx или.yyy.

С точки зрения ОС расширения не служат никакой реальной цели, вместо этого ядро ​​может использовать файловые подписи для файлов, с которыми оно связано, например, shebang #!, но в остальном оно довольно не обращает внимания на файловые шестнадцатеричные подписи.

ПРИМЕЧАНИЕ.:Что касается шестнадцатеричных подписей, то это префиксы (в шестнадцатеричном формате )в начале каждого файла. См. эту статью Википедии под названием:Список подписей файлов для некоторых примеров.

ss#1

Эти префиксы иногда также называют магическими числами-Определение магических чисел .

A magic number is a number embedded at or near the beginning of a file that indicates its file format (i.e., the type of file it is). It is also sometimes referred to as a file signature.

Magic numbers are generally not visible to users. However, they can easily be seen with the use of a hex editor, which is a specialized program that shows and allows modification of every byte in a file.

For common file formats, the numbers conveniently represent the names of the file types. Thus, for example, the magic number for image files conforming to the widely used GIF87a format in hexadecimal (i.e., base 16) terms is 0x474946383761, which when converted into ASCII is GIF87a. ASCII is the de facto standard used by computers and communications equipment for character encoding (i.e., associating alphabetic and other characters with numbers).

Пример

Здесь я использую команду hexdumpдля просмотра заголовков файлов.

jpeg -ff d8
$ hexdump -C simplelock.jpg | head -1
00000000  ff d8 ff e0 00 10 4a 46  49 46 00 01 02 00 00 64  |......JFIF.....d|
гз -1ф 8б
$ hexdump -C rdjpgcom.1.gz | head -1
00000000  1f 8b 08 00 00 00 00 00  02 03 65 55 c1 72 22 37  |..........eU.r"7|

ПРИМЕЧАНИЕ.:Дополнительные сведения о том, как ОС определяет тип файла на основе этих магических чисел и шестнадцатеричных подписей, см. в разделе вопросов и ответов U&L под заголовком:Как узнать типы файлов, если не по суффиксу файла? , в котором объясняется, как работает инструмент командной строки fileи процесс, который он использует для определения типа файла.

Подписи файлов на практике

Механизм подписи, используемый в Unix, на самом деле представляет собой лучшее из возможного. Работа по отслеживанию ассоциаций основывается на довольно архаичном наборе шаблонов, которые были разработаны и созданы с течением времени для определения типа файла. Это несовершенно,Таким образом, ответственность за знание того, какой файл относится к какому приложению, возлагается прежде всего на пользователя.

Ссылки

1
26.06.2021, 01:48
2 ответа

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

Вот программа на C, которая сделает то, что вам нужно:

#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

// Some wrappers around system calls to hide error handling
static void Pipe(int pipe_fds[]);
static pid_t Fork();
static void Dup2(int old_fd, int new_fd);
static void Execvp(const char *file, char *const argv[]);

int main(int argc, char* argv[])
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s <script> [<arg>,...]\n", argv[0]);
        return EXIT_FAILURE;
    }

    // Create a pipe to handle stdout
    int out_pipe[2];
    Pipe(out_pipe);

    // Create a second pipe to handle stderr
    int err_pipe[2];
    Pipe(err_pipe);

    if (Fork() == 0) { // Child
        // Wire the child's stdout stream to the write end of out_pipe
        close(out_pipe[0]);
        Dup2(out_pipe[1], STDOUT_FILENO);
        close(out_pipe[1]);

        // Wire the child's stderr stream to the write end of err
        close(err_pipe[0]);
        Dup2(err_pipe[1], STDERR_FILENO);
        close(err_pipe[1]);

        // Invoke the target program with the remaining args
        Execvp(argv[1], argv + 1);
    }

    // Parent only from here on out

    // Close the write ends of the pipes
    close(out_pipe[1]);
    close(err_pipe[1]);

    // Set up the file descriptors and events we're interested in monitoring
    struct pollfd poll_fds[] = {
        {.fd = out_pipe[0],.events = POLLIN },
        {.fd = err_pipe[0],.events = POLLIN },
    };

    int incomplete_fds = 0;

    while (incomplete_fds != 2 && poll(poll_fds, 2, -1) > 0) {
        for (int i = 0; i < 2; ++i) {
            // Is this file descriptor readable?
            if (poll_fds[i].revents & POLLIN) {
                char buffer[4096];
                const ssize_t num_bytes = read(poll_fds[i].fd, buffer, sizeof(buffer));

                // (3) write both stdout and stderr our stderr
                write(STDERR_FILENO, buffer, num_bytes);

                if (i == 1) { // if this was a write to stderr
                    // (1) write standard error to out stdout
                    write(STDOUT_FILENO, buffer, num_bytes);
                }

            }

            if (poll_fds[i].revents & (POLLHUP | POLLNVAL)) {
                // Don't expect anything more from this
                poll_fds[i].events = 0;
                poll_fds[i].fd = -1;
                ++incomplete_fds;
            }
        }
    }

    return EXIT_SUCCESS;
}

static void Pipe(int pipe_fds[])
{
    if (pipe(pipe_fds) < 0) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
}

static pid_t Fork()
{
    const pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    return pid;
}

static void Dup2(const int old_fd, const int new_fd)
{
    if (dup2(old_fd, new_fd) < 0) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
}

static void Execvp(const char *file, char *const argv[])
{
    execvp(file, argv);
    perror("execvp");

    exit(EXIT_FAILURE);
}

Программа принимает приложение, которое вы хотите запустить, в качестве аргумента вместе с любыми аргументами, которые вы хотите передать этому приложению. Он создает два канала: один для захвата содержимого, которое приложение записывает в stdout, а другой — для stderr. Затем он fork()s, соединяет каналы с выходными потоками дочернего элемента, затем вызывает приложение с любыми аргументами.

Затем родительский процесс может читать из этих каналов; он использует poll()для ожидания активности любого из этих файловых дескрипторов. Все, что программа читает из потоков stdoutили stderrприложения, она записывает в stderr.Все, что программа читает из потока stderrприложения, она записывает в stdout(. Это кажется обратным, но упрощает перенаправление ).

Я могу скомпилировать программу на C с помощью

$ gcc prog.c -o pipe_wrapper

Теперь предположим, что у меня есть что-то, что генерирует вывод как для stdout, так и для stderr:

.
#!/bin/bash
# ex.sh

echo stdout
echo stderr 1>&2

Тогда я смогу бежать:

# Note that the content written to stderr appears on the console
$./pipe_wrapper./ex.sh 2> allout.log | tee errout.log
stderr
$

# That content written to stderr is captured in errout.log
$ cat errout.log
stderr
$

# And that everything written to either stdout of stderr is captured
# in allout.log
$ cat allout.log
stdout
stderr
$

Я не могу гарантировать, что это сохранит порядок; если приложение записывает в stderrи stdoutв быстрой последовательности, эта программа может обрабатывать содержимое, записанное в stdout, до того, как содержимое будет записано в stderr.

1
28.07.2021, 11:22

В zsh это может быть foo 2>&2 2> err.log > all.log 2>&1.

В bash это может работать:

foo 2>&1 >> all.log | tee -a all.log err.log >&2

или

{ foo >> all.log; } 2>&1 | tee -a all.log err.log >&2
2
28.07.2021, 11:22

Теги

Похожие вопросы