Почему части вывода перезаписываются, когда перенаправляются в тот же файл, а не когда на терминал? - PullRequest
2 голосов
/ 16 мая 2019

Я хочу иметь возможность выводить как на стандартный вывод (& stderr), так и на другой файл (файл журнала), не беспокоясь о том, что файл журнала получит поврежденный вывод для случая, когда оболочка перенаправляет стандартный вывод (и / или stderr) на тот же файл журнала.

В моем конкретном случае я попытался проверить статистические биты (man 2 stat) для stdout и для файла журнала, чтобы определить, что они указывают на одно и то же устройство и inode, и в этом случае не открывать () файл журнала, но вместо этого fopen () файл stdout для записи в файл журнала. Именно то, что я имею в виду (.c код): https://github.com/libcheck/check/issues/188#issuecomment-492852881 Это работает как обходной путь.

Вот пример кода .c:

#include <stdio.h>

int main() {
  FILE *f=NULL;
  f = fopen("/tmp/a_out_.log", "w");
  if (NULL == f) {
    fprintf(stderr,"oopsie\n");
  } else {
    fprintf(stdout, "Something");
    fprintf(f," messy ");
    fprintf(f," jessy\n");
    fprintf(stdout, " or another\n");
    fprintf(f,"More stuff\n");
    fclose(f);                                                                                                                  
  }
}

Выполните так (из bash), чтобы увидеть перезаписанный вывод:

$ gcc a.c && { ./a.out >/tmp/a_out_.log ; cat /tmp/a_out_.log ; }
Something or another
uff

Я упростил код .c и сократил его до строк bash, но функциональность (т.е. искаженный вывод) проиллюстрирована точно так же:

Все они показывают правильный вывод:

(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2)
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/tmp/good 2>&1 ; cat /tmp/good
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/dev/stdout 2>/dev/stdout
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/proc/self/fd/1 2>/proc/self/fd/1
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/proc/self/fd/2 2>/proc/self/fd/2

вывод:

1 2 3 4 5 6 7 8 9 10
blah

Но следующий вывод показывает перезаписанный вывод:

(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/tmp/bad 2>/tmp/bad; cat /tmp/bad

(поврежденный) вывод выглядит так:

blah
 4 5 6 7 8 9 10

Реальный пример того, где это происходит (даже с шагами воспроизведения): https://github.com/libcheck/check/issues/188

1 Ответ

1 голос
/ 16 мая 2019

Почему части вывода перезаписываются при перенаправлении в тот же файл, но не при подключении к терминалу?

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

Этого не происходит для терминала, потому что терминалы не доступны для поиска.Как будто они всегда открыты в режиме добавления.Открытие файла журнала в режиме добавления даст половину решения, и в любом случае будет хорошей идеей:

#include <stdio.h>

int main() {
  FILE *f=NULL;
  f = fopen("/tmp/a_out_.log", "a");  // <-- here is the change
  if (NULL == f) {
    fprintf(stderr,"oopsie\n");
  } else {
    fprintf(stdout, "Something");
    fprintf(f," messy ");
    fprintf(f," jessy\n");
    fprintf(stdout, " or another\n");
    fprintf(f,"More stuff\n");
    fclose(f);                                                                                                                  
  }
}

Таким образом, записи в файл f всегда идут в текущий конец файла.независимо от того, что могло быть сделано с файлом другими способами.Однако если вы хотите, чтобы существующий файл журнала был усечен, то вам придется сделать это самостоятельно, в отличие от того, когда вы открываете в режиме записи ("w").

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

...