分类目录归档:Wiki & Howto

Linux JRE中文字体支持

默认情况下,无论Oracle JDK还是OpenJDK,Linux JRE都不提供中文字体支持,这会给那些AWT应用开发者或者基于AWT实现的图表库(jfreechart等)使用者带来一些困扰,本来应该显示中文的地方都被方框代替:

fonts

解决方法可以很简单:

1. 拷贝中文字体到JRE目录。以宋体为例,从一台含中文字体的机器上(Mac下字体在/library/fonts目录,Windows下字体在C:\Windows\Fonts目录)拷贝SimSun.ttf文件到目标机器的$JAVA_HOME/jre/lib/fonts。
2. 重启应用(JVM)。

下面这段程序可以查看当前JRE环境支持哪些字体:

import java.awt.Font;
import java.awt.GraphicsEnvironment;

public class FontTest {

    public static void main(String[] args) {
        Font[] fonts = GraphicsEnvironment
                        .getLocalGraphicsEnvironment().getAllFonts();
        for (Font f : fonts) {
            System.out.println("Name:" + f.getFontName());
        }
    }
}

--EOF--

Tmux应用

Tmux是一个键盘驱动的终端分屏工具,可以替代Linux下的screen。当然,如果是在Mac下使用的话,它的核心功能(例如window,分屏等)也是可以被iTerm2替代的,不过好在Tmux在*nix操作系统中足够通用,用包管理工具(apt-get, brew等)即可安装,所以了解并熟练使用它,还是能为平时终端下的工作节省不少时间的。我从几年前开始使用,但实际上也一直没有形成依赖,因为前面说过了它的可替代性。网上有大堆的所谓Tmux技巧基本上是来自于一本叫『tmux: Productive Mouse-Free Development』的小书,各大网盘也有其电子版下载,抽空通读了一遍,根据示例一步一步个性化Tmux的设置,了解了之前不知道的用法和配置细节。

首先是Tmux的用法简介。

一. session: 会话,Tmux是一个C/S架构的工具,一个会话可以认为是C端和S端一次交互的上下文。我们的所有操作都属于某个session,session可以长时间存在,也可以临时退出再重进。我们可用通过session来区分不同的工作空间,比如本地操作开一个session,远程SSH操作开一个session,又或者SSH生产环境机器开一个session,SSH测试环境机器开一个session。以下是针对session的一些常用操作。

1. open session

1
2
3
$ tmux new-session -s basic
或者
$ tmux new -s basic

-s参数表示session名称,如果不加-s参数,那么Tmux默认会新建一个以数字(下标从0开始)命名的session,并默认打开一个window。打开一个session后,后续的所有控制Tmux本身的快捷键都需要加前缀,默认是Ctrl+b,以下把前缀按键称为Prefix。

2. detach session
想要暂时离开Tmux,回到终端环境时,可以通过快捷键Prefix+d (d for detach)。要注意的时,即使是detach的状态,Tmux中在运行的程序还会继续运行。想要回到Tmux session时,只需执行:

1
$ tmux attach -t basic

-t参数可以指定要attach的session。

3. list session
终端中执行tmux ls (ls for list session)可以列出当前有多少个session。如果已经在session中,执行Prefix+s (s for session)可以列出当前有多少个session,并且可通过上、下键选择要进入的session。

4. kill session
要真正关闭一个session,可以在终端下执行命令tmux kill-session -t basic,其中-t参数表示session名称。

二. window
如果说session是个不可见的东西,那么window就是我们输入、执行命令的地方。一个session可以包含多个window。把window类比成iTerm2中的标签应该就理解了。

1. 创建window
在创建session的时候默认会创建一个以"数字下标+bash"命名的window,并且名称随着bash中执行的不同命令而变化。在新建session时可以通过-n参数指定默认打开的window名称,比如通过tmux new -s basic -n win命名一个win名称的window。也可以随时通过Prefix+,来修改window名称。

2. 切换window
类似标签,我们可以通过一些快捷键在同一个session下的多个window之间切换。比如:

Prefix+p (p for previous):切换到上一个window。
Prefix+n (n for next): 切换到下一个window。
Prefix+0: 切换到0号window,依次类推,1、2、3...
Prefix+w (w for windows): 列出当前session所有window,通过上、下键可以选择切换到指定window。

