解决安装CentOS 8 Stream时提示”dracut initqueue timeout”的问题

昨天在一台Dell 5810上安装Centos 8 Stream,首先下载ISO文件制作了USB启动盘,在BIOS设置了USB启动后,顺利进入安装界面。

但在选择了 Install CentOS Stream 8-stream 后,并没有进入图形化安装界面,而是出现了下面的错误提示:

......
Nov 23 02:29:16 localhost dracut-initqueue[1065]: Warning: dracut-initqueue timeout - starting timeout scripts
Nov 23 02:29:16 localhost dracut-initqueue[1065]: Warning: dracut-initqueue timeout - starting timeout scripts
Nov 23 02:29:17 localhost dracut-initqueue[1065]: Warning: dracut-initqueue timeout - starting timeout scripts
Nov 23 02:29:17 localhost dracut-initqueue[1065]: Warning: dracut-initqueue timeout - starting timeout scripts
Nov 23 02:29:18 localhost dracut-initqueue[1065]: Warning: dracut-initqueue timeout - starting timeout scripts
Nov 23 02:29:18 localhost dracut-initqueue[1065]: Warning: Could not boot.
Nov 23 02:29:18 localhost systemd[1]: Starting Setup Virtual Console...
Nov 23 02:29:18 localhost systemd[1]: systemd-vconsole-setup.service: Succeeded.
Nov 23 02:29:18 localhost systemd[1]: Started Setup Virtual Console.
Nov 23 02:29:18 localhost systemd[1]: Starting Dracut Emergency Shell...

由于在启动过程中发现下面的 TSC_DEADLINE disabled 错误提示,以为是这个提示导致了上面的错误,所以花了很多时间解决这个问题:

Nov 23 02:25:55 localhost kernel: [Firmware Bug]: TSC_DEADLINE disabled due to Errata; please update microcode to version: 0x3a (or later)

费了半天力气后,发现 dracut initqueue timeout 跟上面的 TSC_DEADLINE disabled 没关系,完全是两个问题。

最终经过一番搜索,终于在 stackexchange.com的这个问题 中看到了 Anil Thomas 的回答,怀疑和启动时传入的设备名有关。于是在启动到 Dracut Emergency Shell 后,查看 /dev/disk/by-label/ 中的设备文件,输出如下:

dracut:/# ls -l /dev/disk/by-label/
total 0
lrwxrwxrwx 1 root root 10 Nov 23 02:26 CentOS-Stre -> ../../sda4
lrwxrwxrwx 1 root root 10 Nov 23 02:26 data -> ../../sdc1

然后重启计算机,在安装界面上,选择 Install CentOS Stream 8-stream,然后按 TAB 键显示启动参数,发现在启动参数中 inst.stage2 所使用的设备名和刚才在 Dracut Emergency Shell 中看到的不一致:

Install CentOS Stream 8-stream的启动参数

启动参数中的 hd lable是 hd:LABEL=CentOS-Stream-8-x86_64-dvd,但在 /dev/disk/by-label/ 目录中的设备名是 CentOS-Stre,推测是由于某种未知原因,设备名被截短了。于是把启动参数中 hd 参数部分改为 hd:LABEL=CentOS-Stre,修改后按回车键启动安装,果然顺利进入了图形化安装界面!

希望这篇文章能够对遇到此问题的朋友有所帮助!

leetcode挑战:“无重复字符的最长子串”之滑动窗口解决方案

<< 更多leetcode题目

题目链接

https://leetcode.cn/problems/longest-substring-without-repeating-characters/

难度

中等

介绍

上一篇易水介绍了自己对这个问题的解决方案,但那个方案执行效率和空间使用都很差。本文主要是介绍 stanislav-iablokov 的滑动窗口解决方案,他的算法时间复杂度为 O(n),空间复杂度为 O(1),完全可以用"简洁优雅"这四个字来形容!

C++解决方案

class Solution 
{
public:
    int lengthOfLongestSubstring(string s) 
    {
        int max_len = 0;

        // [1] longest substring is the one with the largest 
        //    difference between positions of repeated characters; 
        //    thus, we should create a storage for such positions 
        vector<int> pos(128,0);

        // [2] while iterating through the string (i.e., moving 
        //    the end of the sliding window), we should also 
        //    update the start of the window 
        int start = 0;

        for (int end = 0; end < s.size(); end++)
        {
            auto ch = s[end];

            // [3] get the position for the start of sliding window 
            //    with no other occurences of 'ch' in it 
            start = max(start, pos[ch]);

            // [4] update maximum length 
            max_len = max(max_len, end-start+1);

            // [5] set the position to be used in [3] on next iterations
            pos[ch] = end + 1;
        }

        return max_len;
    }
};

