【问题标题】:Linux non-blocking fifo (on demand logging)Linux 非阻塞 fifo(按需日志记录)
【发布时间】:2011-11-13 16:47:00
【问题描述】:

我喜欢“按需”记录程序输出。例如。输出记录到终端,但另一个进程可以随时挂接当前输出。

经典的方法是:

myprogram 2>&1 | tee /tmp/mylog

随需应变

tail /tmp/mylog

但是,即使在驱动器空间用完之前不使用,这也会创建一个不断增长的日志文件。所以我的尝试是:

mkfifo /tmp/mylog
myprogram 2>&1 | tee /tmp/mylog

随需应变

cat /tmp/mylog

现在我可以随时阅读 /tmp/mylog。但是,在读取 /tmp/mylog 之前,任何输出都会阻塞程序。我喜欢先进先出刷新任何未读回的传入数据。该怎么做?

【问题讨论】:

  • 好吧,虽然有几个答案可以绕过日志记录的非阻塞 fifo 问题(使用 logrotate、screen 等),但对于大多数用途来说效果很好,但原始问题似乎无法通过简单的 bash 魔法来解决。因此,也许正确的答案是“做不到”。赏金用于实现小缺失工具的答案。
  • 看来魔法确实存在;看我的回答。

标签: linux bash logging fifo


【解决方案1】:

受您的问题启发,我编写了一个简单的程序,可以让您这样做:

$ myprogram 2>&1 | ftee /tmp/mylog

它的行为与tee 类似,但将标准输入克隆到标准输出和命名管道(现在是必需的)而不会阻塞。这意味着如果您想以这种方式记录,您可能会丢失日志数据,但我想这在您的场景中是可以接受的。 诀窍是阻止 SIGPIPE 信号并忽略写入损坏的 fifo 时的错误。 当然,这个示例可以通过各种方式进行优化,但到目前为止,我猜它已经完成了工作。

