编辑:我意识到在扫描我的所有输入后,我尝试运行的程序有一个无限循环。然后无限地打印出来,这样它就永远不会读取 EOF。它将读取最后一个输入,然后进入无限循环
例如,假设该程序是这样的。
printf("%c\n", getch());
while(1)
{
printf("%c\n", getch());
}
有没有办法在读入我的所有输入后终止该程序?我希望能够在程序完成重定向输入文件时终止该程序。 (当程序从输入文件中获取最后一个字符时)注意:我无法修改正在运行的程序,但可以修改运行该程序的脚本。
现在我的脚本中有这个来运行程序。基本上这可以帮助我将输入+输出组合/合并在一起。
编辑:有人帮助我解决了我之前的问题。我本来应该添加||中断此 strace,但我在顶部的编辑中遇到了一个新问题。
mkfifo strace.fifo
{
while read -d, trace; do
if [[ $trace = *"read(0" ]]; then
IFS= read -rn1 answer <&3 || break
echo "$answer" >> in
answer=${answer:-$'\n'}
printf %s "$answer" >&2
printf %s "$answer"
fi
done < strace.fifo 3< input | strace -o strace.fifo -e read stdbuf -o0 ./a.out &
} >> $fname.out 2>&1
所以现在我有一种非常奇怪的方法来尝试通过使用睡眠并终止程序来结束程序。另外,我正在检查到目前为止使用的输入并将其与输入文件进行比较。这只是我正在做的一个小例子,我当前的脚本有多个计时器,并且比较类似于下面的代码。然而,这种方法不是一个好方法,因为如果程序在我输入的任何一秒后都没有完成读取输入文件,它不会杀死程序。在我的真实脚本中,我使用了多个这样的 if 语句,它在 80% 的情况下都有效,但有时它不会做正确的事情,可能是因为计时器和程序运行方式的波动。
sleep .05
awk 'BEGIN{ORS="";} NR==1{print; next;} /^[^ ]/ { print; next; } {print "\n";}' in > temp
diff -w -B temp input > /dev/null
# Check if input done yet
if [ $? -eq 0 ] ; then
# Check if program still running and input done
if [ "$(pidof a.out)" ] ; then
killall -15 a.out > /dev/null 2>&1
fi
fi
所以我想知道输入完成后有没有办法进行kill
处理?strace
我还希望能够将输入和输出合并在一起。
答案1
我认为我有一个更简单的解决方案,但我不确定它的便携性如何。考虑:
$ cat trunc.c
#include <stdio.h>
int main() {
int c;
while(((c = getchar()) != EOF) && ((c & 0xff) != 0xff))
putchar(c);
return 0;
}
这利用了在二进制补码机器上的事实(-1 & 0xff) == 0xff
。这会捕获将 EOF 作为字符打印的程序。我确实测试过:
your_buggy_source_gen < any_ascii_file | trunc
这相当于cat any_ascii_file
假设其中没有值为 0xff 的八位字节。
答案2
“现在我想知道是否有一种方法可以在读取所有输入之前检查程序是否进入无限循环。例如,如果程序进入无限循环,这是停止程序的唯一方法。”
我们已经知道答案这个问题问了近八十年。答案是否定的,没有办法。
感谢您提出一个与您的实际问题并不接近的问题:
“我正在尝试对学生的输出进行评分。我只是想创建一个脚本,以便将来使用。现在,我使用睡眠计时器可以很好地工作,但我希望在没有计时器的情况下使脚本更加健壮”
简单的解决方案:
# how many seconds of CPU (not wall clock)
# should the student's program get?
# 10 seconds is generous for an intro class
# 60 seconds of CPU would be obscene
$ ulimit -t 10
$ run_student_program
# but since I don't have a student program
$ time dd if=/dev/zero of=/dev/null
Killed
real 0m9.998s
user 0m3.080s
sys 0m6.912s
答案3
不完全是 shell 编程技巧,但实际情况是,即使您无法修改其源代码,您也需要修改损坏程序的行为。
一种方法是使用 LD_PRELOAD 模块,该模块拦截“read()”系统调用,以调用实际的“read()”系统调用,然后在遇到文件结束条件时退出。 (理想情况下,我会拦截“getchar()”,但这通常是宏而不是函数调用,因此无法被拦截。)
另一种方法是在调试器下运行代码,找到它进入无限循环的位置,然后通过修改可执行文件或再次应用替换错误函数的 LD_PRELOAD 模块来修补二进制文件。
或者,您可以按照前面的答案建议的那样,将输出通过管道传输到当错误程序输出表明它已到达输入末尾的内容时关闭其输入的内容。 (注意,这依赖于它不是捕获 SIGPIPE,在这种情况下这是一个合理的假设。)
一个更复杂的版本是与另一个程序共享输入文件描述符,该程序不读取它,但监视它是否已到达末尾。然而,这是一件特别棘手的事情,特别是如果您可能正在处理管道、套接字或设备。
假设您的错误程序正在从 stdin 读取数据,请用如下内容将其包装起来:
#!/bin/sh 运行错误程序“$@”& PID=$! perl -MFcntl=:模式 -e ' @S = stat STDIN 或 die "无效输入; $!\n"; S_IFREG($S[2]) 或死亡“输入不是普通文件\n”; while (sysseek(STDIN,0,1) != $S[7]) { 睡眠 1 } ' 杀死$pid 等待
答案4
你确实应该修复损坏的程序,但如果你不能,你可以通过让 shell cat 文件并将其通过管道传输到程序来解决它,然后在退出之前休眠一两秒,从而关闭管道并杀死使用 SIGPIPE 的程序。
(cat file ; sleep 2) | stupid_program