ansi控制码

ansi控制码导致的问题

最近有网友在工作中遇到了下面问题,使用win系统跳板机Telnet登录unix网元时,接收的日志中包含了大量的ansi控制码,导致部分内容无法显示,现需要编写程序将ansi控制码中的换行去除。

可以在win下打开下面日志文件,里面有部分内容无法显示: https://pan.baidu.com/s/1UvFfKFr_rhFQn_2NV1NAOA 提取码: 4d9i

要想解决这个问题,需要对ansi控制码有一些基本认识。

下面内容对ansi做了基本的介绍,如果想直接看解决方式的话,可以向下翻到最后的代码部分。

什么是ansi控制码

ansi控制码就是终端上通用的通信控制协议,我们可以在控制台中显示换行等基本操作,除此之外还可以控制字体,例如粗体、斜体、下划线等,也可以显示不同颜色的字符,甚至还能显示简单的动画。

例如执行下面代码,可以从控制台中看到打印的内容是红色且带有下划线的,注意:下图是在IDEA中执行的效果,win中的一些版本里面的命令提示符不支持ansi控制码。

String str = "\033[94;1;4mMonkey1024\033[0m";
System.out.println(str);

ansi控制码的构成和颜色设置

所有序列都以ASCII字符ESC(十进制的27 ,或十六进制 0x1B,或八进制的033,或转义字符\e)开头,第二个字节则是64–95(即ASCII 的@到_)范围内的字符。上面代码中的033对应的就是ESC。上面实例中是堆字符进行了图形化的显示,此时ansi控制码是下面这种方式,其中XXX是可以控制颜色的变化:

\033[XXXm

上面控制码我们使用的94表示蓝色,1表示加粗,4表示下划线,最后面的0表示截止。这些内容可以从下表中查到:

编码 显示内容 说明
0 Reset / Normal all attributes off
1 Bold or increased intensity
2 Faint (decreased intensity) Not widely supported.
3 Italic Not widely supported. Sometimes treated as inverse.
4 Underline
5 Slow Blink less than 150 per minute
6 Rapid Blink MS-DOS ANSI.SYS; 150+ per minute; not widely supported
7 [[reverse video]] swap foreground and background colors
8 Conceal Not widely supported.
9 Crossed-out Characters legible, but marked for deletion. Not widely supported.
10 Primary(default) font
11–19 Alternate font Select alternate font n-10
20 Fraktur hardly ever supported
21 Bold off or Double Underline Bold off not widely supported; double underline hardly ever supported.
22 Normal color or intensity Neither bold nor faint
23 Not italic, not Fraktur
24 Underline off Not singly or doubly underlined
25 Blink off
27 Inverse off
28 Reveal conceal off
29 Not crossed out
30–37 Set foreground color See color table below
38 Set foreground color Next arguments are 5;<n> or 2;<r>;<g>;<b>, see below
39 Default foreground color implementation defined (according to standard)
40–47 Set background color See color table below
48 Set background color Next arguments are 5;<n> or 2;<r>;<g>;<b>, see below
49 Default background color implementation defined (according to standard)
51 Framed
52 Encircled
53 Overlined
54 Not framed or encircled
55 Not overlined
60 ideogram underline hardly ever supported
61 ideogram double underline hardly ever supported
62 ideogram overline hardly ever supported
63 ideogram double overline hardly ever supported
64 ideogram stress marking hardly ever supported
65 ideogram attributes off reset the effects of all of 60-64
90–97 Set bright foreground color aixterm (not in standard)
100–107 Set bright background color aixterm (not in standard)

另外我们可以根据下面的内容设置背景色,下面会打印出白色字体,黑色背景的内容:

String str = "\033[37;40mMonkey1024\033[0mjava";
System.out.println(str);

上面的37表示前景色白色,40表示背景色

色码表:

前景色 背景色 颜色
30 40 Black
31 41 Red
32 42 Green
33 43 Yellow
34 44 Blue
35 45 Magenta
36 46 Cyan
37 47 White
90 100 Bright Black (Gray)
91 101 Bright Red
92 102 Bright Green
93 103 Bright Yellow
94 104 Bright Blue
95 105 Bright Magenta
96 106 Bright Cyan
97 107 Bright White

除此之外我们还可以根据RGB来设置颜色,感兴趣的同学可以看下关于RGB的设置:https://en.wikipedia.org/wiki/ANSI_escape_code

去除ansi控制码中的换行

在对控制码有了基本认识之后,开始动手解决去除换行的问题,经查询得知ansi控制码的换行由下面格式构成:

\033[y:xH

其中y表示行数,即当我们读取了两个控制码,且其中的y值不同的话,则说明发生了换行。整体思路就是读取控制码中y,根据读取的值来判断是否需要换行。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class HandleAnsi {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("windows telnet unix.log");
             FileOutputStream fos = new FileOutputStream("new windows telnet unix.log")) {

            int content = -1;//读取的内容
            int line = 1;//行号
            while ((content = fis.read()) != -1) {
                if (content == 27){//找到\033
                    fis.read();//将[读出来
                    //读取行号
                    int l1 = fis.read();
                    int l2 = fis.read();
                    //行号可能是2位数,由于文件内容不大,所以这里只判断2位数
                    if (';' != l2){
                        l1 = Integer.parseInt(l1 +"" + l2);
                    }
                    //判断是否换行
                    if (line != l1){
                        line = l1;
                        //System.out.print("\n");
                        fos.write("\n".getBytes());
                    }

                    //使用循环将H读出来
                    int temp = 0;
                    while ((temp = fis.read()) != 72);

                }

                //将非ansi控制码写入到文件
                //System.out.print((char)content);
                fos.write(content);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

参考文章:

https://en.wikipedia.org/wiki/ANSI_escape_code

https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences