捕获标准输出以压缩并使用 CTRL-C 中断会给出损坏的 zip 文件

Capturing stdout to zip and interrupting using CTRL-C gives a corrupted zip file

本文关键字:损坏 文件 zip 中断 CTRL-C 标准输出 压缩      更新时间:2023-10-16

我正在开发一个可以全天运行的C++程序。它输出到标准输出,我想压缩这个输出。未压缩的输出可以达到许多 GB。启动 Bourne shell 脚本编译C++代码并启动程序,如下所示:

./prog | gzip > output.gz

当我使用 CTRL-C 中断脚本时,.gz文件总是损坏。 当我从终端启动程序并使用 CTRL-C 中断它时,.gz文件也总是损坏。 当我启动程序终端并使用 Linux killall 终止它时,.gz文件很好。

另一方面,在终端上,可以使用 CTRL-C 和 cat.gz 中断cat <large_file> | gzip > cat.gz总是可以的。所以我怀疑 cat 有某种信号处理程序,我也必须在 C++ 年的程序中实现......但是在网上查看猫的实现,我没有发现类似的东西。无论如何,我实现了这个:

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埃戈鲁什金,但它不起作用。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 个最小工作解决方案

马克西姆·叶戈鲁什金的答案的实施。脚本:

#!/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);
}
}

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

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


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

prog | setsid gzip > file.gz & wait

它使用 shell 作业控制功能在后台启动管道(即&符号(。然后wait终止作业。OnCtrl+CSIGINT被发送到前台进程(即wait中的 shell 和同一终端进程组中的所有进程(与管道位于前台且SIGINT仅发送到管道中的最后一个进程不同(。prog就在那一组。但是gzipsetsid开始,将其放入另一个组中,这样它就不会收到SIGINT而是在prog终止时关闭其stdin时终止。