3. 关闭window
Prefix+&: 关闭当前window。

三. pane
一个window可以切割成多个pane,也就是所谓的分屏,算是Tmux的核心功能之一。

1. 分屏
Prefix+%: 垂直分屏,用一条垂线把当前窗口分成左右两屏。
Prefix+": 水平分屏,用一条水平线把当前窗口分成上下两屏。

2. 切换pane
默认情况下,被选中(激活状态下)的pane会被绿色边框高亮突显出来。
Prefix+o: 依次切换当前窗口下的各个pane。
Prefix+Up|Down|Left|Right: 根据按箭方向选择切换到某个pane。
Prefix+Space(空格键): 对当前窗口下的所有pane重新排列布局,每按一次,换一种样式。
Prefix+z: 最大化当前pane。再按一次后恢复。

3. 关闭pane
Prefix+x: 关闭当前使用中的pane。

关于Tmux的三个核心概念(session、window和pane)及其基本用法已经介绍完毕。接下来的是一些个性化配置和奇技淫巧,包括重新绑定快捷键、自定义快捷键、UI样式、鼠标支持、复制粘贴等等,这些可配置的高级功能也是Tmux受人推崇的原因。Tmux配置文件推荐放在~/.tmux.conf文件中,避免某个用户修改配置影响到其他用户,修改配置文件后要经过reload操作才会在已打开session中生效。

一. 重新绑定快捷键
Tmux的很多默认配置不够友好,需要个人重新定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unbind C-b
set -g prefix C-a
 
bind C-a send-prefix
 
bind r source-file ~/.tmux.conf \; display "tmux.conf reload!"
 
bind | split-window -h
bind - split-window -v
 
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
 
set -g base-index 1
set -g pane-base-index 1

第1-2行表示重新定义Prefix,把默认的Ctrl+b换成Ctrl+a,便于单手操作。
第4行重新定义Ctrl+a组合键,当Prefix + Ctrl+a按下后,等同于原先Ctrl+a功能,解决Ctrl+a被设置为Prefix后已有快捷键失效的问题,也就是说只要按下两次Ctrl+a,就能实现原先终端下回到行首的功能。
第6行定义新的快捷键Prefix+r,重新加载Tmux配置文件,避免每次要进入命令模式reload配置。
第8-9行重新定义分屏快捷键。使用Prefix+|代替Prefix+%实现垂直分屏,使用Prefix+-代替Prefix+"实现水平分屏。|和-的符号本身就可以表示分屏线形状,非常直观。
第11-14行重新定义上下左右方向键,遵循vi习惯。定义以后,任何需要上下左右方向键的场景都可以用hjkl替代。
第16行表示将window的起始下标设为1。因为标准键盘的0在9后面,Prefix + 0/1/2...切换不便。
第17行表示将pane的起始下标设为1。理由同上。

二. 鼠标支持

1
2
3
4
set-window-option -g mode-mouse on
set -g mouse-select-pane on
set -g mouse-resize-pane on
set -g mouse-select-window on

第1行表示启用鼠标。虽然Tmux推荐用键盘完成所有操作,但是对现代开发人员来说,纯键盘操作的习惯并非那么容易养成,因此启用鼠标配置成为标配。
第2行表示支持鼠标选择pane。
第3行表示支持鼠标调整pane大小。
第4行表示支持鼠标选择window。

三. UI样式调整

1
2
3
4
5
6
7
setw -g window-status-current-fg white
setw -g window-status-current-bg red
setw -g window-status-current-attr bright
 
set -g status-justify left
 
setw -g monitor-activity on

第1-3行表示状态栏中window标签的高亮样式,默认是绿底黑字,设置后当前window红底白字显示。
第5行表示状态栏中window列表左对齐排列。
第7行表示非当前window有内容更新时显示在状态栏。

四. 复制粘贴
默认情况下,按Prefix+[进入复制模式,按回车(Enter)退出复制模式。可以通过配置在复制模式中使用vi习惯操作:

1
setw -g mode-keys vi

在复制模式下,按空格键(Space)开始复制,按回车(Enter)完成复制,并退出模式,按Prefix+]粘贴。这些快捷键也可以通过以下配置进行修改,使操作更加靠近vi。

