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

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

В моем конкретном случае я попытался проверить статистические биты ( man 2 stat ) для stdout и для файла журнала, чтобы определить, что они указывают на одно и то же устройство и inode, и в этом случае не выполняйте fopen () файл журнала, но вместо этого 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
"); } else { fprintf(stdout, "Something"); fprintf(f," messy "); fprintf(f," jessy
"); fprintf(stdout, " or another
"); fprintf(f,"More stuff
"); 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 ответ


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

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

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

#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
");
  } else {
    fprintf(stdout, "Something");
    fprintf(f," messy ");
    fprintf(f," jessy
");
    fprintf(stdout, " or another
");
    fprintf(f,"More stuff
");
    fclose(f);                                                                                                                  
  }
}

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

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


Есть идеи?

10000