温馨提示:本文翻译自stackoverflow.com,查看原文请点击:c++ - Capturing stdout to zip and interrupting using CTRL-C gives a corrupted zip file
c++ gzip linux signals stdout

c++ - 将stdout捕获为zip并使用CTRL-C中断会导致损坏的zip文件

发布于 2020-05-04 11:45:02

我正在开发可以全天运行的C ++程序。它输出到stdout,我想压缩此输出。未压缩的输出可以是许多GB。启动的Bourne Shell脚本编译C ++代码并按如下方式启动程序:

./prog | gzip > output.gz

当我使用CTRL-C中断脚本时,.gz文件总是损坏。当我从终端启动程序并使用CTRL-C中断它时,.gz文件也总是损坏。当我在终端上启动该程序并使用Linux killall终止该程序时,.gz文件就可以了。

另一方面,cat <large_file> | gzip > cat.gz可以使用CTRL-C中断终端,并且cat.gz总是可以的。所以我怀疑cat有某种信号处理程序,我也必须在我的C ++程序中实现...但是在网上看到cat实现时,我发现没有类似的东西。不管怎样,我实现了这一点:

void SignalHandler(int aSignum)
{
  exit(0);
}

void Signals()
{
  signal(SIGINT,  SignalHandler);
  signal(SIGKILL, SignalHandler);
  signal(SIGTERM, SignalHandler);
}

...甚至是bsh脚本中的某些内容,但没有任何帮助。CTRL-C之后,gz文件已损坏。

问题:

  • 猫有什么我的程序没有的东西?
  • 如何按顺序使用CTRL-C和zip文件终止脚本/程序?

编辑1

打开使用生成的文件zcat给出了一些输出,但随后: gzip: file.gz: unexpected end of file在Ubuntu的存档管理器中打开它只会弹出一个对话框An error occurred while extracting files.

编辑2

尝试冲洗;没有发现问题的变化。

编辑3

有关此问题的更多信息:末尾(EOCDR)签名

Fix archive (-F) - assume mostly intact archive
    zip warning: bad archive - missing end signature
    zip warning: (If downloaded, was binary mode used?  If not, the
    zip warning:  archive may be scrambled and not recoverable)
    zip warning: Can't use -F to fix (try -FF)

zip error: Zip file structure invalid (file.gz)
maot@HP-Pavilion-dv7:~/temp$ zip -FF file.gz --out file2.gz
Fix archive (-FF) - salvage what can
    zip warning: Missing end (EOCDR) signature - either this archive
                     is not readable or the end is damaged
Is this a single-disk archive?  (y/n): y
  Assuming single-disk archive
Scanning for entries...
    zip warning: zip file empty
maot@HP-Pavilion-dv7:~/temp$ ls -lh file2.gz
-rw------- 1 maot maot 22 feb 15 15:18 file2.gz
maot@HP-Pavilion-dv7:~/temp$ 

编辑4

感谢@Maxim Egorushkin,但是它不起作用。CTRL-C对脚本的中断会在执行脚本prog的信号处理程序之前终止。因此,我无法发送信号,它已经消失了……并且没有输出SignalHandlerprog从命令行开始,输出SignalHandler观察。编:

#include <iostream>
#include <unistd.h>
#include <csignal>

void SignalHandler(int aSignum)
{
  std::cout << "prog: Interrupt signal " << aSignum << " received.\n";
  fflush(nullptr);
  exit(0);
}

int main()
{
  for (int sig = 1; sig <=31; sig++)
  {
    std::cout << " sig " << sig;
    signal(sig,  SignalHandler);
  }

  while (true)
  {
    std::cout << "prog: Sleep ";
    fflush(nullptr);
    usleep(1e4);
  }
}

脚本:

#!/bin/sh

onerror()
{
  echo "onerror(): Started."
  ps -jef | grep prog
  killall -s SIGINT prog
  exit
}

g++ -Wall prog.cpp -o prog

trap onerror 2

prog | gzip > file.gz

结果:

maot@HP-Pavilion-dv7:~/temp$ test.sh 
^Conerror(): Started.
maot     16733 16721 16721  5781  0 16:17 pts/1    00:00:00 grep prog
prog: no process found
maot@HP-Pavilion-dv7:~/temp$ 

编辑5个最低工作方案

Maxim Egorushkin答案的实现。脚本:

#!/bin/sh
g++ -Wall prog.cpp -o prog
prog | setsid gzip > file.gz & wait

编:

#include <iostream>
#include <unistd.h>
#include <csignal>

void SignalHandler(int aSignum)
{
  std::cout << "prog: Interrupt signal " << aSignum << " received.\n";
  exit(0);
}

int main()
{
  signal(SIGINT,  SignalHandler);

  while (true)
  {
    std::cout << "prog: Sleep ";
    usleep(1e4);
  }
}

查看更多

提问者
TradingDerivatives.eu
被浏览
18
Maxim Egorushkin 2020-02-17 01:17

当您按Ctrl + C时,Shell将发送SIGINT管道中最后一个进程,即gzip此处。gzip终止,下次prog写入stdout时接收SIGPIPE

您需要发送SIGINTprog它以刷新它stdout并退出(前提是您像以前那样安装了信号处理程序),以便gzip接收其所有输出然后终止。


您可以按以下方式运行管道:

prog | setsid gzip > file.gz & wait

它使用外壳作业控制功能在后台(该&符号)启动管道然后wait作业终止。将On Ctrl+C SIGINT发送到前台进程,该前台进程是外壳wait程序和同一终端进程组中的所有进程(不同于管道在前台且SIGINT仅发送到管道中的最后一个进程)。prog在那个组中。但是gzip从开始setsid将其放入另一个组中,以便它不接收SIGINT而是在终止时在其stdin关闭时prog终止。