1
2
3
4
5
6
unbind [
bind Escape copy-mode
unbind p
bind p paste-buffer
bind -t vi-copy 'v' begin-selection
bind -t vi-copy 'y' copy-selection

第1-2行表示重新绑定Escape键,Prefix+Escape为进入复制模式。
第3-4行表示重新绑定p键,Prefix+p为粘贴。
第5行表示重新绑定v键,Prefix+v为开始复制。
第6行表示重新绑定y键,Prefix+y为完成复制。

要查看当前复制的内容,可以在Prefix+:后出现的命令行中输入show-buffer,输入list-buffers可以列出所有的复制历史内容。
关于复制粘贴,更深入的话题是Tmux和系统剪贴板之间的交互,Linux可以使用xclip,Mac可以使用tmux-MacOSX-pasteboard,不过我没有试验成功,暂时可以通过ALT + 鼠标复制内容到系统剪贴板。

五. 多屏操作
默认情况下,一个window上只有一个pane被激活,接收键盘交互。但是某些场景下需要在多个pane中执行相同的操作,比如同时修改两台或更多台远程机器的nginx配置,这时候可以在分屏后按Prefix+:进入命令模式,输入set synchronize-panes,即可进入批量操作模式,要退出批量操作模式,再次输入set synchronize-panes即可。

最后上图一张:
Tmux

--EOF--

nginx location匹配规则

nginx location块的语法为:

1
location [ = | ~ | ~* | ^~ | @ ] uri { ... }

此处不考虑@,仅列出=,~,~*,^~和无前缀这五类前缀的匹配规则。

五类前缀的可分成两大类。
1. 普通匹配(literal string): =(精确匹配), ^~和无前缀(最长匹配)。
2. 正则匹配: ~(大小写敏感), ~*(大小写不敏感)。

匹配顺序为:
1. 精确匹配=前缀。如果匹配上,则停止。
2. 进行最长文本字符串匹配。如果最长匹配指令是以^~前缀开头,则停止;如果是无前缀,则继续。
3. 按定义顺序进行正则表达式匹配,如果匹配上,则停止;否则,用步骤2中的无前缀指令。

借用nginx官网上的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
location  = / {
  # matches the query / only.
  [ configuration A ] 
}
location  / {
  # matches any query, since all queries begin with /, but regular
  # expressions and any longer conventional blocks will be
  # matched first.
  [ configuration B ] 
}
location /documents/ {
  # matches any query beginning with /documents/ and continues searching,
  # so regular expressions will be checked. This will be matched only if
  # regular expressions don't find a match.
  [ configuration C ] 
}
location ^~ /images/ {
  # matches any query beginning with /images/ and halts searching,
  # so regular expressions will not be checked.
  [ configuration D ] 
}
location ~* \.(gif|jpg|jpeg)$ {
  # matches any request ending in gif, jpg, or jpeg. However, all
  # requests to the /images/ directory will be handled by
  # Configuration D.   
  [ configuration E ] 
}

/ -> configuration A
/index.html -> configuration B
/documents/document.html -> configuration C
/images/1.gif -> configuration D
/documents/1.jpg -> configuration E

--EOF--

Vim必知必会:初阶

以前我对待Vim的态度不是很友好,因为它的学习曲线实在太陡,并认为其早就应该被更加现代化的文本编辑器取代。直到工作以后,接触了越来越多的服务器端运维操作,无论修改配置文件还是查询日志,没有Vim已经寸步难行,于是渐渐也就适应了。

本系列文章只讲究实用性,所有操作均来自于平日的实践总结,不罗列那些边边角角、充满秀技意味的命令。此为第一篇,关于Vim的初阶用法,熟记下列命令并熟练应用,就已经可以在Vim的江湖里存活了。

1. 移动光标

移动光标可以用hjkl键和左、下、上、右方向键,两者的作用一样,区别在于,hjkl只适用于普通模式下的操作,而方向键适用于各种模式(包括插入模式、命令模式等)。

2. 自由编辑文本

iIaAoO键可以使Vim进入插入模式,它们的区别在于插入位置不同:

* i: 在光标所在位置前插入。
* I (Shift + i): 在当前行首插入。
* a: 在光标所在位置后插入。
* A (Shift + a): 在当前行尾插入。
* o: 在下一行行首插入。
* O (Shift + o): 在上一行行首插入。

插入完毕后,按ESC键回到普通模式。

3. 保存&退出

普通模式下,按 : 进入命令模式:

* w: 保存。
* w!: 强制保存(比如修改只读文件)。
* q: 退出。
* q!: 忽略未保存修改,强制退出。
* wq: 保存后退出。
* wq!: 强制保存后退出。

4. 删除

在普通模式下,
* x: 删除光标所在处字符。
* dd: 删除当前行。

在插入模式下,
* 退格键(backspace): 删除字符。

5. 快速移动

* gg: 快速移动到文件开头。
* G (Shift + g): 快速移动到文件末尾。
* 0: 快速移动到当前行首。
* $: 快速移动到当前行尾。替代方式:按A进入行尾插入模式,再按ESC退出插入模式。
* Ctrl + f: 向前翻一页。
* Ctrl + b: 向后翻一页。
* w: 移动到下一个单词的首字母,符号、标点也算作一个单词。(可用e替代,移动到下一个单词的尾字母)
* b: 移动到上一个单词的首字母,符号、标点也算作一个单词。

6. 撤销/重做

* u: 撤销上次修改操作。
* Ctrl + r: 重做上次被撤销的操作。

7. 搜索

* /word: 向下搜索关键词word。
* ?word: 向上搜索关键词word。
* n: 跳到下一个搜索关键词匹配处。如果是/word搜索,n跳向文件末尾方向,如果是?word搜索,n跳向文件开头方向。
* N: 跳到上一个搜索关键词匹配处。如果是/word搜索,N跳向文件开头方向,如果是?word搜索,N跳向文件末尾方向。

8. 简单替换

:进入命令模式,

* %s/src_chars/dest_chars/g: 全局替换,将src_chars替换为dest_chars。同1,$s/src_chars/dest_chars/g
* N1,N2s/src_chars/dest_chars/g: 指定区域替换,将第N1行到第N2行(含N1,N2)之间的src_chars替换为dest_chars。

9. 复制/粘贴

按行复制:
* yy: 复制当前行。
* Nyy: 从当前行开始,复制N行。

自由复制
* v: 开始选择字符。
* y: 复制已选择的字符。

粘贴:
* p: 在下一行粘贴已复制内容。

10. 自定义Vim样式

自定义几个常用的样式配置,编辑~/.vimrc:

1
2
3
4
5
6
" 开启语法高亮
syntax enable
" 显示行号
set number
" 开启搜索关键词高亮
set hls

--EOF--

Xstream输出CDATA区段

在XML中,有些符号是有特殊含义的,比如<,&等,当解析器遇到这类字符时,就把它们按一定的语义解析了。于是,W3C定义了CDATA区段: CDATA区段里的文本不会被解析器解析。

用Xstream来将Java Bean转化为XML时,当Java Bean的String型成员变量中包含<, >, &, ', "等字符串,这些字符会被转义为&lt;, &gt;, &amp;, &apos;, &quot;。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class VM {
    private String sid = null;
 
    // getter & setter
}
 
