Qida's Blog

纸上得来终觉浅,绝知此事要躬行。

GNU/CoreUtils 是一组类 Unix 操作系统所需的基础软件包。它包含三组命令,常用的命令如 catlsrm。学习 GNU/Linux 的第一步,就是要熟悉软件包下常用的命令。下面分别介绍这三组常用的命令:

File utilities

Basic operations

命令 描述 备注
cp Copy files and directories cp -rp 备份目录。
-r 递归复制目录,否则提示“略过目录‘xxx’”。
-p 保留源文件或目录的属性(包括属主、属组、权限、修改时间等)。
-f 强制覆盖。
mv Move (rename) files
rm Remove files or directories rm -rf 强制递归删除文件或目录。
-r 递归删除,将指定目录下的所有文件及子目录一并处理。
-f 强制删除文件或目录。
ln Create a link to a file ln -s TARGET LINK_NAME 创建软链接。
mkdir Create a directory -p 递归创建目录。
rmdir Remove empty directories -p 递归删除空目录,如果目录非空会删除失败并提示:rmdir: failed to remove 'xxx': Directory not empty

Directory listing

命令 描述 备注
ls List directory contents -l 查看详细信息。
-a 显示隐藏文件。
-d 仅列出目录本身,而不是列出目录内的文件。列出所有子目录本身:ls -d */
-h 将文件容量以人类较易读的方式(如GB、KB等)列出来。
-t 按时间排序显示,默认为新的排在前面。
-S 按文件容量大小排序,而不是用文件名。
dir List directory contents briefly Exactly like ls -C -b
vdir List directory contents verbosely Exactly like ls -l -b

Changing file attributes

命令 描述 备注
chown Change file owner and group chown -R owner:group /there/is/a/file
-R 递归修改,常用于一次性更改某一目录内所有的文件、目录。目标属主必须在 /etc/passwd
chgrp Change group ownership -R 递归修改,目标属组必须在 /etc/group
chmod Change access permissions chmod [ugoa...][[+-=][rwxX]...][,...]
可用:ugoa,可用权限:r=4w=2x=1
例如:chmod u+x
touch Change file timestamps 改变文件访问和修改时间,也可用于快速创建一个文件。

Disk usage

