一个有意思的问题

一个有意思的问题

问题描述

1
2
3
4
5
6
7
8
9
10
11
12
$mysql -N -h127.0.0.1 -e "select id from sbtest1 limit 1"
+--------+
| 100024 |
+--------+

$mysql -N -h127.0.0.1 -e "select id from sbtest1 limit 1" | cat
100024

$mysql -t -N -h127.0.0.1 -e "select id from sbtest1 limit 1" | cat
+--------+
| 100024 |
+--------+

如上第一和第二个语句,为什么mysql client的输出重定向后就没有ascii制表符了呢? 语句三加上 -t后再经过管道,也有制表符了。

这里也有很多人有同样的疑问,不过不但没有给出第三行的解法,更没有人讲清楚这个里面的原理

分析

strace看看第一个语句:

image.png

再对比下第二个语句的strace:

image.png

从上面两个strace比较来看,似乎mysql client能检测到要输出到命名管道(S_IFIFO )还是character device(S_IFCHR),如果是命名管道的话就不要输出制表符了,如果是character device那么就输出ascii制表符。

fstats里面对不同输出目标的说明

1
2
3
4
5
6
7
8
9
10
11
printf("File type:                ");
switch (sb.st_mode & S_IFMT) {
case S_IFBLK: printf("block device\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFDIR: printf("directory\n"); break;
case S_IFIFO: printf("FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); break;
case S_IFREG: printf("regular file\n"); break;
case S_IFSOCK: printf("socket\n"); break;
default: printf("unknown?\n"); break;
}

第4行和第6行两个类型就是导致mysql client选择了不同的输出内容

误解

所以这个问题不是:

为什么mysql client的输出重定向后就没有ascii制表符了呢

而是:

mysql client 可以检测到不同的输出目标然后输出不同的内容吗? 管道或者重定向是一个应用能感知的输出目标吗?

误解:觉得管道写在后面,mysql client不应该知道后面是管道,mysql client输出内容到stdout,然后os将stdout的内容重定向给管道。

实际上mysql是可以检测(detect)输出目标的,如果是管道类的非交互输出那么没必要徒增一些制表符;如果是交互式界面那么就输出一些制表符好看一些。

要是想想在Unix下一切皆文件就更好理解了,输出到管道这个管道也是个文件,所以mysql client是可以感知各种输出文件的属性的。

背后的实现大概是这样:

1
2
3
4
5
6
7
#include <stdio.h>
#include <io.h>
...
if (isatty(fileno(stdout)))
printf( "stdout is a terminal\n" ); // 输出制表符
else
printf( "stdout is a file or a pipe\n"); // 不输出制表符

isatty的解释

结论就是 mysql client根据输出目标的不同(stdout、重定向)输出不同的内容,不过这种做法对用户体感上不是太好。

参考资料

https://www.pyrosoft.co.uk/blog/2014/09/08/how-to-stop-mysql-ascii-tables-column-separators-from-being-lost-when-redirecting-bash-output/

https://www.oreilly.com/library/view/mysql-cookbook/0596001452/ch01s22.html