算法分析

stanislav-iablokov的算法,思路正是上一篇中易水提过的想法:最长不重复子串,是两个重复字符之间的最大长度。但当时易水陷入了思维定势,始终想用字符查找、比较解决问题,但没有想到滑动窗口的解决方法。
在此算法中,stanislav-iablokov用 start 表示滑动窗口的起始位置,初始值为 0,后续循环中会将其值设为离起点最远的重复字符所在位置的后面(即其数组下标加1);end表示滑动窗口的结束位置,初始值为 0,在循环中依次后移;pos[128]数组用于存放每个字母上一次出现过的位置,初始值为0,代表字母没出现过。
在循环中,end会依次后移,然后会检查start是否需要后移:如果出现了重复字符,且重复字符在当前start之后,则将start后移至该重复字符的后面;否则start保持不变。在确定了滑动窗口的起始地址后,计算窗口中字符长度,即为不重复字符的长度,将其最大长度保存至 max_len 变量即可。
算法的精妙之处在于如何确定滑动窗口起始位置:如果在当前start之后存在end位置上的字符,说明在当前startend间出现了重复字符,此时需要把start后移至重复字符的后面。移动之后,在startend之间就不存在重复字符了。
另外,这个算法还打破了易水的思维定势,程序中完全没有用到字符查找、字符比较,而是用数组下标代之!
易水画了一副示意图,展示了该算法对于字符串 pwkwew 的处理过程:

处理字符串"pwkwew"的过程

总结

以后在遇到下面这类问题时,可以考虑滑动窗口方案:

  • 给定一个序列,例如数组、字符串;
  • 需要处理的数据是其中的一个子序列或子字符串;
  • 给定了一个固定的窗口大小,或是要求找出窗口的大小(例如本题);

使用滑动窗口方案,关键在于窗口起点和终点的确定,这需要根据问题进行具体分析。

leetcode挑战: “无重复字符的最长子串”之易水解决方案

<< 更多leetcode题目

题目链接

https://leetcode.cn/problems/longest-substring-without-repeating-characters/

难度

中等

C++解决方案

class Solution {
public:
    // 计算从位置 start 开始不重复字符的最大长度
    int uniqueChars(string s, int start)
    {
        int len = 0;
        vector<char> chars;
        for (int i = start; i < s.size(); i++)
        {
            char c = s[i];
            // 检查此字符是否在前面出现过
            if (find(chars.begin(), chars.end(), c) == chars.end())
            {
                chars.push_back(c);
                len++;
            }
            else
            {
                break;
            }
        }

        return len;
    }

    int lengthOfLongestSubstring(string s) {
        int m = 0;
        // 依次遍历每个字符,计算最大长度
        for (int i = 0; i < s.size(); i++)
        {
            int len = uniqueChars(s, i);
            m = max(m, len);
        }

        return m;
    }
};

解题思路

如果只是要实现这道题目的要求,难度并不大。 易水的上述实现方案采用的是最笨的方法:遍历字符串,从某一位置开始、计算其后不重复字符的最大长度。

总结

上述方式虽然能实现功能,但此方法的时间复杂度为 O(n^2),空间复杂度为 O(n),执行效率和空间使用都比较差。事实也证明了这一点,这是 leetcode.com 对我的算法给出的评价:

Runtime: 819 ms, faster than 9.07% of C++ online submissions for Longest Substring Without Repeating Characters.
Memory Usage: 335.7 MB, less than 5.39% of C++ online submissions for Longest Substring Without Repeating Characters.

运行时间只超过了 9% 的 C++ 实现方案,而内存使用只比 5% 的 C++ 方案节省内存!
接下来易水尝试优化算法,当时闪过一个想法,所谓的“最长不重复子串”,可以定义为两个重复字符之间的最大长度,但易水在把这个想法转换为算法时遇到了障碍,始终无法摆脱字符查找、比较的思维定势,因此一直找不到更优解决方案。直到在评论区看到stanislav-iablokov的解法,才惊叹于他的实现方案之简洁优雅,我将在下一篇介绍他的解决方案,这一方案所采用的思路,正是易水上面提到的想法!

