课程结构
01.课程概览与 shell
02.Shell 工具和脚本
03.编辑器 (Vim)
04.数据整理
05.命令行环境
06.版本控制(Git)
07.调试及性能分析
08.元编程
09.安全和密码学
10.大杂烩
11.提问&回答
本文档修改自这里,补充了一些视频中展示但配套文档中未提供的代码,以及一些注释,另外,本节中涉及的相关文件可在百度云链接中获取。
强烈建议:本节内容是对编辑器Vim的一个简略入门介绍,更加先直接完成本节的第一道习题,即Vim自带的vimtutor教程
~$ vimtutor
# 终端的“终端(T)”选项,可以设置窗口为80x24,vimtutor在该窗口下效果最好
编辑器Vim--目录
代码编辑器
写作和写代码其实是两项非常不同的活动。当我们编程的时候,会经常在文件间进行切换、阅读、浏览和修改代码,而不是连续编写一大段的文字。因此代码编辑器和文本编辑器是很不同的两种工具(例如微软的 Word 与 Visual Studio Code)。
通常学习使用一个新的编辑器包含以下步骤:
- 阅读教程(比如这节课以及我们为您提供的资源)
- 坚持使用它来完成你所有的编辑工作(即使一开始这会让你的工作效率降低)
- 随时查阅:如果某个操作看起来像是有更方便的实现方法,一般情况下真的会有
学习一个复杂的代码编辑器的过程一般是这样的:
- 头两个小时,您会学习到编辑器的基本操作,例如打开和编辑文件、保存与退出、浏览缓冲区。
- 当学习时间累计达到20个小时之后,您使用新编辑器的效率应该已经和使用老编辑器一样快。
- 在此之后,其益处开始显现:有了足够的知识和肌肉记忆后,使用新编辑器将大大节省你的时间。而现代文本编辑器都是些复杂且强大的工具,永远有新东西可学:学的越多,效率越高。
编辑器选择
程序员们对自己正在使用的文本编辑器通常有着 非常强的执念。
现在最流行的编辑器是什么?Stack Overflow 的调查(这个调查可能并不如我们想象的那样客观,因为 Stack Overflow 的用户并不能代表所有程序员)显示,Visual Studio Code 是目前最流行的代码编辑器。而 Vim 则是最流行的基于命令行的编辑器。
Vim 有很多聪明的设计思想,所以很多其他工具也支持 Vim 模式(比如,140 万人安装了 Vim emulation for VS code)。
编辑模式
Vim 的设计以大多数时间都花在阅读、浏览和进行少量编辑改动为基础,因此它具有多种操作模式:
- 正常模式(
按下<ESC>键
):在文件中四处移动光标进行修改 - 插入模式(
正常模式下按下i
):插入文本 - 替换模式(
正常模式下按下R
):替换文本 - 可视化模式(
正常模式下输入v、V、ctrl+v
):分别表示可选中字符、行、文本块 - 命令模式(
正常模式下输入:
):用于执行命令,若输入的是:!command
,还可以执行外部命令
在不同的操作模式下,键盘敲击的含义也不同。比如,x
在插入模式会插入字母 x
,但是在正常模式 会删除当前光标所在的字母,在可视模式下则会删除选中文本块。
在默认设置下,Vim 会在左下角显示当前的模式。Vim 启动时的默认模式是正常模式。你可以按下 <ESC>
(退出键)从任何其他模式返回正常模式。
因为你会在使用 Vim 时大量使用 <ESC>
键,所以可以考虑把大小写锁定键重定义成 <ESC>
键(MacOS 教程)。
Ubuntu系统可以参考这里,使用cap2esc将Cap_Lock键映射为Esc和Ctrl键
基本操作
插入文本
在正常模式,键入
i
: 在光标所在位置之前,进入插入模式- 小写的
o
: 可以在光标下方打开新的一行并进入插入模式 - 大写的
O
: 可以在光标上方打开新的一行 - 小写的
a
: 可以在光标所在位置之后插入文本 - 大写的
A
: 可以在光标所在行的行末之后插入文本 - 大写的
R
: 可以在光标所在位置开始,进入替换模式,逐一将光标后的原内容替换
键入Esc
键后,可重新回到正常模式
缓存, 标签页, 窗口
当你在终端执行vim filename
命令时,会默认打开一个标签页,这个标签页包含一个窗口。每个窗口显示一个缓存,即你将要编辑的文件内容。
缓存和窗口不是一一对应的关系。
- 一个缓存可以在多个窗口打开
- 例如,你可以打开多个终端窗口,都执行
vim filename
,打开同一个文件。
- 例如,你可以打开多个终端窗口,都执行
- 也可以在同一个标签页内的多个窗口打开,即多窗口模式
- 正常模式下,输入
:sp
或:vsp
,会在当前标签页切分为上下两个窗口
- 正常模式下,输入
多窗口模式的一种实用情形——多窗口间跳转:
- 输入
:sp
打开多个窗口后,输入两次Ctrl+w
,可以将光标从一个窗口跳转到另一个窗口(光标所在窗口是你当前能进行编辑的) - 或者,输入
Ctrl+w
后,再按下j
或k
,分别能跳转到下一个或上一个窗口
多窗口模式下,输入:e {{path/to/file}}
,可以在该窗口打开另一个文件,在该文件选中内容,复制之后,利用窗口跳转到原文档所在窗口,进行粘贴。这样,就实现了跨文件的复制粘贴。
命令行模式
在正常模式下键入 :
进入命令行模式。 在键入 :
后,你的光标会立即跳到屏幕下方的命令行。 这个模式有很多功能,包括打开,保存,关闭文件,以及 退出 Vim。
:q
退出(关闭窗口):q!
强制退出(关闭窗口,且不保存已编辑的内容):w
保存(写):wq
保存然后退出:exit
退出:e {{path/to/file}}
打开要编辑的文件:ls
显示打开的缓存:help {标题}
打开帮助文档:help :w
打开:w
命令的帮助文档:help w
打开w
移动的帮助文档
:set xxx
可以设置 xxx 选项。一些有用的选项如下:- ‘ic’或’ignorecase’——查找时忽略字母大小写
- ‘is’或’incsearch’——查找短语时显示部分匹配
- ‘hls’或’hlsearch’——高亮显示所有的匹配短语
Vim 的接口其实是一种编程语言
Vim 最重要的设计思想是 Vim 的界面本身是一个程序语言。键入操作(以及他们的助记名) 本身是命令,这些命令可以组合使用。这使得移动和编辑更加高效,特别是一旦形成肌肉记忆。
移动
多数时候你会在正常模式下,使用移动命令在缓存中导航。在 Vim 里面移动也被称为 “名词”, 因为它们指向文字块。
- 基本移动:
hjkl
(左, 下, 上, 右) - 词:
w
(下一个词word),b
(词初beginning),e
(词尾ending) - 行:
0
(数字0,行初),^
(第一个非空格字符),$
(行尾) - 屏幕:
H
(屏幕首行High),M
(屏幕中间Middle),L
(屏幕底部Low) - 翻页:
Ctrl-u
(上翻up),Ctrl-d
(下翻down) - 文件:
gg
(文件头),G
(文件尾) - 行数:
:{行数}<Enter>
或者{行数}G
({行数}为行数) - 杂项:
%
(找到配对,比如括号或者 /* */ 之类的注释对)- 如果光标当前位置是符号(){}[],按下
%
会将光标移动到配对的符号上 - 例如,光标置于
(
,按下%
,光标会跳转到配对的)
- 如果光标当前位置是符号(){}[],按下
- 查找(单个字符):
f{字符}
,t{字符}
,F{字符}
,T{字符}
- 查找/到 向前/向后 在本行的{字符}
,
与;
用于导航匹配
- 搜索:
/{正则表达式}
(按Enter
键跳转到匹配结果,使用n
/N
可在匹配结果间导航)
编辑
所有你需要用鼠标做的事, 你现在都可以用键盘:采用编辑命令+移动命令
的组合来完成。 这就是 Vim 的界面开始看起来像一个程序语言的时候。Vim 的编辑命令也被称为 “动词”, 因为动词可以施动于名词。
d{移动命令}
删除 {移动命令}- 例如,
dw
删除词,d$
删除到行尾,d0
删除到行头。
- 例如,
c{移动命令}
改变 {移动命令}- 例如,
cw
改变词 - 等价于:
d{移动命令}
再i
- 例如,
x
删除字符(等同于dl
)s
替换字符(等同于xi
)- 可视化模式(
v/V/ctrl+v
) + 选中文字 + 操作(d/c/x/s
)- 选中文字, 再键入上面的编辑操作,可删除或替换选中的文字
u
撤销,<Ctrl> + r
重做y
复制 / “yank” (其他一些命令比如d
也会复制,d
的功能类似于“剪切”)- 使用可视化模式选中文本后,再使用
y
进行复制, - 或者直接键入
y
,再键入<Enter>
,会复制光标所在的
- 使用可视化模式选中文本后,再使用
p
粘贴y
复制或者d
删除的内容~
改变字符的大小写- 使用可视化模式,选中内容后,按下
~
,可改变选中的内容的大小写
- 使用可视化模式,选中内容后,按下
计数
你可以用一个计数来结合“名词”和“动词”,这会执行指定操作若干次。
3w
向前移动三个词5j
向下移动5行7dw
删除7个词
修饰语
你可以用修饰语改变“名词”的意义。修饰语有 i
,表示“内部”或者“在内“,和 a
, 表示”周围“。
ci(
- 在光标位置往前查找左括号
(
,同时往后查找与之配对的右括号)
- 然后将这对括号
()
之间的内容删除,并在左括号与右括号之间进入插入模式 (
可以替换为成对的其他[ { " '
等
- 在光标位置往前查找左括号
da'
- 删除一个单引号字符串, 包括周围的单引号,具体作用方式同上面的
ci(
'
同样可以替换为成对的其他[ { " (
等
- 删除一个单引号字符串, 包括周围的单引号,具体作用方式同上面的
例子演示
这里是一个有问题的 fizz buzz 实现:
# codes of fizzbuzz.py
def fizz_buzz(limit):
for i in range(limit):
if i % 3 == 0:
print('fizz')
if i % 5 == 0:
print('fizz')
if i % 3 and i % 5:
print(i)
def main():
fizz_buzz(10)
我们会修复以下问题:
- 主函数没有被调用
- for循环从 0 而不是 1 开始
- 在 15 的整数倍的时候在不用行打印 “fizz” 和 “buzz”
- 在 5 的整数倍的时候打印 “buzz”
- 采用硬编码的参数 10 而不是从命令控制行读取参数
修复步骤:
- 主函数没有被调用
G
文件尾o
向下插入一个新行- 输入
if __name__=='__main__': main()
- for循环从 0 而不是 1 开始
- 搜索
/range
ww
向前移动两个词i
插入文字:1,
ea
在 limit 后插入:+1
- 搜索
- 在 15 的整数倍的时候在不用行打印 “fizz” 和 “buzz”
jj$i
插入文字到行尾- 加入
,end=''
jj.
: 可以直接重复输入,end=''
- 在 5 的整数倍的时候打印 “buzz”
ci'
输入buzz
- 从命令控制行读取参数
ggO
向上打开- “import sys”
/10
ci(
,然后输入int(sys.argv[1])
修改后的版本:
import sys
def fizz_buzz(limit):
for i in range(1,limit+1):
if i % 3 == 0:
print('fizz',end='')
if i % 5 == 0:
print('buzz',end='')
if i % 3 and i % 5:
print(i)
def main():
fizz_buzz(int(sys.argv[1]))
if __name__=='__main__':
main()
Vim高级操作
自定义 Vim
Vim 由一个位于 ~/.vimrc
的文本配置文件(包含 Vim 脚本命令)。
我们提供一个文档详细的基本设置,你可以用它当作你的初始设置。我们推荐使用这个设置因为 它修复了一些 Vim 默认设置奇怪行为。 在这儿下载我们的设置,然后将它保存成 ~/.vimrc
.
Vim 能够被重度自定义,花时间探索自定义选项是值得的。你可以参考其他人的在 GitHub 上共享的设置文件,比如,你的授课人的 Vim 设置 (Anish, Jon (uses neovim), Jose)。 有很多好的博客文章也聊到了这个话题。尽量不要复制粘贴别人的整个设置文件, 而是阅读和理解它,然后采用对你有用的部分。
扩展 Vim
Vim 有很多扩展插件。跟很多互联网上已经过时的建议相反,你 不需要在 Vim 使用一个插件 管理器(从 Vim 8.0 开始)。你可以使用内置的插件管理系统。只需要创建一个 ~/.vim/pack/vendor/start/
的文件夹,然后把插件放到这里(比如通过 git clone
)。具体配置方法可以参见本讲的习题部分。
以下是一些我们最爱的插件:
- ctrlp.vim: 模糊文件查找
- ack.vim: 代码搜索
- nerdtree: 文件浏览器
- vim-easymotion: 魔术操作
我们尽量避免在这里提供一份冗长的插件列表。你可以查看讲师们的开源的配置文件 (Anish, Jon, Jose) 来看看我们使用的其他插件。 浏览 Vim Awesome 来了解一些很棒的插件。 这个话题也有很多博客文章:搜索 “best Vim plugins”。
Shell
如果你是一个 Bash 用户,用 set -o vi
。如果你用 Zsh:bindkey -v
。Fish 用 fish_vi_key_bindings
。另外,不管利用什么 shell,你可以 export EDITOR=vim
。 这是一个用来决定当一个程序需要启动编辑时启动哪个的环境变量。 例如,git
会使用这个编辑器来编辑 commit 信息。
Readline
很多程序使用 GNU Readline 库来作为 它们的命令控制行界面。Readline 也支持基本的 Vim 模式, 可以通过在 ~/.inputrc
添加如下行开启:
set editing-mode vi
比如,在这个设置下,Python REPL 会支持 Vim 快捷键。
其他
甚至有 Vim 的网页浏览快捷键 browsers, 受欢迎的有 用于 Google Chrome 的 Vimium 和用于 Firefox 的 Tridactyl。 你甚至可以在 Jupyter notebooks 中用 Vim 快捷键。 这个列表 中列举了支持类 vim 键位绑定的软件。
Vim 进阶
一个好的学习策略是: 当你在使用你的编辑器的时候感觉 “一定有更好的方法来做这个”, 那么很可能真的有:上网搜寻一下!!
搜索和替换
:s
(替换)命令(文档)。
:%s/\[.*\](\(.*\))/\1/g
- 将有命名的 Markdown 链接替换成简单 URLs
命令格式 | 含义 |
---|---|
:s/foo/bar/g | Change each ‘foo’ to ‘bar’ in the current line. |
:%s/foo/bar/g | Change each ‘foo’ to ‘bar’ in all the lines. |
:5,12s/foo/bar/g | Change each ‘foo’ to ‘bar’ for all lines from line 5 to line 12 (inclusive). |
:'a,'bs/foo/bar/g | Change each ‘foo’ to ‘bar’ for all lines from mark a to mark b inclusive (see Note below). |
:'<,'>s/foo/bar/g | When compiled with +visual, change each ‘foo’ to ‘bar’ for all lines within a visual selection. Vim automatically appends the visual selection range (‘<,’>) for any ex command when you select an area and enter :. Also, see Note below. |
:.,$s/foo/bar/g | Change each ‘foo’ to ‘bar’ for all lines from the current line (.) to the last line ($) inclusive. |
:.,+2s/foo/bar/g | Change each ‘foo’ to ‘bar’ for the current line (.) and the two next lines (+2). |
:g/^baz/s/foo/bar/g | Change each ‘foo’ to ‘bar’ in each line starting with ‘baz’. |
宏
q{字符}
来开始在寄存器{字符}
中录制宏q
停止录制@{字符}
重放宏- 宏的执行遇错误会停止
{计数}@{字符}
执行一个宏{计数}
次- 宏可以递归
- 首先用
q{字符}q
清除宏 - 录制该宏,用
@{字符}
来递归调用该宏 (在录制完成之前不会有任何操作)
- 首先用
例子:将 xml 转成 json (example-data.xml),详细步骤及解释参照下面的习题解答。
Gdd
,ggdd
删除第一行和最后一行- 格式化最后一个元素的宏 (寄存器
e
)- 跳转到有
<name>
的行 qe^r"f>s": "<ESC>f<C"<ESC>q
- 跳转到有
- 格式化一个的宏
- 跳转到有
<person>
的行 qpS{<ESC>j@eA,<ESC>j@ejS},<ESC>q
- 跳转到有
- 格式化一个标签然后转到另外一个的宏
- 跳转到有
<person>
的行 qq@pjq
- 跳转到有
- 执行宏到文件尾
999@q
- 手动移除最后的
,
然后加上[
和]
分隔符
扩展资料
vimtutor
是一个 Vim 安装时自带的教程- Vim Adventures 是一个学习使用 Vim 的游戏
- Vim Tips Wiki
- Vim Advent Calendar 有很多 Vim 小技巧
- Vim Golf 是用 Vim 的用户界面作为程序语言的 code golf
- Vi/Vim Stack Exchange
- Vim Screencasts
- Practical Vim(书籍)
课后练习
(提示:本讲课后练习出现的相关文档,均已在百度云盘链接中)
- 完成
vimtutor
。备注:它在一个 80x24(80 列,24 行) 终端窗口看起来效果最好。 - 下载我们提供的 vimrc,然后把它保存到
~/.vimrc
。 通读这个注释详细的文件 (用 Vim!), 然后观察 Vim 在这个新的设置下看起来和使用起来有哪些细微的区别。 - 安装和配置一个插件: ctrlp.vim.(提示:这个插件协助vim编辑器快速查找文件、同时打开多个文件、创建新文件和目录,同时支持正则表达式进行文件搜索)
- 用
mkdir -p ~/.vim/pack/vendor/start
创建插件文件夹 - 下载这个插件:
cd ~/.vim/pack/vendor/start; git clone https://github.com/ctrlpvim/ctrlp.vim
- 阅读这个插件的 文档(提示:阅读readme.md文件)。 尝试用 CtrlP 来在一个工程文件夹里定位一个文件,打开 Vim, 然后用 Vim 命令控制行开始
:CtrlP
. - 自定义 CtrlP:添加 configuration (提示:阅读readme.md文件)到你的
~/.vimrc
来用按 Ctrl-P 打开 CtrlP
- 用
- 练习使用 Vim, 在你自己的机器上重做 演示。
- 下个月用 Vim 完成 所有的 文件编辑。每当不够高效的时候,或者你感觉 “一定有一个更好的方式”时, 尝试求助搜索引擎,很有可能有一个更好的方式。如果你遇到难题,可以来我们的答疑时间或者给我们发邮件。
- 在其他工具中设置 Vim 快捷键 (见上面的操作指南)。
- 进一步自定义你的
~/.vimrc
和安装更多插件。 - (高阶)用 Vim 宏将 XML 转换到 JSON (example-data.xml)。 尝试着先完全自己做,但是在你卡住的时候可以查看上面宏 章节。
习题解答
-
完成 vimtutor。 备注: 它在一个 80x24(80 列,24 行) 终端窗口看起来最好。
~$ vimtutor # 提示:命令行界面的“终端(T)”选项,可以直接设置窗口为80x24,vimtutor在该窗口下效果最好
-
下载我们的vimrc,然后把它保存到
~/.vimrc
。 通读这个注释详细的文件 (用 Vim!), 然后观察 Vim 在这个新的设置下看起来和使用起来有哪些细微的区别。 -
安装和配置一个插件:
ctrlp.vim
.1). 用
mkdir -p ~/.vim/pack/vendor/start
创建插件文件夹2). 下载这个插件:
cd ~/.vim/pack/vendor/start; git clone https://github.com/ctrlpvim/ctrlp.vim
下载后需要在~/.vimrc 中添加如下设置,参考这里3). 请阅读这个插件的文档。 尝试用 CtrlP 来在一个工程文件夹里定位一个文件, 打开 Vim, 然后用 Vim 命令控制行开始 :CtrlP.
4). 自定义 CtrlP: 添加 configuration 到你的 ~/.vimrc 来用按 Ctrl-P 打开 CtrlP~$ vim .vimrc # 进入.vimrc文件后,按住Ctrl+g键,跳转到文件末尾,按o键,在末尾添加如下内容即可: " 设置插件ctrlp set runtimepath^=~/.vim/pack/vendor/start/ctrlp.vim let g:ctrlp_map = '<c-p>' let g:ctrlp_cmd = 'CtrlP' let g:ctrlp_working_path_mode = 'ra' " 设置默认路径为当前目录 ~$ vim
- 进入vim编辑页面后,在正常模式下,按下Ctrl+p,可以打开插件
- 输入一串字符后,会在电脑查询匹配字符串的文件
-
练习使用
Vim
, 在你自己的机器上重做演示。 -
下个月用
Vim
完成 所有 的文件编辑。每当不够高效的时候,或者你感觉 “一定有一个更好的方式”, 尝试求助搜索引擎,很有可能有一个更好的方式。如果你遇到难题,来我们的答疑时间或者给我们发邮件。 -
在你的其他工具中设置
Vim
快捷键 (见上面的操作指南)。 -
进一步自定义你的
~/.vimrc
和安装更多插件。 安装插件最简单的方法是使用Vim
的包管理器,即使用vim-plug
安装插件:1). 安装
vim-plug
~$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
2). 修改 ~/.vimrc
~$ vim .vimrc # 进入.vimrc文件后,键入Ctrl+g,跳转到文件末尾,再键入o,在新的一行里输入以下内容,保存后退出,即可: call plug#begin() Plug 'preservim/nerdtree' " 需要安装的插件 nerdtree Plug 'wikitopian/hardmode' " 安装 hardmode call plug#end()
3). 在 vim 命令行中执行
:PlugInstall
~$ vim
- 进入一个空白的编辑页面后,键入 “:PlugInstall” ,即可
-
(高阶)用 Vim 宏将 XML 转换到 JSON (example-data.xml)。 尝试着先完全自己做,但是在你卡住的时候可以查看上面 宏 章节。
提示:
a. 先对example-data.xml
建立副本,在副本上操作;
b. 对下面的步骤中不清楚的命令,可以在vim编辑页面中,键入:help Command
进行查询;
c. 最终修改完成的json格式文件可参考example-data.json
vim example-data.xml
在vim编辑页面依序执行以下操作:
- 1). 删除首尾两行
Gdd
:跳转到最后一行,并删除该行ggdd
: 跳转到第一行,并删除该行
- 2). 录制寄存器
e
,实现对<name>
标签的处理/<name>
,再键入Enter
,然后键入N
:在文档的开头查找<name>
并跳转到文件的最后一个<name>
- 接下来,录制宏(即寄存器
e
):qe
:即将录制名为e
的宏^r"
:^
跳转到当前行的首个非空字符,即<name>
的<
,r"
将<
替换为"
f>s": "
:f>
查找>
,此处即匹配刚才的<name>
的>
;s"
将>
替换为": "
<Esc>
:回到正常模式f<C"
:查找下一个<
,由于xml文件的特征,此时匹配到的是刚才修改的<name>
对应的</name>
的<
;然后,将当前位置到本行末尾的内容删除,同时插入"
<Esc>
:回到正常模式q
:停止录制,寄存器e
完成
- 寄存器
e
的效果示例:将<name>Mike</name>
修改为"name": "Mike"
- 3). 录制寄存器
p
,实现对<person>
标签的处理gg
:跳转到文件开头(本例中,正好处于一个<person>
标签)- 接下来,录制宏(寄存器
p
)qp
:即将录制宏p
S{
:将当前行删除,并替换为{
<Esc>
:回到正常模式j@e
:移到下一行,并执行宏e
(因为本例的xml文件中,<person>
的下一行为<name>
)A,
:在本行的末尾添加上,
<Esc>
j@e
:移到下一行,并执行宏e
(本例中,完成对<email>
标签行的处理,其处理与<name>
标签行的一致)jS},
:跳转到下一行,并删除整行,同时添加上},
(本例中该行内容为:</person>
)<Esc>q
:回到正常模式,并停止录制,完成宏p
- 4). 录制寄存器
q
,实现从一个<person>
标签到另一个<person>
标签的跳转/<person>
:从第三步的位置开始查找下一个<person>
标签qq
:开始录制宏q
@p
:执行宏p
,可以将这一组<person>
标签包含的内容修改为json格式jq
:移到下一行,并停止录制,完成宏q
(此时,刚好处于下一个<person>
标签行)
- 5). 执行宏
q
到文件尾999@q
:执行999次宏q
(本文档总行数为1002行,执行999次能确保到达文件末尾)
- 6). 手动修改文件内容
gg
O
:(大写的o
)在开头插入如下内容{ "people": { "person": [
<Esc>
/<email>
:跳转到文件末尾唯一未处理的<email>
标签行@e
:对该标签行进行处理jS}
:移到下一行,删除</person>
标签<Esc>
o
:在文件末尾插入以下内容] } }