【问题标题】:Capturing stdout to zip and interrupting using CTRL-C gives a corrupted zip file将标准输出捕获到 zip 并使用 CTRL-C 中断会产生损坏的 zip 文件
【发布时间】:2020-05-31 00:12:56
【问题描述】:

我正在开发一个可以运行一整天的 C++ 程序。它输出到标准输出,我想压缩这个输出。未压缩的输出可以是许多 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文件损坏。

问题:

  • cat 有什么我的程序没有的?
  • 如何按顺序使用 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。因此,我无法向它发送信号,它已经消失了......并且没有SignalHandler 的输出。从命令行启动prog 时,观察到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);
  }
}

【问题讨论】:

  • 您对“文件总是损坏”的定义是什么?这是什么意思?
  • 您尝试在exit(0) 之前拨打flush 吗?可能是文件没有完全写入,缓存了一些残差。

标签: c++ linux signals gzip stdout


【解决方案1】:

当您按下 Ctrl+C 时,shell 将 SIGINT 发送到管道中的 last 进程,此处为 gzipgzip 终止,下次prog 写入stdout 时,它会收到SIGPIPE

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


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

prog | setsid gzip > file.gz & wait

它使用 shell 作业控制功能在后台启动管道(即&amp; 符号)。然后它waits 让工作终止。在Ctrl+C SIGINT 被发送到前台进程,即wait 中的shell 和同一终端进程组中的所有进程(不像管道在前台和SIGINT 仅发送到最后一个进程在管线中)。 prog 在该组中。但是gzipsetsid 开头以将其放入另一个组,因此它不会接收SIGINT,而是在stdin 关闭时终止prog 终止。

【讨论】:

  • 尝试实现您的想法,见上文,但无济于事。可以查一下吗?
  • @TradingDerivatives.eu 为您添加了解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多