捕获标准输出以压缩并使用 CTRL-C 中断会产生损坏的压缩文件
Capturing stdout to zip and interrupting using CTRL-C gives a corrupted zip file
我正在开发一个可以 运行 一整天的 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 文件终止我的 script/program?
编辑 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);
}
}
当您按下 Ctrl+C 时,shell 将 SIGINT
发送到管道中的 last 进程,这里是 gzip
。 gzip
终止,下一次 prog
写入 stdout
它接收 SIGPIPE
。
您需要将 SIGINT
发送到 prog
以使其刷新其 stdout
并退出(前提是您像之前那样安装了信号处理程序),以便 gzip
接收所有输出,然后终止。
您可以 运行 您的管道如下:
prog | setsid gzip > file.gz & wait
它使用 shell 作业控制功能在后台启动管道(即 &
符号)。然后 wait
s 作业终止。在 Ctrl+C
SIGINT
被发送到前台进程,即 wait
中的 shell 和同一终端进程组中的所有进程(不同于管道在前台和SIGINT
仅发送到管道中的最后一个进程)。 prog
在该组中。但是 gzip
以 setsid
开始将其放入另一个组,因此它不会接收 SIGINT
而是在 stdin
关闭时终止 prog
终止。
我正在开发一个可以 运行 一整天的 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 文件终止我的 script/program?
编辑 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);
}
}
当您按下 Ctrl+C 时,shell 将 SIGINT
发送到管道中的 last 进程,这里是 gzip
。 gzip
终止,下一次 prog
写入 stdout
它接收 SIGPIPE
。
您需要将 SIGINT
发送到 prog
以使其刷新其 stdout
并退出(前提是您像之前那样安装了信号处理程序),以便 gzip
接收所有输出,然后终止。
您可以 运行 您的管道如下:
prog | setsid gzip > file.gz & wait
它使用 shell 作业控制功能在后台启动管道(即 &
符号)。然后 wait
s 作业终止。在 Ctrl+C
SIGINT
被发送到前台进程,即 wait
中的 shell 和同一终端进程组中的所有进程(不同于管道在前台和SIGINT
仅发送到管道中的最后一个进程)。 prog
在该组中。但是 gzip
以 setsid
开始将其放入另一个组,因此它不会接收 SIGINT
而是在 stdin
关闭时终止 prog
终止。