atnodes远程执行grep --color无高亮显示引发的RTFSC

atnodes远程执行grep --color无高亮显示引发的RTFSC

By 马冬亮(凝霜  Loki)

一个人的战争(http://blog.csdn.net/MDL13412)

问题描述

最近半年工作太忙,导致好久都没有更新过博客了。今天半夜要上线应用,吃完饭有一点空闲的时间,碰巧同事询问我使用atnodes远程执行grep -color时没有高亮显示的问题,代码如下所示:

atnodes 'grep 13412 --color=always /home/dongliang.ma/a.txt' xxxx.xxxx.qunar.com

其执行结果如下所示:




上面这段代码使用atnodes远程执行一个grep命令时,预期是将13412高亮显示出来,但是运行结果并不是我们所期望的;下面我在本地执行grep --color操作,显示一下期望的效果:


排查问题

我首先是简单的过了一下atnodes的源码,由于它是perl写的,我就搜索了下print这个函数,发现其并没有转义操作,由此可以确定无法高亮不是atnodes导致的。

由于atnodes是基于ssh协议的,接下来我使用ssh远程执行命令来测试,如下所示:

$ ssh xxxx.xxxx.qunar.com 'grep 13412 --colour /home/dongliang.ma/a.txt'
其依然没有高亮输出,由此可以进一步确定问题不在atnodes上。

既然本地执行的时候可以高亮显示,但是通过ssh不能高亮,那就需要从grep --color的输出来分析问题,我在本地执行:

$ grep 13412 --color a.txt 2>&1 | xxd
0000000: 3233 3133 3431 320a 6173 6466 6173 6466  2313412.asdfasdf
0000010: 6173 6466 6131 3334 3132 0a              asdfa13412.
$ grep 13412 --color a.txt 2>&1 | cat
2313412
asdfasdfasdfa13412
xxd和cat命令的结果显示,grep命令的输出经过管道后,并没有保留用于高亮显示的控制字符,于是问题定位到为何通过管道后,转义字符被过滤掉。

我选择的是直接看GNU Grep源码来解决这个问题,下面我们进入RTFSC环节。

RTFSC

由于GNU Grep用的是CVS进行代码托管,我在尝试co多次后,都已网络无法连接告终,于是我选择在线阅读,下面给出我们要阅读的文件链接:grep.c

拿到源码后,首先要搜索的就是--color这个关键字,不过我并没有搜索到具体的解析语句,不过看到了下面几个代码片段:

      --color[=WHEN],\n\
      --colour[=WHEN]       use markers to highlight the matching strings;\n\
                            WHEN is `always', `never', or `auto'\n\
 Generic disadvantages of this remedy are:
	 -- Some very rare terminals might support SGR but not EL (nobody
	    will use "grep --color" on a terminal that does not support
	    SGR in the first place).
	 -- Having these extra control sequences might somewhat complicate
	    the task of any program trying to parse "grep --color"
	    output in order to extract structuring information from it.
      A specific disadvantage to doing it after SGR START is:
	 -- Even more possible background color flicker (when timing
	    with the monitor's redraw is just right), even when not at the
	    bottom of the screen.
      There are no additional disadvantages specific to doing it after
      SGR END.
static const char *sgr_start = "\33[%sm\33[K";
#define SGR_START  sgr_start
static const char *sgr_end   = "\33[m\33[K";
#define SGR_END    sgr_end
看到这些,我可以确定grep使用了控制字符来进行高亮显示,那么现在的问题就是找到这些高亮字符如何生效,经过一番搜索,我找到了下面这些片段:

static const char *sgr_start = "\33[%sm\33[K";
#define SGR_START  sgr_start
static const char *sgr_end   = "\33[m\33[K";
#define SGR_END    sgr_end

/* SGR utility macros.  */
#define PR_SGR_FMT(fmt, s) do { if (*(s)) printf((fmt), (s)); } while (0)
#define PR_SGR_FMT_IF(fmt, s) \
  do { if (color_option && *(s)) printf((fmt), (s)); } while (0)
#define PR_SGR_START(s)    PR_SGR_FMT(   SGR_START, (s))
#define PR_SGR_END(s)      PR_SGR_FMT(   SGR_END,   (s))
#define PR_SGR_START_IF(s) PR_SGR_FMT_IF(SGR_START, (s))
#define PR_SGR_END_IF(s)   PR_SGR_FMT_IF(SGR_END,   (s))
PR_SGR_START_IF(match_color);
fwrite (b, sizeof (char), match_size, stdout);
PR_SGR_END_IF(match_color);
通过上述代码,我们可以得出,PR_SGR_START_IF和PR_SGR_END_IF其实就是根据color_option和*(s)来决定是否进行匹配的高亮显示,那么接下来我们根据color_option这个线索来查找转义的地方。

经过一番搜索,我定位到了下面这些代码片段:

{"color", optional_argument, NULL, COLOR_OPTION},
{"colour", optional_argument, NULL, COLOR_OPTION},
      case COLOR_OPTION:
        if(optarg) {
          if(!strcasecmp(optarg, "always") || !strcasecmp(optarg, "yes") ||
             !strcasecmp(optarg, "force"))
            color_option = 1;
          else if(!strcasecmp(optarg, "never") || !strcasecmp(optarg, "no") ||
                  !strcasecmp(optarg, "none"))
            color_option = 0;
          else if(!strcasecmp(optarg, "auto") || !strcasecmp(optarg, "tty") ||
                  !strcasecmp(optarg, "if-tty"))
            color_option = 2;
          else
            show_help = 1;
        } else
          color_option = 2;
        if(color_option == 2) {
          if(isatty(STDOUT_FILENO) && getenv("TERM") &&
	     strcmp(getenv("TERM"), "dumb"))
                  color_option = 1;
          else
            color_option = 0;
        }
	break;
上述代码中的 if(isatty(STDOUT_FILENO) && getenv("TERM") && strcmp(getenv("TERM"), "dumb")) 是关键,其会判断stdout对应的fileno是否是真正的终端,如果不是,则将终端控制字符转义,从而导致了我们文章开头所说的没有高亮显示的问题。

解决方案

解决方案非常简单,只需要将--color参数改为--color=always即可。

你可能感兴趣的:(Linux)