leetcode挑战: 两数相加

<< 更多leetcode题目

题目链接

https://leetcode.cn/problems/add-two-numbers/

难度

中等

C++解决方案

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:

    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int c = 0;
        ListNode* l = nullptr;
        ListNode** p = &l;
        while (l1 || l2 || c)
        {
            // 求和,需要考虑进位
            int v = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + c;
            // 计算是否产生进位
            c = v / 10;
            // 将node添加到新链表
            *p = new ListNode(v % 10);
            p = &(*p)->next;
            l1 = l1 ? l1->next : l1;
            l2 = l2 ? l2->next : l2;
        }

        return l;
    }
};

Python解决方案

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        if l1 is None or l2 is None:
            return None

        c = 0
        l3 = None
        while l1 is not None or l2 is not None or c != 0:
            # 求和,需要考虑进位
            v1 = 0 if l1 is None else l1.val
            v2 = 0 if l2 is None else l2.val
            v = v1 + v2 + c
            # 计算是否产生进位
            c = v // 10
            # 将node添加到新链表
            node = ListNode(v % 10)
            if l3 is None:
                l3 = node
                prev = l3
            else:
                prev.next = node
                prev = node
            l1 = l1 if l1 is None else l1.next
            l2 = l2 if l2 is None else l2.next

        return l3

解题思路

因为输入链表是逆序的,即低位数字在前,高位数字在后,所以直接遍历链表累加即可。唯一需注意的就是要考虑到两数相加可能会产生进位。

总结

这道题目踩了一个大坑!
易水最初拿到这个题目时,第一次尝试,是把输入的链表转换为 int 类型,求和后再转换为链表输出。但第一次提交时 leetcode 返回了Runtime Error!
原因是漏看了限制条件,这个链表长度最长为100,也就是说它可表示一个最长100位的整数!这远远超过了 int 类型可表示的范围(是否也超出了 uint64 所能表示的范围?),因此产生了溢出。 这种实现方式还存在另外一个问题,那就是当链表长度增加时,执行时间可能也会非常长,因为它需要做乘法操作!
所以,以后在分析题目时,一定要认真阅读限制条件,当限制条件不同时,可采取的解决办法也会随之变化。
Python 版本只是 C++ 版本的一个简单翻译。在实现中遇到两个问题,一是不了解 Optional 的用法,查了一下, Optional[x] 实际上就相当于 Union[x, None];二是不知道在 Python 中如何表示链表,后来仔细想了一下,PythonJava 类似,它的变量名就等同于 C++ 中的指针,所以链表的头指针实际上就是变量 l3

易水leetcode.com(力扣)解题系列: 目录

前几天刚刚知道leetcode.com这个网站,在上面试着做了几道题,觉得很有意思,于是产生了个想法,想把在leetcode上做过的题以及踩过的坑记录下来,当做是总结吧。
刚在网上搜了一下,leetcode网站有一个中文版本,叫做力扣,题库和主站差不太多,据说国内访问时会被直接重定向到力扣中文站,所以在贴题目链接时就贴中文站的链接了。只不过我是在leetcode.com上做的题,没在力扣上注册。如果想知道主站链接,只需要把链接中的 leetcode.cn 换成 leetcode.com 就可以了。
这个帖子汇总了所有我在leetcode上解题的文章。大多数题目,我会尝试分别用 C++Python 两种不同的语言来完成,主要是因为我对Python不够熟练,想多练习一下。
这里是我的leetcode用户页。

git: 找出谁删除了文件的内容

易水最近在工作中发现有文件的内容不正确,有一行内容被删除了,但不知道是谁、因为什么原因删除的,所以想查找一下删除该行内容的commit SHA1。不过由于这行内容已被删除,因此无法使用 git blame 查出commit号。在网上搜了一下,这种情况下可以用 git log -S 找到对应的提交号,具体用法见下。

假设在文件 ./README中,有人删除了一行,而这行内容中包含字符串 Who delete this line,那么就可以用下面的命令找到对应的commit:

git log -S "Who delete this line" ./README

输出如下:

