执行无效命令时STDERR输出未被捕获的问题排查
你的代码之所以没捕获到预期的错误输出,核心问题很简单:你只捕获了标准输出(STDOUT),但错误信息是输出到标准错误(STDERR)的,这部分内容根本没进入你创建的管道,自然不会被父进程读取并打印。
具体分析
当你调用execl("/bin/lsx", ...)时,因为/bin/lsx不存在,execl会执行失败并返回-1,随后你的die("execl")会把字符串execl输出到STDERR(因为fprintf(stderr, ...))。而你的子进程只把STDOUT重定向到了管道写端,STDERR仍然指向原来的终端,所以这部分错误信息直接打印到屏幕,不会被父进程的read(link[0], ...)读取到。
另外你提到预期输出是bash: /bin/lsx: no such file or directory,这个是shell执行命令时的提示——当你在bash里输入不存在的命令时,bash会帮你检查命令是否存在并输出这个错误。但你的代码是直接调用exec函数,没有经过shell,所以默认不会有这个提示,除非你自己处理错误或者通过shell来执行命令。
解决方案1:捕获STDERR到管道
修改子进程的代码,把STDERR也重定向到管道的写端,这样无论是命令的正常输出还是错误输出,都会进入管道:
if(pid == 0) { dup2(link[1], STDOUT_FILENO); dup2(link[1], STDERR_FILENO); // 新增这行,将STDERR重定向到管道 close(link[0]); close(link[1]); execl("/bin/lsx", "ls", "-1", (char *)0); die("execl"); }
这样修改后,父进程会读到execl这个错误信息(因为execl失败后触发了die)。
解决方案2:通过shell执行命令(获取你预期的错误提示)
如果你想得到类似bash: /bin/lsx: no such file or directory的提示,可以让shell来帮你执行命令,同时重定向STDERR到管道:
if(pid == 0) { dup2(link[1], STDOUT_FILENO); dup2(link[1], STDERR_FILENO); close(link[0]); close(link[1]); // 调用shell执行命令,这样shell会输出命令不存在的标准提示 execl("/bin/sh", "sh", "-c", "/bin/lsx -lah /", (char *)0); die("execl"); }
这时候子进程会启动一个shell,shell尝试执行/bin/lsx -lah /,发现文件不存在后,就会输出你预期的错误信息到STDERR,而STDERR已经被重定向到管道,父进程就能捕获并打印出来。
额外建议
- 检查
read的返回值:当前代码没有处理read返回-1的情况(比如管道出错),可以加上错误判断,同时确保字符串以终止符结尾:int nbytes = read(link[0], foo, sizeof(foo)-1); // 留一个位置给终止符 if(nbytes == -1) die("read"); foo[nbytes] = '\0'; // 避免printf输出乱码 printf("Output: (%s)\n", foo); - 检查
wait的返回值:可以通过wait获取子进程的退出状态,帮助排查命令执行的具体问题。
内容的提问来源于stack exchange,提问作者xf900