/* ftee - clone stdin to stdout and to a named pipe 
(c) racic@stackoverflow
WTFPL Licence */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int readfd, writefd;
    struct stat status;
    char *fifonam;
    char buffer[BUFSIZ];
    ssize_t bytes;
    
    signal(SIGPIPE, SIG_IGN);

    if(2!=argc)
    {
        printf("Usage:\n someprog 2>&1 | %s FIFO\n FIFO - path to a"
            " named pipe, required argument\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    fifonam = argv[1];

    readfd = open(fifonam, O_RDONLY | O_NONBLOCK);
    if(-1==readfd)
    {
        perror("ftee: readfd: open()");
        exit(EXIT_FAILURE);
    }

    if(-1==fstat(readfd, &status))
    {
        perror("ftee: fstat");
        close(readfd);
        exit(EXIT_FAILURE);
    }

    if(!S_ISFIFO(status.st_mode))
    {
        printf("ftee: %s in not a fifo!\n", fifonam);
        close(readfd);
        exit(EXIT_FAILURE);
    }

    writefd = open(fifonam, O_WRONLY | O_NONBLOCK);
    if(-1==writefd)
    {
        perror("ftee: writefd: open()");
        close(readfd);
        exit(EXIT_FAILURE);
    }

    close(readfd);

    while(1)
    {
        bytes = read(STDIN_FILENO, buffer, sizeof(buffer));
        if (bytes < 0 && errno == EINTR)
            continue;
        if (bytes <= 0)
            break;

        bytes = write(STDOUT_FILENO, buffer, bytes);
        if(-1==bytes)
            perror("ftee: writing to stdout");
        bytes = write(writefd, buffer, bytes);
        if(-1==bytes);//Ignoring the errors
    }
    close(writefd); 
    return(0);
}

你可以用这个标准命令编译它:

$ gcc ftee.c -o ftee

您可以通过运行例如:

快速验证它

$ ping www.google.com | ftee /tmp/mylog

$ cat /tmp/mylog

另请注意 - 这不是多路复用器。一次只能有一个进程执行$ cat /tmp/mylog

【讨论】:

  • 按预期工作。很好!如果事实证明没有仅限 bash 的解决方案,那么您将获得赏金。而且我们必须使您的工具成为 GNU 标准的工具,随每个 vanilla 发行版一起提供......
  • 你想要实现的目标并不常见,这种方法适用于构建应用程序时。对于大多数情况,tail -f logfile.log 工作得很好。
  • 我认为这对于任何无人看管的长期运行程序都非常有用,只要没有问题,它就可以生成大量没有任何意义的 dbeug 输出。例如,考虑单一用途的嵌入式设备。如果系统多年无人看管,日志文件就不是很有用。甚至可能有一个只读文件系统来保护嵌入式功能免受 fs 垃圾和断电的影响。所以日志文件没有意义。
  • 对于嵌入式设备,我现在解决它就像我上面的 BusyBox 回答一样,效果很好。但我并没有将我的问题限制在此类系统上,所以这是一个解决方案。一个非常好的工具!
  • @racic:哇!!!这些天你真的看不到多少C。如果可以的话,我会给你 10 分,所以我也为你的评论 +1。
【解决方案2】:

为了跟随 Fabraxia 的脚步,我将分享我对 racic 代码的小修改。在我的一个用例中,我需要禁止对STDOUT 的写入,因此我添加了另一个参数:swallow_stdout。如果那不是0,那么输出到STDOUT 将被关闭。

由于我不是 C 编码器,我在阅读代码时添加了 cmets,也许它们对其他人有用。

/* ftee - clone stdin to stdout and to a named pipe 
(c) racic@stackoverflow
WTFPL Licence */

// gcc /tmp/ftee.c -o /usr/local/bin/ftee

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int readfd, writefd;        // read & write file descriptors
    struct stat status;         // read file descriptor status
    char *fifonam;              // name of the pipe
    int swallow_stdout;         // 0 = write to STDOUT
    char buffer[BUFSIZ];        // read/write buffer
    ssize_t bytes;              // bytes read/written

    signal(SIGPIPE, SIG_IGN);   

    if(3!=argc)
    {
        printf("Usage:\n someprog 2>&1 | %s [FIFO] [swallow_stdout] \n" 
            "FIFO           - path to a named pipe (created beforehand with mkfifo), required argument\n"
            "swallow_stdout - 0 = output to PIPE and STDOUT, 1 = output to PIPE only, required argument\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    fifonam = argv[1];
    swallow_stdout = atoi(argv[2]);

    readfd = open(fifonam, O_RDONLY | O_NONBLOCK);  // open read file descriptor in non-blocking mode

    if(-1==readfd)  // read descriptor error!
    {
        perror("ftee: readfd: open()");
        exit(EXIT_FAILURE);
    }

    if(-1==fstat(readfd, &status)) // read descriptor status error! (?)
    {
        perror("ftee: fstat");
        close(readfd);
        exit(EXIT_FAILURE);
    }

    if(!S_ISFIFO(status.st_mode)) // read descriptor is not a FIFO error!
    {
        printf("ftee: %s in not a fifo!\n", fifonam);
        close(readfd);
        exit(EXIT_FAILURE);
    }

    writefd = open(fifonam, O_WRONLY | O_NONBLOCK); // open write file descriptor non-blocking
    if(-1==writefd) // write file descriptor error!
    {
        perror("ftee: writefd: open()");
        close(readfd);
        exit(EXIT_FAILURE);
    }

    close(readfd); // reading complete, close read file descriptor

    while(1) // infinite loop
    {
        bytes = read(STDIN_FILENO, buffer, sizeof(buffer)); // read STDIN into buffer
        if (bytes < 0 && errno == EINTR)
            continue;   // skip over errors

        if (bytes <= 0) 
            break; // no more data coming in or uncaught error, let's quit since we can't write anything

        if (swallow_stdout == 0)
            bytes = write(STDOUT_FILENO, buffer, bytes); // write buffer to STDOUT
        if(-1==bytes) // write error!
            perror("ftee: writing to stdout");
        bytes = write(writefd, buffer, bytes); // write a copy of the buffer to the write file descriptor
        if(-1==bytes);// ignore errors
    }
    close(writefd); // close write file descriptor
    return(0); // return exit code 0
}

【讨论】:

    【解决方案3】:

    日志记录可以定向到 UDP 套接字。由于 UDP 是无连接的,它不会阻塞发送程序。当然,如果接收方或网络跟不上,日志会丢失。

    myprogram 2>&1 | socat - udp-datagram:localhost:3333
    

    那么当你想观察日志时:

    socat udp-recv:3333 -
    

    还有其他一些很酷的好处,例如能够同时附加多个侦听器或广播到多个设备。

    【讨论】:

      【解决方案4】:

      如果您的进程写入任何日志文件,然后擦除文件并不时重新启动,因此它不会变得太大,或者使用logrotate

      tail --follow=name --retry my.log
      

      这就是你所需要的。您将获得与终端一样多的回滚。

      不需要任何非标准的东西。我没有用小日志文件尝试过,但我们所有的日志都像这样轮换,我从来没有注意到丢失的行。

      【讨论】:

      • 此方法的一个优点是,如果您在 vi 中打开日志,并且在后台将其删除,则整个文件将保留在磁盘上,直到您完成它为止。如果您减少日志,您可以按照它被附加到。
      【解决方案5】:

      这是一个(非常)旧的线程,但我最近遇到了类似的问题。事实上,我需要的是将 stdin 克隆到 stdout 并复制到非阻塞管道。第一个答案中提议的 ftee 确实有帮助,但(对于我的用例)太不稳定了。这意味着我丢失了如果我及时得到它可以处理的数据。

      我遇到的情况是我有一个进程(some_process),它聚合一些数据并每三秒将其结果写入标准输出。 (简化的)设置看起来像这样(在实际设置中我使用的是命名管道):

      some_process | ftee >(onlineAnalysis.pl > results) | gzip > raw_data.gz
      

      现在,raw_data.gz 必须被压缩并且必须是完整的。 ftee 很好地完成了这项工作。但是我在中间使用的管道太慢了,无法抓取刷新出来的数据 - 但它足够快,可以处理所有事情,如果它可以到达它,这是用普通三通测试的。但是,如果未命名的管道发生任何事情,普通的 tee 会阻塞,并且因为我希望能够按需挂接,所以 tee 不是一个选项。回到正题:中间放一个缓冲就更好了,结果是:

      some_process | ftee >(mbuffer -m 32M| onlineAnalysis.pl > results) | gzip > raw_data.gz
      

      但这仍然会丢失我本可以处理的数据。所以我继续将之前提出的 ftee 扩展为缓冲版本 (bftee)。它仍然具有所有相同的属性,但使用(低效?)内部缓冲区以防写入失败。如果缓冲区已满,它仍然会丢失数据,但它非常适合我的情况。与往常一样,还有很大的改进空间,但是当我从这里复制代码时,我想将其分享给可能有用的人。

      /* bftee - clone stdin to stdout and to a buffered, non-blocking pipe 
          (c) racic@stackoverflow
          (c) fabraxias@stackoverflow
          WTFPL Licence */
      
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          #include <sys/types.h>
          #include <sys/stat.h>
          #include <fcntl.h>
          #include <errno.h>
          #include <signal.h>
          #include <unistd.h>
      
          // the number of sBuffers that are being held at a maximum
          #define BUFFER_SIZE 4096
          #define BLOCK_SIZE 2048
      
          typedef struct {
            char data[BLOCK_SIZE];
            int bytes;
          } sBuffer;
      
          typedef struct {
            sBuffer *data;  //array of buffers
            int bufferSize; // number of buffer in data
            int start;      // index of the current start buffer
            int end;        // index of the current end buffer
            int active;     // number of active buffer (currently in use)
            int maxUse;     // maximum number of buffers ever used
            int drops;      // number of discarded buffer due to overflow
            int sWrites;    // number of buffer written to stdout
            int pWrites;    // number of buffers written to pipe
          } sQueue;
      
          void InitQueue(sQueue*, int);              // initialized the Queue
          void PushToQueue(sQueue*, sBuffer*, int);  // pushes a buffer into Queue at the end 
          sBuffer *RetrieveFromQueue(sQueue*);       // returns the first entry of the buffer and removes it or NULL is buffer is empty
          sBuffer *PeakAtQueue(sQueue*);             // returns the first entry of the buffer but does not remove it. Returns NULL on an empty buffer
          void ShrinkInQueue(sQueue *queue, int);    // shrinks the first entry of the buffer by n-bytes. Buffer is removed if it is empty
          void DelFromQueue(sQueue *queue);          // removes the first entry of the queue
      
          static void sigUSR1(int);                  // signal handled for SUGUSR1 - used for stats output to stderr
          static void sigINT(int);                   // signla handler for SIGKILL/SIGTERM - allows for a graceful stop ?
      
          sQueue queue;                              // Buffer storing the overflow
          volatile int quit;                         // for quiting the main loop
      
          int main(int argc, char *argv[])
          {   
              int readfd, writefd;
              struct stat status;
              char *fifonam;
              sBuffer buffer;
              ssize_t bytes;
              int bufferSize = BUFFER_SIZE;
      
              signal(SIGPIPE, SIG_IGN);
              signal(SIGUSR1, sigUSR1);
              signal(SIGTERM, sigINT);
              signal(SIGINT,  sigINT);
      
              /** Handle commandline args and open the pipe for non blocking writing **/
      
              if(argc < 2 || argc > 3)
              {   
                  printf("Usage:\n someprog 2>&1 | %s FIFO [BufferSize]\n"
                         "FIFO - path to a named pipe, required argument\n"
                         "BufferSize - temporary Internal buffer size in case write to FIFO fails\n", argv[0]);
                  exit(EXIT_FAILURE);
              }
      
              fifonam = argv[1];
              if (argc == 3) {
                bufferSize = atoi(argv[2]);
                if (bufferSize == 0) bufferSize = BUFFER_SIZE;
              }
      
              readfd = open(fifonam, O_RDONLY | O_NONBLOCK);
              if(-1==readfd)
              {   
                  perror("bftee: readfd: open()");
                  exit(EXIT_FAILURE);
              }
      
              if(-1==fstat(readfd, &status))
              {
                  perror("bftee: fstat");
                  close(readfd);
                  exit(EXIT_FAILURE);
              }
      
              if(!S_ISFIFO(status.st_mode))
              {
                  printf("bftee: %s in not a fifo!\n", fifonam);
                  close(readfd);
                  exit(EXIT_FAILURE);
              }
      
              writefd = open(fifonam, O_WRONLY | O_NONBLOCK);
              if(-1==writefd)
              {
                  perror("bftee: writefd: open()");
                  close(readfd);
                  exit(EXIT_FAILURE);
              }
      
              close(readfd);
      
      
              InitQueue(&queue, bufferSize);
              quit = 0;
      
              while(!quit)
              {
                  // read from STDIN
                  bytes = read(STDIN_FILENO, buffer.data, sizeof(buffer.data));
      
                  // if read failed due to interrupt, then retry, otherwise STDIN has closed and we should stop reading
                  if (bytes < 0 && errno == EINTR) continue;
                  if (bytes <= 0) break;
      
                  // save the number if read bytes in the current buffer to be processed
                  buffer.bytes = bytes;
      
                  // this is a blocking write. As long as buffer is smaller than 4096 Bytes, the write is atomic to a pipe in Linux
                  // thus, this cannot be interrupted. however, to be save this should handle the error cases of partial or interrupted write none the less.
                  bytes = write(STDOUT_FILENO, buffer.data, buffer.bytes);
                  queue.sWrites++;
      
                  if(-1==bytes) {
                      perror("ftee: writing to stdout");
                      break;
                  }
      
                  sBuffer *tmpBuffer = NULL;
      
                  // if the queue is empty (tmpBuffer gets set to NULL) the this does nothing - otherwise it tries to write
                  // the buffered data to the pipe. This continues until the Buffer is empty or the write fails.
                  // NOTE: bytes cannot be -1  (that would have failed just before) when the loop is entered. 
                  while ((bytes != -1) && (tmpBuffer = PeakAtQueue(&queue)) != NULL) {
                     // write the oldest buffer to the pipe
                     bytes = write(writefd, tmpBuffer->data, tmpBuffer->bytes);
      
                     // the  written bytes are equal to the buffer size, the write is successful - remove the buffer and continue
                     if (bytes == tmpBuffer->bytes) {
                       DelFromQueue(&queue);
                       queue.pWrites++;
                     } else if (bytes > 0) {
                       // on a positive bytes value there was a partial write. we shrink the current buffer
                       //  and handle this as a write failure
                       ShrinkInQueue(&queue, bytes);
                       bytes = -1;
                     }
                  }
                  // There are several cases here:
                  // 1.) The Queue is empty -> bytes is still set from the write to STDOUT. in this case, we try to write the read data directly to the pipe
                  // 2.) The Queue was not empty but is now -> bytes is set from the last write (which was successful) and is bigger 0. also try to write the data
                  // 3.) The Queue was not empty and still is not -> there was a write error before (even partial), and bytes is -1. Thus this line is skipped.
                  if (bytes != -1) bytes = write(writefd, buffer.data, buffer.bytes);
      
                  // again, there are several cases what can happen here
                  // 1.) the write before was successful -> in this case bytes is equal to buffer.bytes and nothing happens
                  // 2.) the write just before is partial or failed all together - bytes is either -1 or smaller than buffer.bytes -> add the remaining data to the queue
                  // 3.) the write before did not happen as the buffer flush already had an error. In this case bytes is -1 -> add the remaining data to the queue
                  if (bytes != buffer.bytes)
                    PushToQueue(&queue, &buffer, bytes);
                  else 
                    queue.pWrites++;
              }
      
              // once we are done with STDIN, try to flush the buffer to the named pipe
              if (queue.active > 0) {
                 //set output buffer to block - here we wait until we can write everything to the named pipe
                 // --> this does not seem to work - just in case there is a busy loop that waits for buffer flush aswell. 
                 int saved_flags = fcntl(writefd, F_GETFL);
                 int new_flags = saved_flags & ~O_NONBLOCK;
                 int res = fcntl(writefd, F_SETFL, new_flags);
      
                 sBuffer *tmpBuffer = NULL;
                 //TODO: this does not handle partial writes yet
                 while ((tmpBuffer = PeakAtQueue(&queue)) != NULL) {
                   int bytes = write(writefd, tmpBuffer->data, tmpBuffer->bytes);
                   if (bytes != -1) DelFromQueue(&queue);
                 }
              }
      
              close(writefd);
      
          }
      
      
          /** init a given Queue **/
          void InitQueue (sQueue *queue, int bufferSize) {
            queue->data = calloc(bufferSize, sizeof(sBuffer));
            queue->bufferSize = bufferSize;
            queue->start = 0;
            queue->end = 0;
            queue->active = 0;
            queue->maxUse = 0;
            queue->drops = 0;
            queue->sWrites = 0;
            queue->pWrites = 0;
          }
      
          /** push a buffer into the Queue**/
          void PushToQueue(sQueue *queue, sBuffer *p, int offset)
          {
      
              if (offset < 0) offset = 0;      // offset cannot be smaller than 0 - if that is the case, we were given an error code. Set it to 0 instead
              if (offset == p->bytes) return;  // in this case there are 0 bytes to add to the queue. Nothing to write
      
              // this should never happen - offset cannot be bigger than the buffer itself. Panic action
              if (offset > p->bytes) {perror("got more bytes to buffer than we read\n"); exit(EXIT_FAILURE);}
      
              // debug output on a partial write. TODO: remove this line
              // if (offset > 0 ) fprintf(stderr, "partial write to buffer\n");
      
              // copy the data from the buffer into the queue and remember its size
              memcpy(queue->data[queue->end].data, p->data + offset , p->bytes-offset);
              queue->data[queue->end].bytes = p->bytes - offset;
      
              // move the buffer forward
              queue->end = (queue->end + 1) % queue->bufferSize;
      
              // there is still space in the buffer
              if (queue->active < queue->bufferSize)
              {
                  queue->active++;
                  if (queue->active > queue->maxUse) queue->maxUse = queue->active;
              } else {
                  // Overwriting the oldest. Move start to next-oldest
                  queue->start = (queue->start + 1) % queue->bufferSize;
                  queue->drops++;
              }
          }
      
          /** return the oldest entry in the Queue and remove it or return NULL in case the Queue is empty **/
          sBuffer *RetrieveFromQueue(sQueue *queue)
          {
              if (!queue->active) { return NULL; }
      
              queue->start = (queue->start + 1) % queue->bufferSize;
              queue->active--;
              return &(queue->data[queue->start]);
          }
      
          /** return the oldest entry in the Queue or NULL if the Queue is empty. Does not remove the entry **/
          sBuffer *PeakAtQueue(sQueue *queue)
          {
              if (!queue->active) { return NULL; }
              return &(queue->data[queue->start]);
          }
      
          /*** Shrinks the oldest entry i the Queue by bytes. Removes the entry if buffer of the oldest entry runs empty*/
          void ShrinkInQueue(sQueue *queue, int bytes) {
      
            // cannot remove negative amount of bytes - this is an error case. Ignore it
            if (bytes <= 0) return;
      
            // remove the entry if the offset is equal to the buffer size
            if (queue->data[queue->start].bytes == bytes) {
              DelFromQueue(queue);
              return;
            };
      
            // this is a partial delete
            if (queue->data[queue->start].bytes > bytes) {
              //shift the memory by the offset
              memmove(queue->data[queue->start].data, queue->data[queue->start].data + bytes, queue->data[queue->start].bytes - bytes);
              queue->data[queue->start].bytes = queue->data[queue->start].bytes - bytes;
              return;
            }
      
            // panic is the are to remove more than we have the buffer
            if (queue->data[queue->start].bytes < bytes) {
              perror("we wrote more than we had - this should never happen\n");
              exit(EXIT_FAILURE);
              return;
            }
          }
      
          /** delete the oldest entry from the queue. Do nothing if the Queue is empty **/
          void DelFromQueue(sQueue *queue)
          {
              if (queue->active > 0) {
                queue->start = (queue->start + 1) % queue->bufferSize;
                queue->active--;
              }
          }
      
          /** Stats output on SIGUSR1 **/
          static void sigUSR1(int signo) {
            fprintf(stderr, "Buffer use: %i (%i/%i), STDOUT: %i PIPE: %i:%i\n", queue.active, queue.maxUse, queue.bufferSize, queue.sWrites, queue.pWrites, queue.drops);
          }
      
          /** handle signal for terminating **/
          static void sigINT(int signo) {
            quit++;
            if (quit > 1) exit(EXIT_FAILURE);
          }
      

      这个版本需要一个额外的(可选的)参数,它指定要为管道缓冲的块的数量。我的示例调用现在如下所示:

      some_process | bftee >(onlineAnalysis.pl > results) 16384 | gzip > raw_data.gz
      

      导致在丢弃发生之前缓冲 16384 个块。这使用了大约 32 MB 更多的内存,但是......谁在乎呢?

      当然,在实际环境中,我使用的是命名管道,以便我可以根据需要进行附加和分离。看起来是这样的:

      mkfifo named_pipe
      some_process | bftee named_pipe 16384 | gzip > raw_data.gz &
      cat named_pipe | onlineAnalysis.pl > results
      

      此外,该过程对信号的反应如下: SIGUSR1 -> 将计数器打印到 STDERR SIGTERM, SIGINT -> 首先退出主循环并将缓冲区刷新到管道,第二个立即终止程序。

      也许这对将来的某人有帮助... 享受

      【讨论】:

        【解决方案6】:

        似乎 bash &lt;&gt; 重定向运算符 (3.6.10 Opening File Descriptors for Reading and WritingSee) 使得写入文件/fifo 以非阻塞方式打开。 这应该有效:

        $ mkfifo /tmp/mylog
        $ exec 4<>/tmp/mylog
        $ myprogram 2>&1 | tee >&4
        $ cat /tmp/mylog # on demend
        

        gniourf_gniourf 在#bash IRC 频道上给出的解决方案。

        【讨论】:

        • 这实际上是在 fifo 没有阻塞的情况下工作的。 tail 不会在那个 fifo 上工作,也许它会等待 EOF 永远不会出现。 cat 的输出有效,但提供了自上次 cat 以来的任何输出。所以我想知道使用的是什么缓冲区(通常的管道/fifo缓冲区?)以及它会持续多长时间,以及当它被填充时会发生什么。
        • 经过一些 k 后,缓冲区已用完,程序暂停执行,直到 fifo 耗尽。所以这并不能真正解决我的问题。
        【解决方案7】:

        给定的fifo 方法的问题是,当管道缓冲区被填满并且没有读取过程发生时,整个事情都会挂起。

        对于fifo 的工作方法,我认为您必须实现一个命名管道客户端-服务器模型,类似于BASH: Best architecture for reading from two input streams 中提到的模型(请参阅下面稍作修改的代码,示例代码2)。

        对于解决方法,您还可以使用while ... read 构造而不是teeing stdout 到命名管道,方法是在while ... read 循环内实现计数机制,该循环将定期覆盖指定行数的日志文件.这将防止日志文件不断增长(示例代码 1)。

        # sample code 1
        
        # terminal window 1
        rm -f /tmp/mylog
        touch /tmp/mylog
        while sleep 2; do date '+%Y-%m-%d_%H.%M.%S'; done 2>&1 | while IFS="" read -r line; do 
          lno=$((lno+1))
          #echo $lno
          array[${lno}]="${line}"
          if [[ $lno -eq 10 ]]; then
            lno=$((lno+1))
            array[${lno}]="-------------"
            printf '%s\n' "${array[@]}" > /tmp/mylog
            unset lno array
          fi
          printf '%s\n' "${line}"
        done
        
        # terminal window 2
        tail -f /tmp/mylog
        
        
        #------------------------
        
        
        # sample code 2
        
        # code taken from: 
        # https://stackoverflow.com/questions/6702474/bash-best-architecture-for-reading-from-two-input-streams
        # terminal window 1
        
        # server
        (
        rm -f /tmp/to /tmp/from
        mkfifo /tmp/to /tmp/from
        while true; do 
          while IFS="" read -r -d $'\n' line; do 
            printf '%s\n' "${line}"
          done </tmp/to >/tmp/from &
          bgpid=$!
          exec 3>/tmp/to
          exec 4</tmp/from
          trap "kill -TERM $bgpid; exit" 0 1 2 3 13 15
          wait "$bgpid"
          echo "restarting..."
        done
        ) &
        serverpid=$!
        #kill -TERM $serverpid
        
        # client
        (
        exec 3>/tmp/to;
        exec 4</tmp/from;
        while IFS="" read -r -d $'\n' <&4 line; do
          if [[ "${line:0:1}" == $'\177' ]]; then 
            printf 'line from stdin: %s\n' "${line:1}"  > /dev/null
          else       
            printf 'line from fifo: %s\n' "$line"       > /dev/null
          fi
        done &
        trap "kill -TERM $"'!; exit' 1 2 3 13 15
        while IFS="" read -r -d $'\n' line; do
          # can we make it atomic?
          # sleep 0.5
          # dd if=/tmp/to iflag=nonblock of=/dev/null  # flush fifo
          printf '\177%s\n' "${line}"
        done >&3
        ) &
        # kill -TERM $!
        
        
        # terminal window 2
        # tests
        echo hello > /tmp/to
        yes 1 | nl > /tmp/to
        yes 1 | nl | tee /tmp/to
        while sleep 2; do date '+%Y-%m-%d_%H.%M.%S'; done 2>&1 | tee -a /tmp/to
        
        
        # terminal window 3
        cat /tmp/to | head -n 10
        

        【讨论】:

          【解决方案8】:

          嵌入式设备上经常使用的BusyBox可以通过

          syslogd -C
          

          可以用

          填充
          logger
          

          并由

          阅读
          logread
          

          效果很好,但只提供了一个全局日志。

          【讨论】:

            【解决方案9】:

            如果您可以在嵌入式设备上安装 screen,那么您可以在其中运行“myprogram”并将其分离,然后在您想查看日志时随时重新附加它。比如:

            $ screen -t sometitle myprogram
            Hit Ctrl+A, then d to detach it.
            

            当您想查看输出时,重新附加它:

            $ screen -DR sometitle
            Hit Ctrl-A, then d to detach it again.
            

            这样您就完全不用担心程序输出会占用磁盘空间。

            【讨论】:

            • 甚至更好,因为screen 捕获多个屏幕,您可以使用Ctrl-A ESC 输入copy mode 并使用箭头键向上滚动相当多的量。完成后再次使用ESC 退出模式。
            【解决方案10】:

            但是,即使在驱动器空间用完之前不使用,这也会创建一个不断增长的日志文件。

            为什么不定期轮换日志?甚至还有一个程序可以为你做这件事logrotate

            还有一个用于生成日志消息并根据类型对它们执行不同操作的系统。它叫syslog

            您甚至可以将两者结合起来。让您的程序生成 syslog 消息,配置 syslog 以将它们放在一个文件中,并使用 logrotate 确保它们不会填满磁盘。


            如果您正在为一个小型嵌入式系统编写代码并且程序的输出量很大,那么您可以考虑多种技术。

            • 远程系统日志:将系统日志消息发送到网络上的系统日志服务器。
            • 使用 syslog 中可用的严重级别对消息执行不同的操作。例如。丢弃“INFO”但记录并转发“ERR”或更大。例如。安慰
            • 在您的程序中使用信号处理程序重新读取 HUP 上的配置,并以这种方式“按需”更改日志生成。
            • 让您的程序在 unix 套接字上侦听并在打开时将消息写下来。您甚至可以通过这种方式在您的程序中实现交互式控制台。
            • 使用配置文件,提供对日志输出的精细控制。

            【讨论】:

            • 嗯,它适用于小型嵌入式系统,程序输出很重。所以我喜欢只在需要时获取数据,而不是存储任何东西或很少存储任何东西,而不依赖于常规运行工具。
            • 我想要类似 'dmesg' 的东西,只存储非常有限数量的消息。
            • 在嵌入式设备上,通用二进制 BusyBox 包含一个环形缓冲日志,可以由 'logger' 填充并由 'logread' 读取。效果很好。注意事项:只能使用一个全局日志。
            猜你喜欢
            • 2017-05-17
            • 1970-01-01
            • 2017-04-26
            • 1970-01-01
            • 2017-12-05
            • 1970-01-01
            • 2018-05-31
            • 2014-11-17
            • 1970-01-01
            相关资源
            最近更新 更多