命令 描述 备注
df Show disk free space on file systems -h 以 K,M,G 为单位,更易读的方式显示。
-i list inode information instead of block usage
du Show disk usage on file systems -h 以 K,M,G 为单位,更易读的方式显示。
-s, --summarize 汇总显示(等于 --max-depth=0
-d, --max-depth=N 显示第 N 层子目录各自的大小,常用于找出最占空间的目录。例如:du --max-depth=1 -h ./
--exclude=PATTERN Exclude files that match PATTERN.
stat Return data about an inode
truncate Shrink or extend the size of a file to the specified size -s 参数指定一个大小:K, M, G, T, P, E, Z, Y

disk usage

Text utilities

Output of entire files

命令 描述 备注
cat Concatenates and prints files on the standard output 常用于连接并输出多个文件的内容。
tac Concatenates and prints files on the standard output in reverse 常用于反向连接并输出多个文件的内容。
nl Numbers lines of files -b 指定行号的方式,主要有 a t两种:
-b a 无论是否是空行,同样列出行号。
-b t 默认值,不列出空行行号。
basenc Encode/decode data and print to standard output --base64 same as base64 program (RFC4648 section 4)
--base64url file- and url-safe base64 (RFC4648 section 5)
--base32 same as base32 program (RFC4648 section 6)
--base32hex extended hex alphabet base32 (RFC4648 section 7)
--base16 hex encoding (RFC4648 section 8)
-d, --decode decode data
base64 Encodes or decodes Base64, and prints result to standard output
base32 Encodes or decodes Base32, and prints result to standard output

cat

https://en.wikipedia.org/wiki/More_(command)

https://en.wikipedia.org/wiki/Less_(Unix)

less

Output of parts of files

命令 描述 备注
head Output the first part of files 默认输出 10 行
tail Output the last part of files -n 输出倒数 n 行(默认输出 10 行)
-f 不停读取输出文件的最新内容,常用于实时监视日志输出,用 Ctrl+C 来终止。
tailf 等同于 tail -f -n 10
split Split a file into pieces 用于按行、按大小分割文件
csplit Split a file into context-determined pieces

head&tail

Operating on sorted files

命令 描述 备注
sort Sort text files 详见本文
shuf Shuffling text
uniq Uniquify files 详见本文

Operating on fields

命令 描述 备注
cut Print selected parts of lines 详见本文
paste Merge lines of files 合并多个文件的所有行
join Joins lines of two files on a common field 合并两个文件中相同位置的行

Operating on characters

命令 描述 备注
tr Translate or delete characters 详见本文
expand Convert tabs to spaces
unexpand Convert spaces to tabs

Summarizing files

命令 描述 备注
wc Print the number of bytes, words, and lines in files 详见本文
sum Checksums and counts the blocks in a file
cksum Checksums (IEEE Ethernet CRC-32) and count the bytes in a file
b2sum Computes and checks BLAKE2b message digest
md5sum Computes and checks MD5 message digest
sha1sum
sha224sum
sha256sum
sha384sum
sha512sum
Computes and checks SHA-1/SHA-2 message digests

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

A checksum is a small-sized block of data derived from another block of digital data for the purpose of detecting errors that may have been introduced during its transmission or storage.

checksum

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

单向散列算法

digest

Shell utilities

User information

命令 描述 备注
id Print user identity 显示当前用户的信息(uid、gid、groups)
logname Print current login name
whoami Print effective user ID
groups Print group names a user is in
users Print login names of users currently logged in
who Print who is currently logged in

System context

命令 描述 备注
date Print or set system date and time date +%Y-%m-%d 2016-12-28
arch Print machine hardware name
nproc Print the number of available processors
uname Print system information
hostname Print or set system name
hostid Print numeric host identifier
uptime Print system uptime and load 常用于查看系统负载

Working context

命令 描述 备注
pwd Print working directory 显示当前所在目录
stty Print or change terminal characteristics
tty Print file name of terminal on standard input
printenv Print all or some environment variables

Modified command invocation

命令 描述 备注
nohup Run a command immune to hangups
timeout Run a command with a time limit
env Run a command in a modified environment

Process control

命令 描述 备注
kill Send a signal to processes

Delaying

命令 描述 备注
sleep Delay for a specified time

Redirection

命令 描述 备注
tee Redirect output to multiple files or processes 详见本文

Conditions

命令 描述 备注
false Do nothing, unsuccessfully
true Do nothing, successfully
test Check file types and compare values
expr Evaluate expressions

Printing text

命令 描述 备注
echo Print a line of text -n Do not print the trailing newline character \n.
-e 开启转义字符,例如:反斜杠 \\、换行符 \n
printf Format and print data
yes Print a string until interrupted

yes 命令小技巧,使用管道自动输入“y”进行文件强制覆盖,方法:yes | cp 源文件 目的文件

Numeric operations

命令 描述 备注
seq Print numeric sequences
numfmt Reformat numbers 常用于格式化数字

File name manipulation

命令 描述 备注
basename Strip directory and suffix from a file name 截取出文件名
dirname Strip last file name component 截取出目录名

zip 压缩文件查看命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
zip -rmq dist.zip dist

-q 表示不显示压缩进度状态
-r 表示子目录子文件全部压缩为zip;这部分比较重要,不然的话只有something这个文件夹被压缩,里面的没有被压缩进去
-e 表示你的压缩文件需要加密,终端会提示你输入密码的;还有种加密方法,这种是直接在命令行里做的,比如zip -r -P Password01! modudu.zip SomeDir, 就直接用Password01!来加密modudu.zip了
-m 表示压缩完删除原文件

unzip [选项] dist.zip

-x 文件列表 解压缩文件,但不包括指定的file文件。
-v 查看压缩文件目录,但不解压。
-t 测试文件有无损坏,但不解压。
-d 目录 把压缩文件解到指定目录下。
-z 只显示压缩文件的注解。
-n 不覆盖已经存在的文件。
-o 覆盖已存在的文件且不要求用户确认。
-j 不重建文档的目录结构,把所有文件解压到同一目录下。
命令 描述 备注
zcat
zless
zmore
zgrep
zdiff

参考

GSLB(Global Server Load Balance,全局负载均衡)作为 CDN 系统架构中最核心的部分,负责流量调度。本文站在服务提供方的视角,做一些技术总结。

GSLB 横向对比

下表是三种常见的实现方式对比:

比较项 基于 DNS 解析方式 基于 HTTP 重定向方式 基于 IP 路由方式
性能 本地 DNS 服务器和用户终端 DNS 缓存能力使 GSLB 的负载得到有效分担 GSLB 处理压力大,容易成为系统性能的瓶颈 借助 IP 网络设备完成负载均衡,没有单点性能瓶颈
准确度 定位准确度取决于本地 DNS 覆盖范围,用户的本地 DNS 设置错误会造成定位不准确 在对用户 IP 地址数据进行有效维护的前提下,定位准确且精度高 就近性调度准确,但对设备健康性等动态信息响应会有延迟
效率 效率约等于 DNS 系统本身处理效率 依靠服务器做处理,对硬件资源的要求高 效率约等于 IP 设备本身效率
扩展性 扩展性和通用性好 扩展性较差,需对各种应用协议进行定制开发 通用性好,但适用范围有限
商用性 在 Web 加速领域使用较多 国内流媒体 CDN 应用较多 尚无商用案例

其中,基于 DNS 解析方式的 GSLB 有两个注意点:

准确度

本地 DNS 服务器(英文:Local DNS Server,缩写:LDNS)是用户所在局域网或 ISP 网络中使用的域名服务器,定位准确度就取决于它了。因为当用户在浏览器里访问某个域名时,浏览器会首先向 LDNS 发起查询,LDNS 再代为向整个 DNS 域名系统发起查询,直到找到解析结果。域名解析流程详见本文

如果 LDNS 设置不当,例如没有使用当前 ISP 提供的当地 LDNS,如 8.8.8.8,这种实现方式可能会误判用户的位置,从而将用户误导到错误的 CDN 缓存节点,造成加速效果差的问题。

缓存

DNS 的查询机制给使用它的互联网应用带来额外的时延,有时时延还比较大,为了解决问题,引入了“缓存”机制。缓存是指 DNS 查询结果在 LDNS 中缓存,当其它主机向它发起查询请求时,它就直接向主机返回缓存中能够找到的结果,直到数据过期。

在基于 DNS 解析方式下无论采用何种工作方式,都会有一些请求不会到达 GSLB,这是 DNS 系统本身的缓存机制在起作用。当用户请求的域名在本地 DNS 或本机(客户端浏览器)得到了解析结果,这些请求就不会达到 GSLB。Cache 更新时间越短,用户请求到达 GSLB 的几率越大。由于 DNS 的缓存机制屏蔽掉相当一部分用户请求,从而大大减轻了 GSLB 处理压力,使得系统抗流量冲击能力显著提升,这也是很多商业 CDN 选择 DNS 机制做全局负载均衡的原因之一。但弊端在于,如果在 DNS 缓存刷新间隔之内系统发生影响用户服务的变化,比如某个节点故障,某个链路拥塞等,用户依然会被调度到故障点去。

智能 DNS 实现浅析

基于 DNS 解析方式的 GSLB 的实现关键,就在于使 DNS “智能化”。简单来说,就是通过建立 IP 地址访问列表,判断用户的访问来源,以确定其访问节点的位置。下面浅析如何实现智能 DNS:

IP 地址收集策略

由于基于 DNS 解析方式的 CDN 使用 LDNS 进行寻址,因此我们只需要收集互联网上 DNS 服务器的 IP 地址。这样一来,收集的数量就会大大降低。为了更进一步缩小范围,一般使用 IP 地址加子网掩码的形式,如 123.175.0.0/16。在 IP 地址列表文件,就这么一行,却可以囊括很多 DNS 服务器。

IP 地址收集方法

除了可以跟第三方购买 IP 地址段之外,这里重点介绍下如何自行收集 IP 地址段。

ICANN

ICANN —— 一个负责 IP 地址分配以及域名管理的机构,与之关联的五个 RIR 机构负责替 ICANN 分配与登记部分区域的 IP 地址段:

RIR Region
AFRINIC Africa region
APNIC Asia and Pacific region
ARIN Canada, many Caribbean and North Atlantic islands, and the United States
LACNIC Latin America and parts of the Caribbean
RIPE NCC Europe, the Middle East and parts of Central Asia

可见,亚太地区的 IP 地址由 APNIC 分配,访问这里可以知道在何处得到 IP 地址分配的有用信息。进入 FTP ,阅读 README 以了解该下载哪个文件以及文件的格式。下载 delegated-apnic-latest 文件,过滤出分配给中国大陆(CN)的 IP 地址。

然后可以通过 ICANN LookupCNNIC IP 地址注册信息查询系统查询这个地址段属于哪个运营商,但一次只能查询一个地址段,根本无法手工完成所有地址段的查询,因此推荐在 Linux 下使用 whois 命令以遍历的方式逐个查询,然后按关键字归类、去重、排序,按运营商产生几个独立的文件。如果各 IP 地址租用方未能按统一的标准在 APNIC 提交注册信息则需要特殊处理。

IP 地址列表使用

最后,将每个 IP 地址列表文件关联一个 Bind 的视图 View。定义视图的目的在于,当有来自某个文件所列 IP 范围内的客户发起查询请求时,使用本视图的区文件进行域名解析。通俗的说,就是让某个运营商线路的用户,去访问某个运营商机房的服务器。

使用 Vim 也有好几年了,虽然这款“编辑器之神”的学习曲线非常陡峭,但一旦上手将会极大提高文本编辑效率,因此值得投入精力学习。

Vim

此外,Vim 哲学早已走出编辑器范畴,渗透到各种工具,例如:

Vim

本文我将会从三个方面总结 Vim 的知识。

四种常用模式

Vim 效率之高的秘密,就在于它拥有多种“模式”。如果你已经习惯了 Windows 下的编辑器,这些模式在一开始会很违反你的使用直觉。因此学习 Vim 的第一件事,就是要习惯这些模式之间的切换。

Vim 共具有 6 种基本模式和 5 种派生模式,下面只介绍最常用的 4 个基本模式:

普通模式(NORMAL MODE)

Vim 启动后的默认模式。这正好和许多新用户期待的操作方式相反,因为大多数编辑器的默认模式为插入模式(就是一打开编辑器就可以开始码字)。

Vim 强大的编辑能力中很大部分是来自于其普通模式的命令(及组合)。在普通模式下,用户可以执行一般的编辑器命令,比如移动光标,删除文本等等。如果进一步学习各种各样的文本间移动/跳转命令和其它编辑命令,并且能够灵活组合使用的话,能够比那些没有模式的编辑器更加高效的进行文本编辑。

下面介绍普通模式下几类常用的快捷键:

移动命令

跨行移动:

快捷键 说明
hjkl VIM allows using the cursor keys in order to move around. However, for a pure VIM experience you should stick to using ‘h’, ‘j’, ‘k’ and ‘l’. It’s considered more efficient since you don’t have to move your hand from the home row when you’re typing.
gg 到第一行
G 到最后一行
nG 到第 n 行
% 匹配括号移动,包括 () {} [](需要先把光标先移到括号上)
* 匹配光标当前所在的单词(# 反向)

当前行移动:

快捷键 说明
0 到行头($ 反向)
^ 到本行第一个非 blank 字符的位置(所谓 blank 字符就是空格、tab、换行、回车等)
w 到下一个单词的开头(b 反向)
e 到下一个单词的结尾
f Find next character(F 反向)
fi 到字符 i 处
4fi 到第四个字符 i 处
t Find before character(T 反向)

Vim 当前行移动

编辑命令

文本替换:

快捷键 说明
r Replace current character
When you need to replace only one character under your cursor, without changing to insert mode, use r.

剪切/复制/粘贴:

快捷键 说明
x Cut current character
d dd Cut current line
dt Cut till …
y yy Copy current line (yank)
yt Copy till …
p Paste

缩进/补全:

快捷键 说明
<< 左缩进
>> 右缩进
= 自动缩进
Ctrl + p 在 Insert 模式下,自动补全…

从别的编辑器里粘贴到 vim 里的代码经常由于不正常的缩进变得格式混乱,可以使用如下命令:

  • 自动缩进当前行: ==

  • 全文格式化:gg=G ,即:

    1. gg - Goto the beginning of the file
    2. = - apply indentation
    3. G - till end of file

重复命令

快捷键 说明
. 重复执行上一个命令
n<command> 重复执行某个命令 n 次
<start position><command><end position> 对某段起止文本执行某个命令,例如:d(删除)、y(复制)、v(选择)、gU(变大写)、gu(变小写)。例如:gg=G

插入模式(INSERT MODE)

在这个模式中,大多数按键都会向文本缓冲中插入文本。大多数新用户希望文本编辑器在编辑过程中一直保持这个模式。

使用以下快捷键进入插入模式:

当前行插入

快捷键 说明
i Switch to insert mode on current character
a Switch to insert mode after current character
I Switch to insert mode on first visible character of the current line
A Switch to insert mode on last visible character of the current line

另起一行插入

快捷键 说明
o Switch to insert mode after current line
O Switch to insert mode before current line

可视模式(VISUAL MODE)

这个模式与普通模式比较相似。但是移动命令会扩大高亮的文本区域。高亮区域可以是字符、行或者是一块文本。当执行一个非移动命令(例如复制、删除)时,命令会被执行到这块高亮的区域上。

使用以下快捷键进入可视模式:

快捷键 说明
Ctrl + v Switch to visual block mode
v Switch to visual character mode
V Switch to visual line mode

命令行模式(COMMAND MODE)

在命令行模式中可以输入命令。在命令执行完后,Vim 返回到命令行模式之前的模式,通常是普通模式。

使用以下快捷键进入命令行模式:

快捷键 说明
: 执行命令:
:h 帮助文档,例如查看 s文本替换命令(substitude)的帮助::h s
! 过滤命令
/? 搜索字符串

[range] 有以下一些表示方法,例如常用的 % 表示替换所有行,等价于 1,$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
不写 range   :  默认为当前光标所在行。
. : 当前光标所在行。
$ : 最后一行。
1 : 第1行。
33 : 第33行。
'a : 标记a所在的行(之前要使用ma做过标记)。

通过 +、- 设置相对偏移量:
.+1 : 当前光标所在行的下一行。
$-1 : 倒数第二行。(这里说明我们可以对某一行加减某个数值来取得相对的行)。

通过 , 设置范围:
22,33 : 第22~33行。
1,$ : 第1行 到 最后一行。
1,. : 第1行 到 当前行。
.,$ : 当前行 到 最后一行。
'a,'b : 标记a所在的行 到 标记b所在的行。

% : 所有行(与 1,$ 等价)。

?chapter? : 从当前位置向上搜索,找到的第一个chapter所在的行。(其中chapter可以是任何字符串或者正则表达式。
/chapter/ : 从当前位置向下搜索,找到的第一个chapter所在的行。(其中chapter可以是任何字符串或者正则表达式。

[flags] 有以下一些表示方法:

1
2
3
4
5
6
7
8
无      :  只对指定范围内的第一个匹配项进行替换。
g : 对指定范围内的所有匹配项进行替换。
c : 在替换前请求用户确认。
e : 忽略执行过程中的错误。
i : Ignore case for the pattern.
I : Don't ignore case for the pattern.

注意:上面的所有flags都可以组合起来使用,比如 gc 表示对指定范围内的所有匹配项进行替换,并且在每一次替换之前都会请用户确认。

Substitute Text

命令 描述
:[range]s[ubstitute]/{pattern}/{string}/[flags] [count] For each line in [range] replace a match of {pattern} with {string}.

详细命令:

1
2
3
4
5
6
7
8
9
10
11
4.2 Substitute                                          *:substitute*
*:s* *:su*
:[range]s[ubstitute]/{pattern}/{string}/[flags] [count]
For each line in [range] replace a match of {pattern} with {string}.
For the {pattern} see |pattern|.
{string} can be a literal string, or something special; see |sub-replace-special|.
When [range] and [count] are omitted, replace in the current line only.
When [count] is given, replace in [count] lines, starting with the last line in [range].
When [range] is omitted start in the current line.
Also see |cmdline-ranges|.
See |:s_flags| for [flags].

例子,批量替换所有空格:

  • : 进入命令行模式
  • % 表示所有行(与 1,$ 等价)
  • s 表示文本替换命令
  • \s 表示空格
  • g 表示对指定范围内的所有匹配项进行替换
1
:%s/\s//g

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.  替换当前行中的内容:    :s/from/to/
:s/from/to/ : 将当前行中的第一个from,替换成to。如果当前行含有多个from,则只会替换其中的第一个。
:s/from/to/g : 将当前行中的所有from都替换成to。
:s/from/to/gc : 将当前行中的所有from都替换成to,但是每一次替换之前都会询问请求用户确认此操作。

注意:这里的from和to都可以是任何字符串,其中from还可以是正则表达式。

2. 替换某一行的内容: :33s/from/to/g
:.s/from/to/g : 在当前行进行替换操作。
:33s/from/to/g : 在第33行进行替换操作。
:$s/from/to/g : 在最后一行进行替换操作。

3. 替换某些行的内容: :10,20s/from/to/g
:10,20s/from/to/g : 对第10行到第20行的内容进行替换。
:1,$s/from/to/g : 对第一行到最后一行的内容进行替换(即全部文本)。
:1,.s/from/to/g : 对第一行到当前行的内容进行替换。
:.,$s/from/to/g : 对当前行到最后一行的内容进行替换。
:'a,'bs/from/to/g : 对标记a和b之间的行(含a和b所在的行)进行替换。其中a和b是之前用m命令所做的标记。

4. 替换所有行的内容: :%s/from/to/g
:%s/from/to/g : 对所有行的内容进行替换。

Deleting text

命令 描述
:[range]d[elete] [x] Delete [range] lines (default: current line) [into register x].
:[range]d[elete] [x] {count} Delete {count} lines, starting with [range](default: current line) [into register x].
:[range]j[oin][!] [flags] Join [range] lines.
:[range]j[oin][!] {count} [flags] Join {count} lines, starting with [range] (default: current line).

例子:

1
2
# 刪除 1-10 行
:1,10d

Copying and moving text

命令 描述
:[range]y[ank] [x] Yank [range] lines [into register x].
:[range]y[ank] [x] {count} Yank {count} lines, starting with last line number in [range] (default: current line), [into register x].
:[range]co[py] {address} Copy the lines given by [range] to below the line given by {address}.
:[range]m[ove] {address} Move the lines given by [range] to below the line given by {address}.

Formatting text

Shifting lines left or right:

命令 描述
:[range]< Shift [range] lines one 'shiftwidth' left. Repeat '<' for shifting multiple 'shiftwidth's.
:[range]< {count} Shift {count} lines one 'shiftwidth' left, starting with [range] (default current line). Repeat ‘<’ for shifting multiple 'shiftwidth's.
:[range]> [flags] Shift {count} [range] lines one 'shiftwidth' right. Repeat '>' for shifting multiple 'shiftwidth's.
:[range]> {count} [flags] Shift {count} lines one 'shiftwidth' right, starting with [range] (default current line). Repeat '>' for shifting multiple 'shiftwidth's.

Left-align, right-align lines or center lines:

命令 描述
:[range]le[ft] [indent] Left-align lines in [range]. Sets the indent in the lines to [indent] (default 0).
:[range]ri[ght] [width] Right-align lines in [range] at [width] columns
(default 'textwidth' or 80 when 'textwidth' is 0).
:[range]ce[nter] [width] Center lines in [range] between [width] columns
(default 'textwidth' or 80 when 'textwidth' is 0).

Sorting text

命令 描述
:[range]sor[t][!] [b][f][i][n][o][r][u][x] [/{pattern}/] Sort lines in [range]. When no range is given all lines are sorted.
With [!] the order is reversed.
With [i] case is ignored.

配置

使用 Vim 年月较久后总会定制一套个性化的 Vim 配置,例如截取一段常用的 ~/.vimrc 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
set number                  " 显示行号
set cursorline " 突出显示当前行
set ruler " 打开状态栏标尺
set shiftwidth=4 " 设定 << 和 >> 命令缩进时的宽度为 4
set softtabstop=4 " 使得按退格键时可以一次删掉 4 个空格
set tabstop=4 " 设定 tab 长度为 4
set nowrapscan " 禁止在搜索到文件两端时重新搜索
set incsearch " 输入搜索内容时就显示搜索结果
set hlsearch " 高亮显示搜索结果
syntax on " 程序语法开关
inoremap jj <ESC> " 重映射 ESCAPE 键
" 定义缩写:ab [缩写] [要替换的文字]
ab asap as soon as possible

另外注意,Vim 的操作记录会写入 ~/.viminfo

JSON 格式化

1
2
3
4
5
6
# JSON 格式化
# : 进入命令行模式
# % [range] 参数,指定所有行
# ! 执行具体的命令,这里使用 python
# -m json.tool 调用python里json.tool这个模块
:%!python -m json.tool

可以将该常用命令放到 ~/.vimrc 配置文件中,方便使用:

1
2
3
4
5
6
7
8
9
10
11
12
" F3 快捷键 JSON 格式化当前行
map <F3> :.!python -m json.tool<CR>
" F4 快捷键 JSON 格式化全文
map <F4> :%!python -m json.tool<CR>

" :JsonFormat 命令格式化全文
command! JsonFormat :execute '%!python -m json.tool'
" :JsonFormat 命令格式化全文,并解决汉字以 unicode 码显示问题,参考:http://qiita.com/tomoemon/items/cc29b414a63e08cd4f89
command! JsonFormat :execute '%!python -m json.tool'
\ | :execute '%!python -c "import re,sys;chr=__builtins__.__dict__.get(\"unichr\", chr);sys.stdout.write(re.sub(r\"\\u[0-9a-f]{4}\", lambda x: chr(int(\"0x\" + x.group(0)[2:], 16)).encode(\"utf-8\"), sys.stdin.read()))"'
\ | :set ft=javascript
\ | :1

设置键盘映射

https://blog.csdn.net/lym152898/article/details/52171494

各种版本

GVim

GVim 是 Windows 版的 Vim,因为有了标准的 Windows 风格的图形界面,所以叫 G(Graphical)Vim。

GVim 的多标签切换:

快捷键 说明
:tabnew 新建标签页
:tabs 显示已打开标签页的列表
:tabc 关闭当前标签页
:tabn 移动到下一个标签页
:tabp 移动到上一个标签页
:tabfirst 移动到第一个标签页
:tablast 移动到最后一个标签页

字符集配置参考 这里,其它小技巧参考 这里

MacVim

https://github.com/macvim-dev/macvim

其它

Editing a .jar with vim

一般来说,jar 包可以通用 vim 直接编辑。但要注意的是,Spring Boot 插件打包的可执行的 jar 无法使用 vim 浏览并编辑内部文件。

参考

https://en.wikipedia.org/wiki/Vim_(text_editor)

https://zh.wikipedia.org/wiki/Vim

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

https://zh.wikipedia.org/zh-hk/编辑器之战

https://missing-semester-cn.github.io/

为什么 Vim 使用 HJKL 键作为方向键

简明 Vim 练级攻略

Vim文本替换命令

12 个关于 vim 的编辑技巧

Vim 分屏基本操作详解

Vim 最全图解

VIM 插件:https://vimawesome.com/plugin/json-vim

Vim philosophy

HTML 标签语义化

标签名 英文全拼 中文翻译
a anchor
abbr abbreviation 缩写词
acronym acronym 取首字母的缩写词
address address 地址
blockquote block quotation 区块引用于
br break 换行
caption caption 标题
center center 居中
div division 分隔
fieldset fieldset 域集
font font 字体
h1~h6 header1~header6 标题1~标题6
hr horizontal rule 水平尺
legend legend 图标
p paragraph 段落
pre preformatted 预定义格式
span span 范围
var variable 变量

HTML Formatting

标签名 英文全拼 中文翻译
b bold 粗体
i italic 斜体
s strikethrough 删除线
u underlined 下划线
strong strong 加重
em emphasized 加重
big big 变大
small small 变小
del delete 删除
ins inserted 插入
sub subscripted 下标
sup superscripted 上标

HTML Lists

标签名 英文全拼 中文翻译
ol ordered list 排序列表
ul unordered list 不排序列表
li list item 列表项目
dl definition list 定义列表
dt definition term 定义术语
dd definition description 定义描述

参考

https://www.w3schools.com/html/

上文

有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起。这种问题只能由人来裁决,解决冲突的办法无非是从冲突中二者选其一或者由你亲自整合到一起。

你完全可以手工编辑处理冲突,或者推荐使用图形化的外部合并与比较工具(mergetool)。

mergetool 是什么?

Merge tool is a GUI that steps you through each conflict, and you get to choose how to merge. Sometimes it requires a bit of hand editing afterwards, but usually it’s enough by itself. It is much better than doing the whole thing by hand certainly.

mergetool 如何选择?

mergetool 需自行选择安装。选择很多,例如:meld, opendiff, kdiff3, tkdiff, xxdiff, tortoisemerge, gvimdiff, diffuse, ecmerge, p4merge, araxis, vimdiff, emerge …

推荐使用 meld,一款优秀的可视化 diff 和代码合并工具(merge tool),支持特性如下:

  • 跨平台,支持 Linux/Unix、 OS X、Windows,多种便捷的安装方式
  • 跨工具,支持多种版本控制系统(VCS),如 Git、SVN、Mercurial …
  • 支持双方或三方文件、目录对比
  • GUI 界面好看 :)

meld 如何使用?

安装好 meld ,还需进行如下配置:

用于 git diff

首先配置好 git :

1
$ git config --global diff.external ~/meld.sh

然后准备编写 meld.sh 包装脚本:

1
$ vim ~/meld.sh

默认情况下, git diff 会传递 7 个参数给该包装脚本:

1
path old-file old-hex old-mode new-file new-hex new-mode

但我们仅仅只需要 old-filenew-file 参数,因此需要用包装脚本来传递它们。脚本内容如下:

1
2
#!/bin/sh
meld $2 $5

如果对涉及到的参数感兴趣,可以在脚本补充一段 echo $0 $*

最后对于 Linux/Unix、OS X,还需要增加脚本的可执行权限:

1
$ chmod +x ~/meld.sh

以上配置好后,就可以调用图形化工具愉快的使用 git diff 了。

用于 git mergetool

首先配置好 git :

1
$ git config --global merge.tool meld

如果合并的时候出现如下冲突:

1
CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.

使用如下命令:

1
$ git mergetool

就可以调用图形化工具愉快的解决冲突了。

在解决了所有文件的所有冲突后,运行 git add 将把它们标记为已解决状态即可(一旦暂存,就表示冲突已经解决)。

参考

如果工作目录的本地代码做了修改但尚未提交,pull 拉取远程仓库的新提交时,往往会提示冲突:

1
2
3
4
$ git pull
error: Your local changes to the following files would be overwritten by merge:
/there/is/a/conflict/file
Please, commit your changes or stash them before you can merge.

如上所示,有 commitstash 两种处理方法。针对本地代码的完成情况我们需要作出选择。

代码已完成

如果确认本地代码已经完成无误,可以先将本地代码 commit 到本地仓库。再次 pull 拉取远程仓库时,如无冲突,Git 会自动产生一次“合并”提交:

1
2
3
4
5
6
7
$ git lg
* e6f4e18 - Merge branch 'master' of origin (1 minutes ago)
|\
* | abfa93b - 本地仓库的提交 (2 minutes ago)
| * 1f1c21d - 远程仓库的提交 (3 minutes ago)
|/
* 17ef24c - 基准版本 (4 minutes ago)

这是因为 pull 的默认策略是“fetch + merge”。如果本地仓库的提交一直不 push 到远程仓库,极端情况下每一次 pull 都可能会产生一次“合并”提交,这会造成祖先图谱(graph)无谓的复杂。此时推荐使用 rebase 避免本地仓库无谓的合并节点:

1
$ git pull --rebase

代码未完成

但如果本地代码仍未完成,此时推荐使用 stash 命令暂存修改,避免将未完成的功能代码 commit 到本地仓库,污染仓库。

暂存当前修改

第一步,stash 暂存当前修改:

1
2
$ git stash save "填写你的备注"
Saved working directory and index state ......
1
2
3
$ git status
On branch master
nothing to commit, working directory clean

可以看到暂存后工作目录一干二净。这是因为 stash 命令可以将“修改过的被追踪的文件(modified tracked files)”和“暂存的变更(staged changes)”暂存到临时堆栈中,并将工作目录还原干净,以便后续的操作。

Stashing takes the dirty state of your working directory – that is, your modified tracked files and staged changes – and saves it on a stack of unfinished changes that you can reapply at any time.

拉取远程仓库

第二步,继续 pull 拉取远程仓库并进行自动合并:

1
$ git pull

重新还原暂存

第三步,stash pop 重新还原暂存修改:

1
$ git stash pop

处理冲突

最后一步,如果还原后产生冲突,需要手工或使用 mergetool 进行处理。处理完毕后,使用 add 标明冲突已解决:

1
$ git add .

参考

Git-工具-储藏(Stashing)

cherry-pick 这个命令的名字是比较形象的,即“摘樱桃”,使用该命令可以将任意的 commit 合并到你想要的分支上。 例如:

1
2
3
4
5
# 切换到 master 分支
$ git checkout master

# cherry-pick 特性分支上的三个 commit
$ git cherry-pick e7ce3f8 915fe84 dc6baf3

合并完毕后,会在 master 分支上新产生三个 commit 号,但提交内容不变。

如果只是想整理当前分支,可以使用 rebase 命令。

参考

Git知识总览(四) git分支管理之rebase 以及 cherry-pick相关操作

本文介绍一个生僻但相当好用的命令 rebase(衍合)。

使用场景

衍合的两个使用场景:

  1. 生成干净历史、补丁
  2. 整理当前分支

生成干净历史

开发过程中,常常需要定期将最新的远程分支拉取(pull)到本地分支,保持本地代码最新(up to date)。如果拉取频繁,pull 默认的 merge 行为会造成祖先图谱(ancestry graph)无谓的复杂:

1
2
3
4
5
6
7
8
$ git pull origin master    // pull = fetch + merge

* ab900eb - 三方合并版本(注意这里!)
|\
| * 756ba83 - 本地分支提交的版本
* | 915fe84 - 先被推送到远程分支的版本
|/
* e7ce3f8 - 基准版本(共同祖先)

解决方案是改用 rebase 命令,其产生的祖先图谱如下,非常简洁:

1
2
3
4
5
*   dc6baf3 - 本地分支提交的版本(注意这个提交被改写了!)
|
* 915fe84 - 先被推送到远程分支的版本
|
* e7ce3f8 - 基准版本(共同祖先)

可见,这个神奇的命令功能类似 merge ,但它避免了上述无谓的合并节点,从而产生一个更为整洁的提交历史。如果视察一个衍合过的分支历史,仿佛所有的提交都是在一根时间轴上先后进行的,尽管实际上它们原本是同时并行发生的。这么做的好处是,非常便于项目管理人员按时间轴进行代码审查

命令用法

可以在 pull 时主动加上 --rebase 参数:

1
$ git pull --rebase origin master

甚至推荐将 rebase 设为 pull 命令的默认行为,从而应用于所有新建分支:

1
$ git config --global branch.autosetuprebase always

注意,对于应用上述命令前已存在的分支(例如 master),需要补充执行如下配置:

1
$ git config branch.master.rebase true

命令原理

下面进一步介绍 rebase 命令的原理:

  1. 把本地分支从上一次 pull 之后的变更暂存起来;
  2. 恢复到上一次 pull 时的情况;
  3. 合并远程分支的提交;
  4. 最后再逐一合并刚暂存下来的本地提交(相当于重放一遍)。

生成干净补丁

使用衍合的另一个目的,是想要得到一个能在远程分支上干净应用的补丁 — 比如某些项目、或些分支你不是维护者,但想帮点忙的话,最好用衍合:先在自己的一个独立分支中进行开发,当准备向主项目提交补丁的时候,根据最新的 origin/master 进行一次 git rebase 衍合操作然后再提交,这样维护者就不需要做任何整合工作(实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。

整理当前分支

衍合的另一个用法是整理当前分支,使用 git rebase [-i | --interactive] 命令。

首先选取提交范围,e7ce3f8 为当前分支的历史提交:

1
2
3
4
5
*   dc6baf3 - commit3
|
* 915fe84 - commit2
|
* e7ce3f8 - commit1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ git rebase -i e7ce3f8

1 pick dc6baf3 本地分支提交的版本
2 pick 915fe84 先被推送到远程分支的版本
3
4 # Rebase 1ff20826..61527529 onto 1ff20826 (2 commands)
5 #
6 # Commands:
7 # p, pick = use commit
8 # r, reword = use commit, but edit the commit message
9 # e, edit = use commit, but stop for amending
10 # s, squash = use commit, but meld into previous commit
11 # f, fixup = like "squash", but discard this commit's log message
12 # x, exec = run command (the rest of the line) using shell
13 # d, drop = remove commit
14 #
15 # These lines can be re-ordered; they are executed from top to bottom.
16 #
17 # If you remove a line here THAT COMMIT WILL BE LOST.
18 #
19 # However, if you remove everything, the rebase will be aborted.
20 #
21 # Note that empty commits are commented out

可见,我们可以选取、编辑、合并、丢弃指定提交,达到整理分支的目的。

使用风险

注意,衍合必须遵守的准则:一旦本地分支中的提交(commit)已经被推送到远程仓库,就千万不要对该分支进行衍合操作。如果把衍合当成一种在推送(push)代码之前整理提交历史的手段,而且仅仅衍合那些尚未推送的本地提交,就没问题。如果衍合那些已经推送的提交,并且已经有人基于这些提交对象开展了后续开发工作的话,就会出现叫人沮丧的麻烦。

参考

快进式合并

默认情况下,当使用 git merge 合并代码时,背后实际上是进行了一次“快进式合并”:

1
2
$ git checkout master
$ git merge feature-test

什么是“快进式合并(fast-forward merge)”?如果顺着一个分支走下去可以直接到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。

fast-forward merge

非快进式合并

作为对比,加上 --no-ff 参数进行“非快进式合并(no-fast-forward merge)”:

1
2
$ git checkout master
$ git merge --no-ff feature-test

其祖先图谱如下:

no-fast-forward merge

可见,合并后保留有分支历史痕迹(每一次提交),能看得出来曾经做过分支合并,版本演进比较清晰。

压缩合并

但大多数时候,没有必要把特性分支的历史保留得太细,只需把整个特性分支压缩(squash)为主干上的一个提交即可。这样的祖先图谱既清晰,又能方便后人审查代码,推荐使用:

1
2
3
$ git checkout master
$ git merge --squash feature-test
$ git commit

参考

checkout 命令可以用于三种场景:

  • 切换分支
  • 创建分支
  • 撤销修改

本文只介绍第三种场景。

例子

如果我们想要撤销一个文件的本地修改,自然可以手工编辑恢复,但这样做实在是吃力不讨好。 checkout 命令可以帮助我们:

只撤销本地修改

修改文件后,使用 status 命令查看一下文件状态:

1
2
3
4
5
6
$ git status
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: /there/is/a/modified/file

Git 提示我们,对于未 add 进暂存区的文件,可以使用 git checkout -- <file> 快速撤销本地修改。

同时撤销本地和暂存区修改

那么,对于已 add 进暂存区的文件,如何撤销本地修改?还是先使用 status 命令查看一下文件状态:

1
2
3
4
5
$ git status
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: /there/is/a/modified/file

先取消暂存修改

Git 提示我们,可以使用 reset 命令取消暂存:

1
$ git reset /there/is/a/modified/file

取消暂存后,文件状态就回到了跟“例1”一样了:

1
2
3
4
5
6
$ git status
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: /there/is/a/modified/file

再撤销本地修改

这时按提示使用 checkout 即可:

1
$ git checkout -- /there/is/a/modified/file

这时工作目录就干净了:

1
2
$ git status
nothing to commit, working directory clean

可以看到,结合使用 resetcheckout 命令,可以撤销 index 和 working tree 的修改。

一步到位

那么有更便捷的、一步到位的办法吗?有,指定提交即可:

1
2
3
4
5
$ git status
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: /there/is/a/modified/file
1
$ git checkout HEAD -- /there/is/a/modified/file
1
2
$ git status
nothing to commit, working directory clean

那么 checkout 命令的全貌究竟是怎样的呢?

checkout 命令格式

checkout 命令的格式及描述如下:

1
2
3
git checkout [<tree-ish>] [--] <paths>...

Updates the named paths in the working tree from the index file (default) or from a named <tree-ish> (most often a commit, tag or branch)
  • 默认使用 index 暂存区的内容覆盖本地修改,如果不指定 <tree-ish> 参数。
  • 或者可以使用指定的提交、标记、分支版本覆盖本地修改。
  • 为了避免文件路径 <paths><tree-ish> 同名而发生冲突,在 <paths> 前用 -- 作为分隔。

checkoutreset

还记得在《git reset 命令回退版本》中介绍的 reset 命令吗?它与 checkout 命令之间有什么区别与关系?

区别

在这里介绍 reset 命令的另一种形式:

1
2
3
git reset [<tree-ish>] [--] <paths>...

This form copy entries from <tree-ish> to the index for all <paths>. (It does not affect the working tree or the current branch.)

checkout 命令的参数一模一样,区别是什么?

命令 操作目标 描述
checkout 工作目录(working tree) 用于撤销本地修改
reset 暂存区(index) 只用于覆盖暂存区

因此 git reset <paths> 等于 git add <paths> 的逆向操作。

如果企图用 reset 命令覆盖工作目录,是会报错的:

1
2
$ git reset --hard /there/is/a/modified/file
fatal: Cannot do hard reset with paths.

关系

After running git reset <paths> to update the index entry, you can use git checkout -- <paths> to check the contents out of the index to the working tree.

Alternatively, using git checkout [<tree-ish>] [--] <paths> and specifying a commit, you can copy the contents of a path out of a commit to the index and to the working tree in one go.

参考

git checkout
git reset
Git 教程 - 撤销修改