commit 7f77ef68c00fc7c0c6cbe4c06614843bb1caf921 (HEAD -> master)
Author: Easwy <easwy@easwy.com>
Date:   Tue Nov 9 18:31:52 2021 +0800

    update README to delete one line

commit 2f50750d4597df244fe58099257dd287de62f4e2
Author: Easwy <easwy@easwy.com>
Date:   Tue Nov 9 18:31:28 2021 +0800

    add README

这条命令把 ./README 文件中涉及到 Who delete this line 内容的commits都列出来,此时再用 git show就可以轻松找出是哪个commit删除了该行。

git log -S 类似,你也可以使用 git log -G 命令进行查找,这条命令的参数是正则表达式,可以用来查找更为复杂的内容。

参考文档

让你的网站支持 https 访问

回本系列文章目录

在上一篇文章中,易水介绍了如何 对wordpress博客进行百度熊掌号改造,本篇文章将会介绍如何让你的网站支持 https 访问。

昨天在访问易水博客时,突然发现提示网站不安全,仔细一看是网站的 ssl 证书过期了。回想了一下,应该是上次在升级 Ubuntu 时导致证书不能正常更新造成的,所以需要重新更新一下证书。

易水博客上使用的是 letsencrypt.org 提供的免费证书方案。letsencrypt 是一个可以发放证书的 CA,只要在网站主机上运行一个支持 ACME 协议的程序,就可以通过它到 letsencrypt 获取、更新证书。

需要注意的是,要使用 letsencrypt 的证书方案,你需要能访问网站主机的 Linux shell。以下操作均需在网站主机的 shell 中操作。按网站上的建议,首先需要下载 certbot。易水博客上运行的是 Ubuntu + Nginx 组合,所以参考的是这篇文档

在 Ubuntu 18.04 上安装 certbot 需要 snapd,一般 snapd 在 Ubuntu 18.04 上是缺省安装的,不过易水的网站上不知为什么没安装,所以需要先安装它:

$ sudo apt install snapd

Snap 是一个全新的软件应用环境,它把软件封装在类似于 Docker 的容器中,即开即用。所以接下来就需要下载 Snap core 软件,然后安装 Certbot:

$ sudo snap install core
$ sudo snap refresh core
$ sudo snap install --classic certbot

上面的下载需要比较长时间,如果你总是下载失败,可能需要设置 snap 使用代理下载。安装完成后,需要创建一个符号链接,以保证 certbot 在你的执行路径中:

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

接下来就可以获取并安装证书了, Certbot 提供了单步操作,集获取证书、更新 Nginx 配置、使能 https 于一体,易水就偷了点懒,直接使用这条命令了,按命令的提示操作即可:

$ sudo certbot --nginx

执行完这条命令后,你的网站就支持 https 访问了。下载的证书放在 /etc/letsencrypt/live 目录中,你也可以查看一下 Certbot 对 Nginx 配置的修改。

不过这种方式获得的证书有限期只有3个月,所幸上述命令会启动定时任务定期去更新证书,所以你还需要测试一下证书能否正常更新:

$ sudo certbot renew --dry-run

如果由于某种原因你想删除 Certbot 安装的证书,你可以使用这条命令:

$ sudo certbot delete

参考文档

在 Linux 中配置双显示器

新加了一个显示器后,发现在 Linux 里的显示器位置和实际位置不符。本来我的主显示器 BENQ 在右边,而辅显示器 DELL 在左边,,但在 Linux 里,鼠标却要向右边移才能移到 DELL 显示器中,因此需要重新配置一下。

在 Linux 中配置显示器,最好用的是 Xorg 自带的 xrandr,这个工具使用起来很方便。首先查看一下显示器的信息:

$ xrandr --listmonitors
Monitors: 2
 0: +*HDMI-1 1920/621x1080/341+0+0  HDMI-1
 1: +DP-2 1680/433x1050/271+1920+0  DP-2

从上面的输出可以看到 X window 检测到两个显示器,BENQ 显示器的编号为0,接口为 HDMI-1,而 DELL 显示器的编号为1,接口为 DP-2。接下来调整一下两个显示器的位置:

$ xrandr --output HDMI-1 --primary --right-of DP-2

上面的命令把 HDMI-1 设置为主显示器,并把它置于 DP-2 的右边。命令执行完后,X Window 中显示器的位置就正常了。

