如何将可执行 C 程序的错误信息重定向到标准输出?(MAC OS X)

Liz*_*Liu 5 osx bash c pipe io-redirection

我想编写一个自动 C 程序检查器。例如,我有一个玩具“hello.c”程序:

#include <stdio.h>

int main()
{
    int a, b;

    while (scanf("%d %d", (&a)-1000000000000000, &b) != EOF)
    {
        printf("%d\n", a+b);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是我的输入文件“1.in”:

1 2
4 5
10 10
2 2
3 2
7 4
Run Code Online (Sandbox Code Playgroud)

和输出文件“1.out”:

3
9
20
4
5
11
Run Code Online (Sandbox Code Playgroud)

我使用“gcc hello.c -o hello.o”来编译并生成一个可执行程序“hello.o”。很明显,程序会遇到“段错误”:(在我的MAC OS X中运行)

$ ./hello.o <1.in
Segmentation fault: 11
Run Code Online (Sandbox Code Playgroud)

但我想使用管道和差异制作一个自动检查器:

./hello.o <1.in | diff - 1.out
Run Code Online (Sandbox Code Playgroud)

输出是:

0a1,6
> 3
> 9
> 20
> 4
> 5
> 11
Run Code Online (Sandbox Code Playgroud)

没有错误信息显示!但我想在终端(MAC OS X)中显示它们。

我尝试将 stderr 重定向到 stdout,例如:

./hello.o <1.in 2>&1 | diff - 1.out
Run Code Online (Sandbox Code Playgroud)

但是没有效果!

我还尝试将 stderr 重定向到如下文件:

./hello.o <1.in 2>log
Run Code Online (Sandbox Code Playgroud)

并且信息“Segmentation fault: 11”显示在终端中,而文件中没有。

当我使用时会发生同样的情况

./hello.o <1.in &>log
Run Code Online (Sandbox Code Playgroud)

也许错误信息不在标准错误中。

那么,我该如何解决这个问题呢?谢谢!

Eli*_*gan 10

您已将程序的标准错误流重定向到标准输出,但这不会重定向“分段错误”消息,因为该消息不是由您的程序发出的。相反,它是由调用程序的 shell 发出的。

您应该在这里做什么取决于您的实际目标是什么。

如果您真的只想将标准错误重定向到标准输出,您可以像使用任何命令一样使用 C 程序执行此操作。重定向之类的2>&1,以及您使用的其他方法,工作得很好。您的程序实际上并没有向标准错误写入任何内容,但是如果您编写了一个这样做的 C 程序,那么您将看到它被重定向。您可以使用fputsorfprintf函数写入stderr. 例如:

fprintf(stderr, "error: refusing to say hello world\n");
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您的目标是在子进程以 终止后将 shell 写入的“分段错误”消息重定向到标准错误SIGSEGV,那么您可以编写一个包含对程序调用的复合命令,并从该复合命令重定向命令。您不必有任何其他命令;正如Gilles 解释的那样,将您的一个命令包含在{ ;}. 例如,您可以运行:

{ ./hello.o 1000; } 2>>outfile
Run Code Online (Sandbox Code Playgroud)

这会将运行程序的所有标准错误输出 - 程序产生的输出(在这种情况下为无)和 shell 产生的输出 - 附加到文件outfile.

(我怀疑,但是,你真的希望只是重定向错误输出,你的程序实际上可能产生,这就是为什么我回答这个问题,而不是萎靡不振它关闭为重复。)


尽管让您的 C 程序写入一个明显虚假的地址似乎是故意触发分段错误的可靠方法,但实际上并非如此。这是因为 C 编译器被允许假设未定义的行为不会发生,并且一些编译器即使在没有使用高级优化进行编译时也会利用这个假设。要测试程序在分段错误下的行为,我建议您SIGSEGV在它运行时向它发送信号。它可以使用该raise函数对自己执行此操作,也可以使用killorkillall命令来执行此操作。

例如,为了测试上面的示例,我刚刚运行,{ sleep 1000; } >&out并在另一个终端中运行killall -SEGV sleep. (由于该sleep命令可能在后台使用,我应该提醒您,您可能不想在生产机器上遵循那个精确的过程,至少不是作为正在执行其他重要工作的用户,当然也不是作为 root。)

最后,您可能不想用.o后缀命名可执行文件,因为这些通常用于编译器生成的目标文件,这些目标文件必须链接在一起才能生成实际可以执行的文件。在类 Unix 操作系统上,可执行二进制文件通常命名为不带扩展名。


iga*_*gal 4

注意:我已替换hello.ohello,因为.o此上下文中的文件扩展名通常表示目标文件而不是最终的可执行程序。

根据您的帖子,您想要运行命令:

./hello <1.in 2>&1 | diff - 1.out
Run Code Online (Sandbox Code Playgroud)

并且您希望运行时的错误消息./hello <1.in出现在该命令的输出中。然而,错误消息不是来自hello.o程序本身,而是来自 shell。我能想到的用一行来近似达到所需效果的最接近的方法是在子 shell 中运行该命令,然后将此输出与您的diff命令一起使用:

2>&1 bash -c './hello <1.in' | diff - 1.out
Run Code Online (Sandbox Code Playgroud)

这给了我们以下输出:

1c1,6
< bash: line 1: 58469 Segmentation fault: 11  ./hello < 1.in
---
> 3
> 9
> 20
> 4
> 5
> 11
Run Code Online (Sandbox Code Playgroud)

唯一的区别是,在这种情况下,您会获得 shell 输出的一些附加元数据(即行号和命令字符串)。如果您想准确地复制错误消息,那么您可以使用trap插入一个钩子来打印出正确的字符串。

我找不到以编程方式提取错误消息的方法,因此我转到Bash 源代码并搜索“分段错误”消息。我在一个名为siglist.c的文件中找到了它,以及一堆其他信号和错误描述。使用该信息我编写了以下脚本:

1c1,6
< bash: line 1: 58469 Segmentation fault: 11  ./hello < 1.in
---
> 3
> 9
> 20
> 4
> 5
> 11
Run Code Online (Sandbox Code Playgroud)

现在,使用此脚本,我们可以运行以下命令:

#!/bin/bash 

# trapdesc.sh
#
#   Take an error code from the `trap` command and
#   print out the corresponding error message.
#
#   Example usage:
#
#       (trap 'bash trapdesc.sh $?' EXIT; <COMMAND>)
#

# List of signal codes and corresponding error messages
#
# Taken from bash source (siglist.c):
#
#   https://github.com/tpruzina/bash/blob/master/siglist.c
#
declare -a SIGNALS=(
"SIGHUP":"Hangup"
"SIGINT":"Interrupt"
"SIGQUIT":"Quit"
"SIGILL":"Illegal instruction"
"SIGTRAP":"BPT trace/trap"
"SIGABRT":"ABORT instruction"
"SIGEMT":"EMT instruction"
"SIGFPE":"Floating point exception"
"SIGKILL":"Killed"
"SIGBUS":"Bus error"
"SIGSEGV":"Segmentation fault"
"SIGSYS":"Bad system call"
"SIGPIPE":"Broken pipe"
"SIGALRM":"Alarm clock"
"SIGTERM":"Terminated"
"SIGURG":"Urgent IO condition"
"SIGSTOP":"Stopped (signal)"
"SIGTSTP":"Stopped"
"SIGCONT":"Continue"
"SIGCLD":"Child death or stop"
"SIGTTIN":"Stopped (tty input)"
"SIGIO":"I/O ready"
"SIGXCPU":"CPU limit"
"SIGXFSZ":"File limit"
"SIGVTALRM":"Alarm (virtual)"
"SIGPROF":"Alarm (profile)"
"SIGWINCH":"Window changed"
"SIGLOST":"Record lock"
"SIGUSR1":"User signal 1"
"SIGUSR2":"User signal 2"
"SIGMSG":"HFT input data pending"
"SIGPWR":"power failure imminent"
"SIGDANGER":"system crash imminent"
"SIGMIGRATE":"migrate process to another CPU"
"SIGPRE":"programming error"
"SIGGRANT":"HFT monitor mode granted"
"SIGRETRACT":"HFT monitor mode retracted"
"SIGSOUND":"HFT sound sequence has completed"
"SIGINFO":"Information request"
)

# Make sure we get an integer 
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
    2>&1 echo "Not a signal identifier: $1"
    exit 1
fi

# Convert the signal from the `trap` function value to the signal ID
sid="$(($1 - 128))"

# Make sure the signal ID is in the valid range
if [[ "${sid}" -lt 0 || "${sid}" -gt 40 ]]; then
    2>&1 echo "Unrecognized signal: ${sid}"
    exit 1
fi

# Get the array-index for the signal
index="$((sid-1))"

# Get the signal description
description="$(echo ${SIGNALS[index]} | cut -d: -f2)"

# Print the error description
echo "${description}: ${sid}"
Run Code Online (Sandbox Code Playgroud)

这会产生与运行相同的字符串./hello <1.in

Segmentation fault: 11
Run Code Online (Sandbox Code Playgroud)

但现在您可以从标准错误(stderr)捕获该字符串并将其通过管道传输到diff您想要的位置:

(trap 'bash trapdesc.sh $?' EXIT; ./hello <1.in)
Run Code Online (Sandbox Code Playgroud)

如果错误消息已写入您最初期望的标准输出,这将产生您将获得的准确输出:

1c1,6
< Segmentation fault: 11
---
> 3
> 9
> 20
> 4
> 5
> 11
Run Code Online (Sandbox Code Playgroud)