常用的curl、mysql命令行客户端等程序,进行操作后会进行输出,这都是很正常的程序交互,并没有什么问题。但是,在使用他们的过程中,出现了下面这样的问题:
curl 下载一个文件的命令后续没有任何其他管道符操作时,会默认讲文件输出到命令行,但是添加管道符进行后续操作时,就会输出下载文件的进度统计信息,文件内容则不会输出
首先直接下载,输出全部文件内容:
$ curl https://www.baidu.com/404.html
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
然后,使用管道符进行grep操作:
$ curl https://www.baidu.com/404.html | grep "a"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 227 100 227 0 0 4098 0 --:--:-- --:--:-- --:--:-- 4127
<head>
location.replace(location.href.replace("https://","http://"));
</head>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
这里先输出了下载进度信息,然后进行匹配,输出包含”a”的行。这里的问题就出现了,为什么不使用管道符的时候,下载进度信息没有输出。
这里根据这个现象,推测是curl程序使用stderr输出了下载进度信息,使用stdout输出了文件内容。因此使用管道符的时候接收stdout的文件内容交给grep程序进行匹配,而stderr内容则不交给管道符,因此会先于匹配内容输出;匹配内容是grep程序最后匹配完成之后输出的。
这里的问题又出现了,如果是stderr输出下载进度信息,为什么没有管道符的时候不会输出呢?因此这样推测下去,就只有一种可能,那就是curl程序内部判断了stdout是否通过管道符或者命令行输出。因此,带着这样的困惑,找到了一篇很早的博客,确实有人遇到了同样的问题http://superuser.com/questions/173209/curl-how-to-suppress-strange-output-when-redirecting。
然后查阅了man 手册,发现了isatty函数,这是标准C里面的POXIC协议下的一个函数,用于判断给定文件描述符的文件是否是terminal,函数原型如下:
int isatty(int fd);
基于此,可以使用如下代码在程序中判断stdout是否绑定到终端,从而控制输出不同内容:
#include <unistd.h>
int main(int argc, char ** argv) {
if (isatty(1)) {
std::cout << "output when stdout binds to the terminal." << std::endl;
} else {
std::cout << "output not to a terminal binded stdout." << std::endl;
}
}
在APUE中查询到了习惯信息,该函数用于一个文件描述符是否绑定于一个终端。并给出了该函数的实现:
#include <termios.h>
int isatty(int fd) {
struct termios ts;
return (tcgetattr(fd, &ts) != -1);
}
另外,与此函数相关的还有如下函数:
//返回在给定描述符上打开的终端的文件路径名称
char * ttyname(int fd);
//返回终端的名称,大多控制终端名字都是 /dev/tty,该函数作用主要是改善向其他操作系统的可移植性
char * ctermid(char * str);