接下来需要在每次启动时都执行这条命令,我用的是 Openbox 窗口管理器,因此把上述命令加到 Openbox 启动文件 ~/.config/openbox/autostart 中:

echo "xrandr --output HDMI-1 --primary --right-of DP-2" >> ~/.config/openbox/autostart

设置完成,每次启动后显示器的位置就会被正确设置。如果你使用 Gnome 或 KDE,可以把上述命令加入到 .xprofile 中。

xrandr 不仅可以配置显示器的位置,还可以查询显示器参数、配置分辨率或刷新频率等,请参考其帮助。

参考文档

对wordpress博客进行百度熊掌号改造

熊掌号

回本系列文章目录

上篇文章介绍了如何 使用阿里云的邮件推送服务发送邮件,本篇则记录了如何进行百度熊掌号改造。

百度在去年年底时正式推出了熊掌号,从名字看与腾讯的企鹅号有异曲同工之炒,阿里系也有自己的大鱼号,一时间各大互联网巨头都吹起了“集结号”,凭借各自优势继续跑马圈地。

百度熊掌号最吸引易水的一点是,熊掌号现在可以支持原创保护了!这样一来,即使那些所谓的资源收集网站PR值比你高,你的原创文章的搜索排名也会在那些网站之前。因此,在易水博客迁到阿里云服务器后,易水也对易水博客进行了熊掌号改造,本文记录了易水博客的改造过程。

如果你还没有自己的网站,可以参考易水的建站笔记来建设自己的网站。易水博客目前托管在阿里云上,使用易水的阿里云幸运券购买阿里云服务可以获得优惠。

开通熊掌号,并关联网站

首先需要申请熊掌号,并与网站相关联。易水是从百度搜索资源平台进入熊掌号首页,然后选择绑定熊掌号。

熊掌号绑定首页

由于易水以前并未申请过百家号,所以不能用百家号绑定,于是在下一步选择了 注册百度熊掌号。在这里需要吐槽一下百度,易水在这一步卡了很久,每次点注册百度熊掌号,弹出来的都是一个空白网页,开始以为是百度网站临时故障,结果后来发现不是,因为把Chrome换成IE后网页可以正常访问。以前只在小网站上遇到过这种情况,在互联网巨头身上还真没遇到过!

注册百度熊掌号

后面的注册和关联过程就不再一一详述了。这一步完成后,易水就把易水博客与名为 易水扬 的熊掌号关联到一起了。

网站熊掌号改造

百度对接入熊掌号的网站有一套的内容规范要求,所以接下来就需要对网站进行熊掌号改造。易水博客的主题用的是 wordpress 的官方主题 twentyseventeen,本文以此为例介绍改造的过程,其他主题的改造方法类似。

这里需要提一句,百度对HTML5页面和MIP页面的改造要求不尽相同,所以需要根据网站页面的具体形式选择不同的改造方式。易水此处进行的是H5页面改造。

添加熊掌号ID声明

打开主题的 header.php,在 </head> 标签前添加百度熊掌号ID,此处直接把熊掌号页面改造处的代码拷贝过来即可,下面是易水博客所用的代码,要记得把ID换成你自己网站的ID:

<!-- baidu XZH ID -->
<script src="//msite.baidu.com/sdk/c.js?appid=1588937706929265"></script>

添加关注功能代码

关注功能相关的代码可以加在顶部、段落之间或底部,易水是加在了底部,所以打开主题的 footer.php,在 </body> 标签之前添加如下代码:

<!-- baidu XZH footer -->
<script>cambrian.render('tail')</script>

加了这句话后,没看到页面有关注按钮,也许是这个功能还不支持?

添加canonical标签

接下来要对页面进行结构化改造,要求添加canonical标签,其 href 的内容为MIP页或H5页所对应的PC页地址,如果没有PC页的话,填写当前页面地址即可。这一步易水跳过了,因为易水博客上所使用的 Yoast SEO 插件会自动插入canonical标签。

添加 JSON-LD 数据

JSON-LD (JSON Linked Data)是一项 W3C 推荐规范,一般来说,网页的内容对人类来说很容易理解,但对计算机来说只能根据其中的关键字进行搜索,却无法理解其内容的含义。但为网页添加了 JSON-LD 数据后,就可以使计算机更容易理解网页的内容,并据这些内容提供相应的搜寻服务。也就是说,添加JSON-LD数据可以更好的支持机器学习,这样一来搜索引擎(或其他程序)就能更好的帮助人们找到真正需要的数据内容。