public class TestVM {
    public void print() {
        System.out.println("TestVM");
    }
 
    public static void main(String[] args) {
        XStream xstream = new XStream(new XppDriver());
        xstream.alias("Result", VM.class);
        VM vm = new VM();
        vm.setSid("<>&'\"");
        System.out.println(xstream.toXML(vm));
    }
}

输出:

1
2
3
<Result>
  <sid>&lt;&gt;&amp;&apos;&quot;</sid>
</Result>

为了使Xstream正确处理这种含特殊字符的节点值,需要扩展XppDriver类,重写其中的createWriter()方法,指定成员变量名称(示例中的sid):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class TestVM {
    public static void main(String[] args) {
        XStream xstream = new XStream(new XppDriver() {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out) {
                    boolean cdata = false;
 
                    @Override
                    public void startNode(String name) {
                        super.startNode(name);
                        cdata = "sid".equals(name);
                    }
 
                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (cdata && !text.isEmpty()) {
                            writer.write("<![CDATA[");
                            writer.write(text);
                            writer.write("]]>");
                        } else {
                            super.writeText(writer, text);
                        }
                    }
                };
            }
        });
        xstream.alias("Result", VM.class);
        VM vm = new VM();
        vm.setSid("<>&'\"");
        System.out.println(xstream.toXML(vm));
    }
}

输出:

1
2
3
<Result>
  <sid><![CDATA[<>&'"]]></sid>
</Result>

--EOF--