要添加 JSON-LD 数据,把下面这段代码放在 </body> 标签之前就可以了。需要把代码中的 appid 换成你自己的百度熊掌号ID:

<!-- baidu XZH ld+jason -->
<?php if ( is_single() || ( is_page() && ! twentyseventeen_is_frontpage() ) ): ?>
<script type="application/ld+json">
{
    "@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld",
    "@id": "<?php echo esc_url( get_permalink() ); ?>",
    "appid": "你的百度熊掌号ID",
    "title": "<?php echo wp_title('', false); ?>",
    "images": ["<?php the_post_thumbnail_url(); ?>"],
    "description": "<?php
        if ($post->post_excerpt) {
            $printDescription = wp_strip_all_tags($post->post_excerpt);
                } else{
            $printDescription = preg_replace('/\s+/','',mb_strimwidth(strip_tags($post->post_content),0,120,''));
        }
        echo $printDescription;
        ?>",
    "pubDate": "<?php echo get_the_date('Y-m-d\TH:i:s'); ?>",
    "upDate": "<?php echo get_the_modified_date('Y-m-d\TH:i:s'); ?>"
}
</script>
<?php endif; ?>

这里易水又要吐槽一下,上面的发布时间和更新时间居然都不包含时区信息,莫非百度认为所有的网站都位于中华大局域网?百度的格局实在太小了!

在线校验

在完成上述改造后,可以使用百度的在线校验工具对网页进行检查,点击 在线校验工具,然后输入你要检查的URL以及并把该页面的源代码拷贝过来,就可以进行检查了。

这一步易水又折腾了一番,刚开始以为百度会直接访问上面所填写的网址进行检查,所以始终不理解在线校验工具中“请填写对应URL的页面代码”这一栏填什么,后来才明白原来百度不会直接访问网址,而是需要把页面的HTML内容拷贝到这一栏。

在线校验工具

恭喜!

如果在线检验显示校验成功,就说明百度熊掌号改造已经完成了,恭喜,你的网站已经完成百度熊掌号改造了!

如果你对编程不太熟悉,那么也可以使用WordPress 熊掌号页面改造插件:Fanly XZH来进行熊掌号改造,具体方法请参考作者网页。

在下一篇文章中,易水将为你介绍如何让你的网站支持 https 访问

参考文档

vi / vim 初学者入门(系列文章)

Vi or Not Vi

做为一个有十多年 Vim 使用经验的 Vimer,易水几乎所有的文本编辑任务都是使用 Vim 完成的,由此可见易水对 Vim 这个编辑器的依赖程度。出于对 Vim 的喜爱,易水也一直积极在国内推广普及 Vim,其中最值得骄傲的是易水与车文隆一起翻译了 Practical Vim,中文译名为Vim实用技巧,这本书的第二版已经由人民邮电出版社出版了。除此之外,易水也写了Vi/Vim使用进阶系列文章帮助初步掌握了 Vim 命令的用户更高效的使用 Vim,你也可以在易水博客上找到更多与 Vim 相关的配置与技巧。

这些年随着 Linux 在国内的普及,越来越多的人开始接触 Linux,其中的大多数人也被迫地开始使用 Vi (其实是 Vim)。大概是 Vi/Vim 的学习曲线太过陡峭,所以易水身边的同事,大多数还只是停留在打开 Vi 编辑器、输入 i 命令,移动光标编辑,然后 :wq 退出。每次看到他们这样使用 Vi,坐在旁边易水心里都不免有些心急,因为只需要掌握几个简单的 Vim 命令,就可以让编辑工作变得更高效快捷。也正因为如此,易水萌发了为 Vi/Vim的初学者写些文章,使初学者不再将使用 Vi 视做畏途。本文即作为本系列文章的目录。

易水会尽量保持本系列文章篇幅较短,一方面由于易水的工作比较忙,没有太多时间精力,另一方面本系列文章计划在微信公众号和博客上同时发表,保持篇幅短小也方便读者快速阅读,同时避免内容过多使读者产生畏难情绪,好不容易才下定决心学习 Vim,看到这么难一下子就不想学了。

本文做为本系列文章的目录和总纲,方便大家查找和索引:

:文中的漫画由turnoff.us原创,LCTT GHLandy汉化。