├── .gitignore ├── 00-author.Rmd ├── 01-getting-started.Rmd ├── 02-completion.Rmd ├── 03-history.Rmd ├── 04-editor.Rmd ├── 05-tricks.Rmd ├── 06-goodies.Rmd ├── 07-finish.Rmd ├── Makefile ├── README.md ├── _bookdown.yml ├── _output.yml ├── css └── style.css ├── images ├── bash-comp-conf.png ├── bash-it-install.png ├── bashit-alias-git.png ├── bashit-show-alias.png ├── bashit-show-comp.png ├── bashit-show-plug.png ├── bashit-theme.png ├── caution.png ├── cli-hello.png ├── cmd-comp-more.png ├── completion-hostname.png ├── completion-prog-bash.png ├── completion-prog-zsh.png ├── cover-thumb.png ├── cover.jpg ├── cover.pdf ├── cover.png ├── cover.svg ├── dec_vt100.jpg ├── editor-emacs-view-body.png ├── editor-vi-view-body.png ├── gimp-comp.png ├── history-five.png ├── history-mode-i.png ├── history-search.png ├── history-word.png ├── hstr-arg.png ├── hstr.png ├── ibm_1620_model_1.jpg ├── ibm_1620_model_1_front_panel.jpg ├── important.png ├── linux_booting.png ├── man.png ├── note.png ├── ohmyzsh-install.png ├── ohmyzsh-theme.png ├── ohmyzsh-view-plug.png ├── shell.png ├── shell.svg ├── subcmd-comp.png ├── synhigh-a.png ├── synhigh-b.png ├── systemd.png ├── tip.png ├── upgrade-zsh.png ├── user-comp.png ├── var-comp.png ├── warning.png ├── xterm.png ├── zsh-autosug.png ├── zsh-cmd-opt-comp.png ├── zsh-stats.png ├── zsh-user-comp.png ├── zsh-var-comp.png └── zsh_prompt.png ├── index.Rmd └── latex ├── after_body.tex ├── before_body.tex ├── preamble.tex └── template.tex /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | _book 6 | usingcli.f* 7 | usingcli.i* 8 | usingcli.l* 9 | usingcli.aux 10 | usingcli.out 11 | usingcli.toc 12 | latex/__latex* 13 | -------------------------------------------------------------------------------- /00-author.Rmd: -------------------------------------------------------------------------------- 1 | # 作者简介 {#author .unnumbered} 2 | 3 | 徐小东,网名 ~toy。GNU/Linux 爱好者,DevOps 践行者。喜技术,好分享。通过 网站数年间原创及翻译文章达 3000 余篇。另著有[《像黑客一样使用命令行》](https://selfhostedserver.com/usingcli-book)、[《容器化工具三剑客:Podman、Buildah 和 Skopeo》](https://selfhostedserver.com/nextcontainer)、[《Terraform:自动化管理云基础设施》](https://selfhostedserver.com/terraform),译有[《笨办法学 Git》](https://selfhostedserver.com/learngit)、《Perl 程序员应该知道的事》等图书。Twitter:,Mail:。 4 | -------------------------------------------------------------------------------- /01-getting-started.Rmd: -------------------------------------------------------------------------------- 1 | \mainmatter 2 | 3 | # 入门指引 4 | 5 | 虽然如今计算机图形化界面大行其道,然而在计算机诞生之初却是命令行界面的天下。在图形化界面中,我们惯常使用鼠标来操作图标或窗口,从而完成各种任务。对于命令行界面来说,情况则有很大的不同。要在命令行界面下执行操作,我们需要更多的依赖键盘。那么,什么是命令行界面呢?在回答这个问题之前,不妨让我们先来谈谈控制台、终端、终端模拟器、以及 Shell 这几个基本概念。 6 | 7 | ## 控制台 8 | 9 | 控制台(Console),又称为系统控制台(System console)、计算机控制台(Computer console)、根控制台(Root console)、以及操作员控制台(Operator's console)。事实上,早先的控制台是一种用来操作计算机的硬件,如图 \@ref(fig:ibm-1620-model-1) 所示。^[https://en.wikipedia.org/wiki/System_console#/media/File:IBM_1620_Model_1.jpg]从这幅图片中,我们可以看到 IBM 1620 计算机的控制台由左边的操作前面板(参考图 \@ref(fig:ibm-1620-model-1-front-panel))和右边的打字机组成。通过控制台,操作员将文本数据或待执行的指令录入到计算机,并最终通过计算机读取或执行。 10 | 11 | ```{r ibm-1620-model-1, fig.cap='IBM 1620 的控制台'} 12 | knitr::include_graphics('images/ibm_1620_model_1.jpg') 13 | ``` 14 | 15 | ```{r ibm-1620-model-1-front-panel, fig.cap='IBM 1620 控制台的操作前面板'} 16 | knitr::include_graphics('images/ibm_1620_model_1_front_panel.jpg') 17 | ``` 18 | 19 | 随着计算机的发展,控制台从硬件概念变成了一个软件概念。于是,控制台有了新的称呼:虚拟控制台。虚拟控制台正好与物理的控制台硬件相对。通过观察 Linux 系统的启动过程,我们不难发现:在经过计算机硬件自检之后,一旦由引导载入程序接管,不一会儿便会进入系统控制台。在这个过程中,通常会显示如图 \@ref(fig:linux-booting) 所示的 Linux 系统引导信息。^[https://en.wikipedia.org/wiki/Linux_console#/media/File:Knoppix-3.8-boot.png] 20 | 21 | ```{r linux-booting, fig.cap='Linux 系统虚拟控制台'} 22 | knitr::include_graphics('images/linux_booting.png') 23 | ``` 24 | 25 | ## 终端 26 | 27 | 跟控制台一样,起初的终端(Terminal)也是一种计算机硬件设备。从外形上看,终端类似于我们今天所看到的显示器和键盘的结合体。通过终端,用户将指令和数据输入到计算机。同时,终端也将计算机执行的结果展示给用户。图 \@ref(fig:dec-vt100) 中显示的是曾经广为流行的终端 DEC VT100。^[https://en.wikipedia.org/wiki/Computer_terminal#/media/File:DEC_VT100_terminal.jpg] 28 | 29 | ```{r dec-vt100, fig.cap='DEC VT100 终端'} 30 | knitr::include_graphics('images/dec_vt100.jpg') 31 | ``` 32 | 33 | 或许你会产生疑问,为什么会出现终端这种硬件设备呢?以今天的眼光来看,显得似乎有些难以理解。诞生之初的计算机造价相当昂贵,可不像现在人人都能拥有一台那么简单。除了大型商业组织或大学研究机构,很难在别处看到计算机的身影。为了能够共享计算机资源,终端应运而生。然而,伴随着科技的进步,终端最终掉进了历史的黑洞。不过,它后来却以新的形式重生,这就是终端模拟器(Terminal emulator),或称之为虚拟终端。 34 | 35 | ## 终端模拟器 {#term} 36 | 37 | 终端模拟器,即用来模拟终端硬件设备的应用程序。在物理终端中存在的某些显示体系结构,比如用来控制色彩的转义序列、光标位置等在终端模拟器中也得到了支持。图 \@ref(fig:xterm) 显示 Linux 中流行的终端程序之一 XTerm。 38 | 39 | ```{r xterm, fig.cap='XTerm 终端模拟器'} 40 | knitr::include_graphics('images/xterm.png') 41 | ``` 42 | 43 | 不管是 Linux 操作系统,还是 macOS 操作系统,乃至 Windows 操作系统,今天都有许多终端模拟器可以选择。以下罗列的是这三个操作系统中比较流行的终端模拟器。 44 | 45 | ### Linux 46 | 47 | - [XTerm](https://invisible-island.net/xterm/):XTerm 是 X 窗口环境的默认终端。它提供了与 DEC VT102 和 Tektronix 4014 终端兼容的特性。此外,它也支持 ISO/ANSI 彩色模式。 48 | - [GNOME Terminal](https://gitlab.gnome.org/GNOME/gnome-terminal/):GNOME Terminal 是 GNOME 桌面环境的默认终端。它提供了与 XTerm 相似的特性。除此之外,它也包括支持多配置、标签页、鼠标事件等其它功能。 49 | - [Konsole](https://kde.org/applications/system/konsole/):Konsole 是 KDE 桌面环境的默认终端。它包括标签页、多配置、书签支持、搜索等特性。 50 | - [rxvt-unicode](http://software.schmorp.de/pkg/rxvt-unicode.html):rxvt-unicode 原本克隆自 rxvt,但加入了 unicode 支持,具有很强的定制特性。另外,rxvt-unicode 还包含 Daemon 模式、嵌入了 Perl 编程语言等功能。本书作者使用的就是这款终端模拟器。 51 | 52 | ### macOS 53 | 54 | - Terminal.app:Terminal.app 是 macOS 操作系统默认的终端。它的功能不多,除了提供设置 `TERM` 环境变量的选项外,还包括能够使用其搜索功能来查找 Man pages。 55 | - [iTerm2](https://www.iterm2.com/):iTerm2 是 macOS 系统上针对默认终端的开源替代品。它非常流行,包含许多很棒的功能,比如窗口分割、自动补全、无鼠拷贝、粘贴历史等等。如果你在 macOS 上工作,那么不妨使用 iTerm2 这款终端模拟器,相信它所具有的功能一定不会让你失望。 56 | 57 | ### Windows 58 | 59 | - [Mintty](https://mintty.github.io/):Mintty 是一个支持 Cygwin、MSYS、WSL 等多种环境的终端模拟器。它的功能与 XTerm 兼容,包括 256 色和真彩色、unicode、以及 Emoji 表情支持。 60 | - [ConEmu](https://conemu.github.io/):ConEmu 是 Windows 上一款相当流行的开源终端模拟器。它包含标签页、多种图形窗口模式、用户友好的文本块选择等功能。 61 | 62 | ## Shell 63 | 64 | Shell 是一种命令解释程序,它负责用户输入命令的读取、解析和执行。现代 Shell 除了具有与用户直接交互的特性之外,通常也包含编程功能,支持变量、数组、函数、循环、条件等编程基本要素。 65 | 66 | Shell 之所以如此称呼,是由于它相对 Unix 及 Linux 的核心——内核(Kernel)而言,处于整个操作系统的最外层,就像乌龟的壳一样。也正因为如此,Shell 提供用来访问系统服务的用户界面,扮演着与内核交互的角色,如图 \@ref(fig:shell) 所示。 67 | 68 | ```{r shell, out.width='50%', fig.align='center', fig.cap='Shell 与内核'} 69 | knitr::include_graphics('images/shell.png') 70 | ``` 71 | 72 | 在 Unix 及 Linux 的发展过程中,出现了许多种 Shell,其中比较知名的包括:sh、csh、ksh、bash、zsh 等等。 73 | 74 | ### sh {#sh} 75 | 76 | sh,即 Bourne shell,它是 Unix 第 7 版的默认 Shell。Bourne shell 由贝尔实验室的 Stephen Bourne 开发,于 1979 年发布。随着《Unix 编程环境》(Brian Kernighan 与 Rob Pike 著)一书的出版,sh 变得大为流行。 77 | 78 | Bourne shell 早已被后来的 Shell 所取代,现代 Linux 系统中的 sh 通常是符号链接的某个兼容 Shell。例如,本书作者所用的 Debian 9 里的 sh 为 dash。 79 | 80 | ```bash 81 | root@toydroid:~# ls -l /bin/sh 82 | lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash 83 | ``` 84 | 85 | 而在作者的另一个系统 Arch Linux 上,sh 则为 bash。 86 | 87 | ```bash 88 | root@codeland:~# ls -l /bin/sh 89 | lrwxrwxrwx 1 root root 4 Feb 7 15:15 /bin/sh -> bash 90 | ``` 91 | 92 | ### csh 93 | 94 | csh 是 C shell 的简称,它由 Bill Joy 开发,通过 BSD 得到了广泛的分发。在风格上,开发者将 csh 设计得像 C 编程语言一样,因而由此得名。同时,csh 具有很不错的交互使用体验。后来被其它 Shell 所吸收的诸如历史、别名、目录栈、文件名补全、作业控制等特性均出自 csh。 95 | 96 | csh 有一个改进版本叫 tcsh,目前是 FreeBSD 的默认 Shell。 97 | 98 | ### ksh 99 | 100 | ksh 指 Korn Shell,其开发者为 David Korn,在 1983 年公布于世。ksh 遵循 POSIX 标准,能够向下兼容 Bourne shell,整合了来自 C shell 的诸多特性。ksh 的一大亮点是引入了 vi 和 Emacs 风格的命令行编辑模式,使用户完全可以按照自己的按键习惯操作。此外,在 ksh 中还增加了关联数组的特性。 101 | 102 | 由于 ksh 最初以私有软件的形式进行分发,从而被限制了传播。代之以出现的替代品包括 pdksh(public domain ksh,公有域的 ksh)、mksh(后成为 Android 的默认 Shell)等。 103 | 104 | ### bash 105 | 106 | bash 作为理查德·斯托曼 GNU 工程的一部分出现,从它诞生之初就是为了用来取代 Bourne shell(参考 \@ref(sh) 节)。Brian Fox 开发了最初的 bash,首个版本发布于 1989 年。如今,bash 已变得十分流行,它是大多数 Linux 发行版以及 macOS 的默认 Shell。此外,通过 WSL(Windows Subsystem for Linux),在 Windows 10 中也可以安装并使用 bash。 107 | 108 | bash 的名称来自于 **B**ourne-**a**gain **sh**ell,它也遵循 POSIX 标准,其特性吸收自 sh、csh、ksh 等多种 Shell。 109 | 110 | ### zsh 111 | 112 | zsh 是 Z shell 的简称,最初的版本由 Paul Falstad 所开发,发布于 1990 年。zsh 极大的扩展了 Bourne shell 的功能,并包含来自 tcsh、ksh、bash 等 Shell 的特性。 113 | 114 | 在交互用户体验上,zsh 尤其出彩。比如,它支持对命令的选项进行补全、可以设置右提示符等,如图 \@ref(fig:zsh-prompt) 所示。 115 | 116 | ```{r zsh-prompt, fig.cap='zsh 的右提示符'} 117 | knitr::include_graphics('images/zsh_prompt.png') 118 | ``` 119 | 120 | 本书主要讨论 bash 和 zsh 这两种目前市面上最流行的 Shell。 121 | 122 | ## 命令行界面 123 | 124 | 命令行界面(Command-line interface),经常缩写为 CLI,亦即用户输入命令的地方。一旦用户将命令输入完毕并加以提交后,后续对命令的解析以及执行的任务都由 Shell 来完成。 125 | 126 | 与 CLI 相对的是 GUI,即 Graphical user interface,意为图形用户界面,它采用图形化的方式让用户与计算机进行交互。因其具有容易使用的优点,包括 Linux、macOS、Windows 等在内的现代操作系统无一例外都提供了图形用户界面。 127 | 128 | 既然图形用户界面要比命令行界面更加易用,那么是否说明可以完全抛弃命令行界面呢?答案是并非如此。事实上,有经验的用户尤其擅长使用命令行界面,其理由至少包括以下几个方面。 129 | 130 | ### 功能强大 131 | 132 | 让我们先来看一个例子: 133 | 134 | ```bash 135 | xiaodong@codeland:~$ history | 136 | awk '{CMD[$2]++;count++;}END \ 137 | { for (a in CMD)print CMD[a] " " \ 138 | CMD[a]/count*100 "% " a;}' | 139 | grep -v "./" | 140 | column -c3 -s " " -t | 141 | sort -nr | 142 | nl | 143 | head -n10 144 | ``` 145 | 146 | 在作者的 macOS 系统上执行这条命令后,其输出结果如下: 147 | 148 | ```bash 149 | 1 1348 14.3771% cd 150 | 2 1034 11.0282% l 151 | 3 838 8.93771% git 152 | 4 569 6.06869% ssh 153 | 5 513 5.47142% cat 154 | 6 405 4.31954% vim 155 | 7 372 3.96758% brew 156 | 8 360 3.83959% scp 157 | 9 265 2.82637% rm 158 | 10 264 2.8157% grep 159 | ``` 160 | 161 | 这条命令虽然看起来似乎有些“吓人”,因为它由 history、awk、grep、column、sort、nl、head 等 7 个命令组成,并通过管道符(|)串接在一起;然而其结果却颇为有趣。它将作者平时在命令行中执行的所有命令都进行了统计,最终展示出 10 个最常用的命令,并相应列出每个命令的使用次数和所占百分比。 162 | 163 | 管道符将前一命令的输出作为后一命令的输入,使这些表面上不相干的命令进行协同工作,犹如搭积木一般。这是命令行的真正威力所在。 164 | 165 | ### 灵活高效 166 | 167 | 再看另一个例子,假如我们打算从 photos 目录中找出今年三月份拍摄的照片,并将其文件名称保存到 mar_photos.txt 这个文本文件中。在图形用户界面中,首先,我们可能会打开一个文件管理器(在 Linux 下也许是 GNOME Files,macOS 中则是 Finder)。接着,导航到 photos 这个目录,同时切换成详细视图模式。然后,我们睁大双眼逐一找出符合要求的照片。可是,现在怎么把照片的文件名称写到文本文件中呢?我们当然可以直接输入,或者想省点力使用复制和粘贴也行。要是找出的文件数量比较多,那可绝对是体力活。 168 | 169 | 但是,如果在命令行下,那么我们只需通过执行一条命令即可达到目的: 170 | 171 | ```bash 172 | xiaodong@codeland:~$ cd photos; \ 173 | ls -l | grep 'Mar' | awk '{ print $9 }' > mar_photos.txt 174 | ``` 175 | 176 | ### 能自动化 177 | 178 | 使用命令行还有一个很棒的优势,那就是能够自动化各种操作。Shell 允许我们将所用的命令编写成函数(Function)或脚本(Script)。这样,我们不仅可以反复执行它们,而且函数或脚本比手动输入效率更高。由此,我们得以从重复的劳动中解放出来,从而能够腾出时间去做其它有意义的事情。 179 | 180 | ```bash 181 | xiaodong@codeland:~$ ./script.sh 182 | ``` 183 | 184 | ## 如何进入命令行 185 | 186 | 通过前面的描述,现在你应当了解:我们想要输入命令的界面是由 Shell 提供的。那么,如何执行 Shell 呢?我们可以通过下面两种方法来进入命令行。 187 | 188 | ### 通过控制台进入命令行 189 | 190 | 为了节省系统资源,Linux 服务器通常没有附带图形用户界面。当它启动完毕时,在控制台按照提示输入用户帐号及密码并登录后,所进入的即是命令行界面。以下为 Linux 服务器的登录提示: 191 | 192 | ```bash 193 | login: 194 | Password: 195 | ``` 196 | 197 | 作为普通用户来说,一般使用的是具有图形用户界面的 Linux 桌面系统。在它启动后就直接进入了桌面,那么此时想要进入控制台,可以按照下列步骤执行: 198 | 199 | 1. 按 Ctrl + Alt + F1 组合键,进入编号为 1 的控制台。 200 | 201 | 2. 按 Ctrl + Alt + F2 组合键,进入编号为 2 的控制台。 202 | 203 | 3. 依次类推,可以分别进入 3 号、4 号、5 号、以及 6 号控制台。在默认情况下,Linux 一般提供 6 个控制台。 204 | 205 | 4. 如果要从控制台返回到桌面,则可以按 Ctrl + Alt + F7 组合键。 206 | 207 | ```{block2, type='rmdtip'} 208 | 要是在控制台的丛林中迷失了方向,我们可以执行 `tty` 命令来了解当前在哪个控制台。 209 | ``` 210 | 211 | ### 通过终端模拟器进入命令行 212 | 213 | 另外一种进入命令行界面的方法是使用终端模拟器。在不同的操作系统中,可以选择的终端模拟器程序也有所不同(参考 \@ref(term) 节)。本书作者在 Linux 下常用 rxvt-unicode,macOS 中则使用 iTerm2。 214 | 215 | 一般而言,终端模拟器程序会跟系统的登录 Shell(或称默认 Shell)绑定在一起。有些终端模拟器程序提供了更改 Shell 的特性,从而使用户可以方便的选择自己惯用的 Shell。如果不能从终端程序中直接更改 Shell,那么也可以通过 `chsh` 命令来改变登录 Shell。假如我们想把默认 Shell 更改成 zsh,则可以执行以下命令: 216 | 217 | ```bash 218 | xiaodong@codeland:~$ chsh -s /bin/zsh 219 | ``` 220 | 221 | ```{block2, type='rmdtip'} 222 | 怎么判断当前所用的 Shell 是哪一种呢?只需执行 `echo $SHELL` 即可。 223 | ``` 224 | 225 | ## 你好,命令行 226 | 227 | 在《C 程序设计语言》中,作者 Brian W. Kernighan 和 Dennis M. Ritchie 介绍的第一个程序是在屏幕上输出一行“Hello world”的消息。为了说明命令行的使用,我们也将在屏幕上输出类似的消息——“你好,命令行”。 228 | 229 | 当我们进入控制台或打开终端模拟器时,通常会看到跟图 \@ref(fig:cli-prompt) 相似的命令行界面。 230 | 231 | ```{r cli-prompt, fig.cap='命令行界面'} 232 | knitr::include_graphics('images/cli-hello.png') 233 | ``` 234 | 235 | 从图 \@ref(fig:cli-prompt) 中我们可以看到命令行一般由下面几个部分组成: 236 | 237 | 1. 当前登录的用户名称,在本例中是 `xiaodong`。 238 | 239 | 2. `codeland` 是主机名称,跟 `hostname -s` 的输出一致。 240 | 241 | 3. 当前工作目录,`~` 代表用户的主目录,在 Linux 系统下也就是 `/home/<用户名>`,macOS 中则为 `/Users/<用户名>`。 242 | 243 | 4. `$` 为命令提示符。通常普通用户的命令行提示符与超级用户(root)的不同,以 bash 为例,root 用户的命令行提示符为 `#`。 244 | 245 | 5. 待执行的命令,在本例中是 `echo -e "\t你好,命令行"`,除 `echo` 命令本身外,还包括该命令的选项(`-e`)以及参数(`\t你好,命令行`)等部分。命令的选项参数一般由引号(`"`)引起,以避免诸如空格之类的特殊字符所导致的岐义。可以使用单引号(`'`)或双引号(`"`),但语意会不同。 246 | 247 | 除了这 5 个部分之外,在这个命令行中,我们还可以看到 `@`、`:`、以及 `` ` ` ``(空格)等字符。`@` 一般用来分隔用户名和主机名,其形式跟电子邮箱地址一样。`:` 在这里起到提示说明作用。空格则常常用来分隔命令的选项和参数。因为命令行提示符可以定制,所以你的命令行界面可能跟我们在这里介绍的不同。 248 | 249 | 现在,请你跟我们一起,在命令行的提示符(`$` 或 `#`)后面输入 `echo -e "\t你好,命令行"`。如果在输入过程中有错误,不必慌张,按**退格键**(BackSpace)或**删除键**(Delete)删除后重新输入即可。当所有字符全部输入完成后,按下**回车键**(Enter)。 250 | 251 | 发现了什么?命令行向我们回显了一条“你好,命令行”的消息。而且 `echo` 命令参数中的 `\t` 在输出中产生了一个制表符(Tab),从而让消息有了缩进效果。 252 | 253 | ```bash 254 | xiaodong@codeland:~$ echo -e "\t你好,命令行" 255 | 你好,命令行 256 | ``` 257 | 258 | 恭喜!你刚刚在命令行成功执行了一条命令,是否感觉并没有想象中那么恐怖呢?在后面的章节中,我们将教你如何更加高效的使用命令行,从而提升你的工作效率。 259 | -------------------------------------------------------------------------------- /02-completion.Rmd: -------------------------------------------------------------------------------- 1 | # 神奇补全 2 | 3 | 如果你编写过代码,那么一定听说过“代码补全”吧。在如今流行的代码编辑器和 IDE (集成开发环境) 中,这绝对是一项深受大家喜欢的功能。在此我要讲的 Shell 补全与它很相似。我相信,在学习了本章所讲的内容后,你肯定会爱上它。首先,我们会谈谈什么叫自动补全,然后看看如何触发自动补全,接着详细探讨诸如文件名或路径名、程序名或命令名、用户名、主机名、以及变量名等各种自动补全类型,最后再介绍可编程补全。 4 | 5 | ## 何谓补全 6 | 7 | 现在回过头来看,在学习命令行时,我最想率先学习的功能一定是自动补全。为什么这么说呢?因为自动补全这项功能让我们只需输入开头的一个或几个字符便能通过 Shell 自动补全剩下的内容。对于痛恨输入长命令或文件名的朋友而言,自动补全绝对是福音。自动补全不仅减少了输入,而且节省了时间,从而极大的提高了我们的操作效率。 8 | 9 | 让我们通过一个例子来说明何谓自动补全。首先,我通过在 bash 中直接输入完整的命令行 10 | 11 | ```bash 12 | xiaodong@codeland:~$ ls -l reallylongname.txt 13 | ``` 14 | 15 | 来查看 `reallylongname.txt` 这个文本文件的信息。 16 | 17 | 然后,我在输入 18 | 19 | ```bash 20 | xiaodong@codeland:~$ ls -l r 21 | ``` 22 | 23 | 之后按 **Tab 键**,于是 bash 帮我自动补全了该文件名剩下的部分。 24 | 25 | ```bash 26 | xiaodong@codeland:~$ ls -l reallylongname.txt 27 | ``` 28 | 29 | 比较两次输入,bash 帮我少输了 17 个字符。是不是感觉很爽呢? 30 | 31 | 再看一个例子:这次,我在输入 32 | 33 | ```bash 34 | xiaodong@codeland:~$ ls -l f 35 | ``` 36 | 37 | 后按 **Tab**,bash 自动补全了 `file`。 38 | 39 | ```bash 40 | xiaodong@codeland:~$ ls -l file 41 | ``` 42 | 43 | 接着,我连按两下 **Tab**,这时 bash 向我们展示了可以自动补全的文件名列表,总共包括 5 个项目。 44 | 45 | ```bash 46 | xiaodong@codeland:~$ ls -l file 47 | file1 file2 file3 file4 file5 48 | ``` 49 | 50 | 我输入 `1` 来完成 bash 自动补全过程。 51 | 52 | 比较这两个例子,我们可以发现,如果我们输入的开头字符唯一,那么 bash 将直接自动补全余下的内容。反之,则提供一个可供补全的备选列表。不过,这时候需要我们连按两下 **Tab 键**。这样的话,经常操作起来感觉还是有点麻烦。 53 | 54 | 下面我们对 bash 自动补全的配置进行一番优化,使之更加好用。利用文本编辑器打开 `~/.inputrc` 文件 (若不存在,则创建一个),加入下列内容: 55 | 56 | ```bash 57 | # completion 58 | set show-all-if-ambiguous on 59 | set visible-stats on 60 | set colored-completion-prefix on 61 | ``` 62 | 63 | 其中,开启 `show-all-if-ambiguous` 这个选项后,我们只需按一次 **Tab** 即可看到备选补全列表;`visible-stats` 选项通过在列表项目尾部添加指示符号来说明类型,例如:`@` 代表符号链接、`/` 代表目录等;最后的 `colored-completion-prefix` 选项则给补全的前缀字符加上颜色。如图 \@ref(fig:bash-comp-conf) 所示。 64 | 65 | ```{r bash-comp-conf, fig.cap='bash 自动补全配置结果'} 66 | knitr::include_graphics('images/bash-comp-conf.png') 67 | ``` 68 | 69 | ## 补全触发按键 70 | 71 | 通过这些例子,我们也可以知道,要触发自动补全,一般只要按 **Tab 键**即可。bash 和 zsh 都是这个默认设定。 72 | 73 | ## 文件名、路径名补全 74 | 75 | 前面的例子显示的是在 bash 中文件名自动补全的情况。下面我们看一个在 zsh 中自动补全文件名的例子。当我输入 76 | 77 | ```bash 78 | xiaodong@codeland:~$ ls -l f 79 | ``` 80 | 81 | 后按 **Tab**,zsh 为我自动补全了 `file`。 82 | 83 | ```bash 84 | xiaodong@codeland:~$ ls -l file 85 | ``` 86 | 87 | 我接着再按 **Tab 键**,这时 zsh 提供了可以备选的自动补全菜单。 88 | 89 | ```bash 90 | xiaodong@codeland:~$ ls -l file 91 | file1 file2 file3 file4 file5 92 | ``` 93 | 94 | 再次按 **Tab** 则可以选择具体的菜单项目。 95 | 96 | ```bash 97 | xiaodong@codeland:~$ ls -l file2 98 | file1 **file2** file3 file4 file5 99 | ``` 100 | 101 | 然后按**回车键**完成自动补全过程。 102 | 103 | ```bash 104 | xiaodong@codeland:~$ ls -l file2 105 | ``` 106 | 107 | 最后再按一次**回车键**执行命令。 108 | 109 | 不知大家有没有发现与 bash 补全的区别呢?bash 提供的备选补全列表不能选择具体的项目,而 zsh 则可以。这也说明与 bash 相比,zsh 具有更棒的用户交互功能。所以我平常也更喜欢使用 zsh 一些。如果你还没有用过 zsh,那么我在此建议你一定要试一试。 110 | 111 | 说到备选补全列表,在 bash 中我喜欢使用的一组快捷键是 **Alt + ?**。当 bash 补全 `file` 后,我没有按 **Tab**,而是按 **Alt + ?**,bash 就立即呈现了备选补全列表。 112 | 113 | ```bash 114 | xiaodong@codeland:~$ ls -l file 115 | file1 file2 file3 file4 file5 116 | ``` 117 | 118 | 还有一种情况,有时候我们希望 Shell 不要补全某些特别的文件类型。为了达到这种效果,我们可以使用 `FIGNORE` 变量。在下面的例子中,我想查看 `Welcome.java` 的内容,因此只想 Shell 补全 `.java` 文件,并排除 `.class` 文件。 119 | 120 | ```bash 121 | xiaodong@codeland:~$ cat W 122 | Welcome.class Welcome.java 123 | xiaodong@codeland:~$ cat Welcome. 124 | ``` 125 | 126 | 在将 `.class` 扩展名赋给 `FIGNORE` 变量后,Shell 就为我剔除掉了 `.class` 文件类型。 127 | 128 | ```bash 129 | xiaodong@codeland:~$ FIGNORE='.class' 130 | xiaodong@codeland:~$ cat W 131 | xiaodong@codeland:~$ cat Welcome.java 132 | ``` 133 | 134 | 如果想要排除多种文件类型,则只需用 `:` (冒号) 分隔即可。例如: 135 | 136 | ```bash 137 | xiaodong@codeland:~$ FIGNORE='.o:.class' 138 | ``` 139 | 140 | 这将让 Shell 在自动补全时排除 `.o` 和 `.class` 文件。无论是 bash,还是 zsh,当前都支持 `FIGNORE`。 141 | 142 | 路径名补全和文件名补全很相似,只是在补全后自动追加上 `/` (斜杠),便于我们输入下一级的路径名。在下例中,我在输入 143 | 144 | ```bash 145 | xiaodong@codeland:~$ cd g 146 | ``` 147 | 148 | 后按 **Tab**,Shell 补完了全名,并在其后添加了一个 `/`。 149 | 150 | ```bash 151 | xiaodong@codeland:~$ cd guessing_game/ 152 | ``` 153 | 154 | 之后我接着输入 `s` 并按 **Tab**,这次 Shell 补全了下级目录 `src`。 155 | 156 | ```bash 157 | xiaodong@codeland:~$ cd guessing_game/src/ 158 | ``` 159 | 160 | 对于开头字符不唯一的情况,跟文件名补全也是一样,bash 中只要按 **Tab** 即可看到备选补全列表。 161 | 162 | ```bash 163 | xiaodong@codeland:~$ cd h 164 | hello/ hello_world/ 165 | xiaodong@codeland:~$ cd hello 166 | ``` 167 | 168 | 顺便提一句,自动补全不光在命令行下使用,即便在有些图形化程序中也能使用。比如,在 GIMP 中,我也可以通过自动补全来打开文件。如图 \@ref(fig:gimp-comp) 所示。 169 | 170 | ```{r gimp-comp, fig.cap='在 GIMP 中自动补全文件名'} 171 | knitr::include_graphics('images/gimp-comp.png') 172 | ``` 173 | 174 | ## 程序名、命令名补全 175 | 176 | 不带选项的程序名、命令名补全几乎跟文件名补全一样,让我们来看一个例子。要是你看过《黑客帝国》这部电影,那么下面的画面你应该会很熟悉。当我输入 177 | 178 | ```bash 179 | xiaodong@codeland:~$ cmat 180 | ``` 181 | 182 | 后按 **Tab**,bash 立即为我自动补全了 `cmatrix` 命令。 183 | 184 | ```bash 185 | xiaodong@codeland:~$ cmatrix 186 | ``` 187 | 188 | 而当我在输入 189 | 190 | ```bash 191 | xiaodong@codeland:~$ cma 192 | ``` 193 | 194 | 后按 **Tab**,bash 为我提供了一个备选补全列表。 195 | 196 | ```bash 197 | xiaodong@codeland:~$ cma 198 | cmake cmake-gui cmapcube cmark cmark-gfm cmatrix 199 | ``` 200 | 201 | 此时,需要再输入 `t` 才能完成补全。 202 | 203 | ```bash 204 | xiaodong@codeland:~$ cmat 205 | ``` 206 | 207 | 如果我在仅仅输入 `c` (命令开头的第一个字符) 208 | 209 | ```bash 210 | xiaodong@codeland:~$ c 211 | ``` 212 | 213 | 后便按 **Tab**,这时 bash 询问我:“Display all 474 possibilities? (y or n)” (是否显示所有 474 个补全列表项目) 按 **y** 予以显示。按 **n** 则不显示。 214 | 215 | ```bash 216 | xiaodong@codeland:~$ c 217 | Display all 474 possibilities? (y or n) 218 | ``` 219 | 220 | 因为可供自动补全的列表项目太多,一屏已经显示不下了,所以 bash 使用 `more` 这个页面查看程序来呈现。现在按 **Space (空格键)** 可以翻页,如果想退出,那么按 **q** 即可。如图 \@ref(fig:cmd-comp-more) 所示。 221 | 222 | ```{r cmd-comp-more, fig.cap='命令自动补全备选列表'} 223 | knitr::include_graphics('images/cmd-comp-more.png') 224 | ``` 225 | 226 | 除了直接补全命令名之外,Shell 也能自动补全程序的子命令,例如:`git status` 中的 `status` 以及命令的选项。不过,bash 需要安装一个单独的 `bash-completion` 包;而 zsh 因为内置了对此功能的支持,所以不需要额外的包。 227 | 228 | [bash-completion 的源代码](https://github.com/scop/bash-completion)位于 GitHub 上,在此可以了解如何对其安装和配置。例如: 229 | 230 | 在 Debian 中,我们可以通过执行 231 | 232 | ```bash 233 | xiaodong@codeland:~# apt install bash-completion 234 | ``` 235 | 236 | 来安装它。 237 | 238 | 在 CentOS 上,除了安装 `bash-completion` 外,我推荐把 `bash-completion-extras` 也装上。 239 | 240 | ```bash 241 | xiaodong@codeland:~# yum install bash-completion bash-completion-extras 242 | ``` 243 | 244 | 而在 Arch Linux 上,则可以执行 245 | 246 | ```bash 247 | xiaodong@codeland:~# pacman -S bash-completion 248 | ``` 249 | 250 | 进行安装。 251 | 252 | 要配置 `bash-completion`,则只需要将下面这行指令加入 `~/.bashrc` (个人) 或 `/etc/bash.bashrc` (全局) 即可。 253 | 254 | ```bash 255 | [ -r /usr/share/bash-completion/bash_completion ] \ 256 | && . /usr/share/bash-completion/bash_completion 257 | ``` 258 | 259 | 在正常使用 zsh 的命令补全功能之前,我们也需要将下列内容加入到 `~/.zshrc` 配置文件中: 260 | 261 | ```bash 262 | # completion 263 | autoload -U compinit 264 | compinit -i 265 | ``` 266 | 267 | 让我们先来看一个自动补全命令选项的例子。我在输入 268 | 269 | ```bash 270 | xiaodong@codeland:~$ find - 271 | ``` 272 | 273 | 后便立即按 **Tab**,bash 马上列出了可以自动补全的选项列表。如图 \@ref(fig:subcmd-comp) 所示。 274 | 275 | ```{r subcmd-comp, fig.cap='命令选项自动补全备选列表'} 276 | knitr::include_graphics('images/subcmd-comp.png') 277 | ``` 278 | 279 | 我接着输入 `ina` 280 | 281 | ```bash 282 | xiaodong@codeland:~$ find -ina 283 | ``` 284 | 285 | 并再次按下 **Tab**,此时 bash 自动补全了 `-iname` 选项。 286 | 287 | ```bash 288 | xiaodong@codeland:~$ find -iname 289 | ``` 290 | 291 | 下面的例子演示了 bash 补全子命令的情形。在输入 292 | 293 | ```bash 294 | xiaodong@codeland:~$ git in 295 | ``` 296 | 297 | 后,按 **Tab**,bash 提供可以自动补全的子命令列表。 298 | 299 | ```bash 300 | xiaodong@codeland:~$ git in 301 | info init instaweb 302 | ``` 303 | 304 | 跟着输入 `i` 305 | 306 | ```bash 307 | xiaodong@codeland:~$ git ini 308 | ``` 309 | 310 | 并再按 **Tab**,这次 bash 便自动补全了子命令 `init`。对于执行 `git status` 子命令的过程同样如此。 311 | 312 | ```bash 313 | xiaodong@codeland:~$ git init 314 | ``` 315 | 316 | 与 bash 比较而言,zsh 对于命令选项的补全提供更好的用户体验。在下面的例子中,你将看到,zsh 不仅列出了可供补全的选项列表,更有对该选项用途的解释。如图 \@ref(fig:zsh-cmd-opt-comp) 所示。此外,正如前面提到的,你还可以选择这些列表项目。 317 | 318 | ```{r zsh-cmd-opt-comp, fig.cap='zsh 中的命令选项自动补全'} 319 | knitr::include_graphics('images/zsh-cmd-opt-comp.png') 320 | ``` 321 | 322 | 对于子命令的补全,zsh 提供与命令选项补全相同的效果。 323 | 324 | 此外,子命令以及选项补全也可以合用。在下例中,我先补全了子命令 `git status`,然后又补全了选项 `--verbose`。 325 | 326 | ```bash 327 | xiaodong@codeland:~$ git sta 328 | stash -- stash away changes to dirty working directory 329 | status -- show working-tree status 330 | xiaodong@codeland:~$ git status --v 331 | xiaodong@codeland:~$ git status --verbose 332 | ``` 333 | 334 | ### Zsh 自动建议插件 335 | 336 | 对于使用 zsh 的朋友,我在此推荐一个好用的命令自动建议插件。这个插件叫做 [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)。针对命令进行自动建议这项功能源自于 `fish` shell,现在,zsh 从其借鉴过来,使得我们这些 zsh 的忠实拥趸也能使用这项好功能。 337 | 338 | zsh-autosuggestions 的安装很简单,只需从 GitHub 将其克隆到本机,然后在 `.zshrc` 中引用 `zsh-autosuggestions.zsh` 并重新打开终端即可。 339 | 340 | ```bash 341 | xiaodong@codeland:~$ git clone \ 342 | https://github.com/zsh-users/zsh-autosuggestions.git \ 343 | ~/.zsh-autosuggestions 344 | xiaodong@codeland:~$ echo source \ 345 | ~/.zsh-autosuggestions/zsh-autosuggestions.zsh \ 346 | >> ~/.zshrc 347 | xiaodong@codeland:~$ source ~/.zshrc 348 | ``` 349 | 350 | 下面让我们来看看 `zsh-autosuggestions` 的用法,首先我在没有开启 `zsh-autosuggestions` 插件的情况下输入 `ls -l`、`cd hello_world` 等命令,除了能够使用命令补全之外,这儿没有命令的自动建议。当 `zsh-autosuggestions` 插件开启后,我一旦输入 `ls`,其后便会出现灰色的自动建议 `-la`。这是因为 zsh 知道我先前曾输入过 `ls -la` 这条命令,所以它给出了自动建议。如图 \@ref(fig:zsh-autosug) 所示。 351 | 352 | ```{r zsh-autosug, fig.cap='zsh 中的命令自动建议'} 353 | knitr::include_graphics('images/zsh-autosug.png') 354 | ``` 355 | 356 | 这时,我们有两种选择:一是按 **→ (右方向箭)** 接受建议,二是继续输入新的内容,这样也就放弃建议了。对于输入 `cd h` 后,zsh 同样给出了自动建议 `ello_world`。 357 | 358 | ## 用户名、主机名及变量名补全 359 | 360 | 除了常见的文件名、命令名补全外,Shell 自动补全还支持其它补全类型。这充分展现了 Shell 自动补全多才多艺的一面。下面我们就来看一看 Shell 如何自动补全用户名。 361 | 362 | 当我输入 363 | 364 | ```bash 365 | xiaodong@codeland:~$ ls ~ 366 | ``` 367 | 368 | 之后按 **Tab**,此时 bash 为我呈现了系统中存在的用户名列表。如图 \@ref(fig:user-comp) 所示。 369 | 370 | ```{r user-comp, fig.cap='bash 中的用户名自动补全备选列表'} 371 | knitr::include_graphics('images/user-comp.png') 372 | ``` 373 | 374 | 我继续输入 `x` 并再次按 **Tab**,于是 bash 补全了 `xiaodong` 这个用户名。 375 | 376 | ```bash 377 | xiaodong@codeland:~$ ls ~x 378 | xiaodong@codeland:~$ ls ~xiaodong/ 379 | ``` 380 | 381 | 在 zsh 中,我们可以看到,与 bash 相比,提供的补全用户名列表表现形式略有差异。bash 中包含 `~` 前缀,并在结尾带有 `/` (斜杠)。zsh 中则仅有用户名本身。如图 \@ref(fig:zsh-user-comp) 所示。 382 | 383 | ```{r zsh-user-comp, fig.cap='zsh 中的用户名自动补全备选列表'} 384 | knitr::include_graphics('images/zsh-user-comp.png') 385 | ``` 386 | 387 | 如果你经常使用 `ssh` 登录远程机器的话,那么主机名自动补全将助你一臂之力。同样,我们先来看一个例子。我在输入 388 | 389 | ```bash 390 | xiaodong@codeland:~$ ssh xiaodong@l 391 | ``` 392 | 393 | 后按 **Tab**,这时 bash 展示了可以自动补全的主机名列表。 394 | 395 | ```bash 396 | xiaodong@codeland:~$ ssh xiaodong@l 397 | xiaodong@lab.github.com xiaodong@localhost 398 | xiaodong@linuxtoy.org xiaodong@localhost.localdomain 399 | xiaodong@codeland:~$ ssh xiaodong@l 400 | ``` 401 | 402 | 我接着输入 `i` 并按 **Tab**,这次 bash 就自动补全了完整的主机名 `linuxtoy.org`。你也可以直接在 `@` 后按 **Tab**,这样的话就会显示全部主机名了。 403 | 404 | ```bash 405 | xiaodong@codeland:~$ ssh xiaodong@li 406 | xiaodong@codeland:~$ ssh xiaodong@linuxtoy.org 407 | ``` 408 | 409 | 不仅是主机名,而且 IP 地址也同样支持自动补全。另一种情况是,直接在输入 `ssh l` 后按 **Tab**,bash 也能对主机名进行自动补全。 410 | 411 | ```bash 412 | xiaodong@codeland:~$ ssh l 413 | lab.github.com linuxtoy.org localhost 414 | ``` 415 | 416 | 看到这里,你或许会想,bash 从哪里找到这些可以用来自动补全的主机名呢?一个是 `/etc/hosts` 文件的内容,另一个是 `ssh` 的配置文件,比如 `~/.ssh/config`。如图 \@ref(fig:completion-hostname) 所示。所以,如果你打算让 bash 为你自动补全常用的主机名的话,那么不妨考虑将其添加到这两个文件中。此外,还包括 `~/.ssh/known_hosts` 文件。凡是通过 `ssh` 登录过的主机,便会包含其中。 417 | 418 | ```{r completion-hostname, fig.cap='自动补全的主机名来源'} 419 | knitr::include_graphics('images/completion-hostname.png') 420 | ``` 421 | 422 | zsh 对主机名的自动补全与 bash 类似,此不赘述。 423 | 424 | 最后,让我们来看看对变量名的自动补全情况。当我输入 425 | 426 | ```bash 427 | xiaodong@codeland:~$ echo $ 428 | ``` 429 | 430 | 后按 **Tab** 并根据提示按 **y**,这时 bash 显示了全部可供补全的变量名。如图 \@ref(fig:var-comp) 所示。 431 | 432 | ```{r var-comp, fig.cap='bash 中的变量名自动补全备选列表'} 433 | knitr::include_graphics('images/var-comp.png') 434 | ``` 435 | 436 | 然后,我继续输入并搭配 **Tab** 按键,从而补全了变量 `BASH_VERSION`。 437 | 438 | ```bash 439 | xiaodong@codeland:~$ echo $BASH_VERSION 440 | ``` 441 | 442 | zsh 对变量的自动补全与 bash 相似,不过,在我的系统上比 bash 提供的补全列表更多一些。如图 \@ref(fig:zsh-var-comp) 所示。 443 | 444 | ```{r zsh-var-comp, fig.cap='zsh 中的变量名自动补全备选列表'} 445 | knitr::include_graphics('images/zsh-var-comp.png') 446 | ``` 447 | 448 | 综合来看,这几种补全类型跟前面我们所讲的文件名、命令名自动补全还是有一点差异,那就是它们带着一个特殊的前缀字符,参考表 \@ref(tab:auto-comp-table)。 449 | 450 | Table: (\#tab:auto-comp-table) 用户名、主机名及变量名自动补全前缀字符 451 | 452 | | 前缀字符 | 自动补全类型 | 453 | | -------- | ------------ | 454 | | ~ | 用户名 | 455 | | @ | 主机名 | 456 | | $ | 变量名 | 457 | 458 | ## 可编程补全 459 | 460 | 在熟悉了命令行自动补全的用法之后,如果你是一位开发人员的话,那么或许会问到这样的问题:“如何为自己所写的程序或脚本添加命令补全呢?”利用 bash 和 zsh 提供的可编程补全特性,我们可以方便地对命令补全加以定制。下面我们就从示例出发来一探究竟。 461 | 462 | ### bash 示例 463 | 464 | 假设我编写的程序名叫 `mycmd`,它具有 `--help` 和 `--version` 两个命令选项。让我们先来看看它的命令补全效果。当我输入 465 | 466 | ```bash 467 | xiaodong@codeland:~$ mycmd - 468 | ``` 469 | 470 | 并按 **Tab** 后,这时 bash 为我呈现了该命令的全部选项列表,同时补全成了 `mycmd --`。 471 | 472 | ```bash 473 | xiaodong@codeland:~$ mycmd - 474 | --help --version 475 | xiaodong@codeland:~$ mycmd -- 476 | ``` 477 | 478 | 我接着输入 `h`,再按 **Tab**,bash 这次就自动补全了命令选项 `--help`。啊哈,这正是我想要的命令补全。那么,如何实现可编程补全呢? 479 | 480 | ```bash 481 | xiaodong@codeland:~$ mycmd --h 482 | xiaodong@codeland:~$ mycmd --help 483 | ``` 484 | 485 | 首先,我们需要在 `/etc/bash_completion.d` 目录下创建 `mycmd` 文件(亦即 `/etc/bash_completion.d/mycmd`)。这样,bash 就会自动加载我们在 `mycmd` 中编写的补全代码。 486 | 487 | 其次,我们在 `mycmd` 中编写如下用于处理命令自动补全的代码。如图 \@ref(fig:completion-prog-bash) 所示。 488 | 489 | ```bash 490 | # 491 | # Completion for mycmd 492 | # 493 | _mycmd() { 494 | local cur opts 495 | 496 | cur="${COMP_WORDS[COMP_CWORD]}" 497 | opts="--help --version" 498 | 499 | if [[ ${cur} == -* ]]; then 500 | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) 501 | return 0 502 | fi 503 | } 504 | 505 | complete -F _mycmd mycmd 506 | ``` 507 | 508 | ```{r completion-prog-bash, fig.cap='bash 可编程补全示例'} 509 | knitr::include_graphics('images/completion-prog-bash.png') 510 | ``` 511 | 512 | 这是一个典型的 bash 脚本。开头的 `#` 为注释,用于说明补全的用途。 513 | 514 | 接着我们定义了一个名为 `_mycmd` 的函数,该函数包含用来处理 `mycmd` 命令的选项的逻辑。 515 | 516 | `local` 声明了两个变量:`cur` 和 `opts`。其中,`cur` 存储当前在命令行正输入的字,它通过 bash 内置的变量 `COMP_WORDS` 和 `COMP_CWORD` 获取。 517 | 518 | - `COMP_WORDS`:数组变量,包含当前命令行中单独的字。 519 | - `COMP_CWORD`:表示当前光标位置在 `${COMP_WORDS}` 中的索引。 520 | 521 | 而 `opts` 则用来保存 `mycmd` 命令所有的命令选项。 522 | 523 | 然后,我们判断 `$cur` 是否为 `-` 打头,若为真,那么就用 `compgen` 命令来生成可供补全的选项列表。`-W` 选项后跟我们需要的 `mycmd` 命令选项。 524 | 525 | 与此同时,我们将 `compgen` 产生的输出赋给又一个 bash 内置变量 `COMPREPLY`。这样,当需要补全时,bash 就会采用 `compgen` 生成的补全列表了。 526 | 527 | 最后,我们用 `complete` 将补全函数 `_mycmd`(`-F` 选项)与程序 `mycmd` 绑定在一起即可。 528 | 529 | ### zsh 示例 530 | 531 | 现在,让我们来看看在 zsh 中又怎么实现可编程补全吧。 532 | 533 | 假如我们把 `mycmd` 的补全代码保存到 `$HOME/.zsh/_mycmd` 中的话,那么需要在 `$HOME/.zshrc` 里设置 `$fpath`,以便 zsh 能够加载我们的补全代码。 534 | 535 | ```bash 536 | fpath=($HOME/.zsh $fpath) 537 | ``` 538 | 539 | 下面就是我们针对 zsh 而改写的 `mycmd` 自动补全代码。如图 \@ref(fig:completion-prog-zsh) 所示。 540 | 541 | ```bash 542 | #compdef mycmd 543 | # 544 | # Completion for mycmd 545 | # 546 | _mycmd() { 547 | local cur opts 548 | 549 | cur="${words[CURRENT]}" 550 | opts=(--help --version) 551 | 552 | if [[ ${cur} == -* ]]; then 553 | compadd -- ${opts} 554 | return 0 555 | fi 556 | } 557 | 558 | _mycmd "$@" 559 | ``` 560 | 561 | ```{r completion-prog-zsh, fig.cap='zsh 可编程补全示例'} 562 | knitr::include_graphics('images/completion-prog-zsh.png') 563 | ``` 564 | 565 | 第一行的注释并非普通注释(`#compdef mycmd`),它允许 zsh 为我们自动载入补全代码。 566 | 567 | 接下来定义的函数与变量跟 bash 示例相似,其中已经替换成 zsh 里等价的内容。 568 | 569 | - `words` 相当于 bash 中的 `COMP_WORDS` 570 | - `CURRENT` 与 bash 中的 `COMP_CWORD` 类似 571 | - `COMPREPLY` 则和 `compadd` 这个内置的 zsh 命令相同 572 | 573 | 要试验 `mycmd` 在 zsh 中的补全效果,只需先执行一下 `source ~/.zshrc`。从下面的例子中,你可以看到 `mycmd` 的命令补全跟 bash 中几乎一样,当然也带着 zsh 原本的补全功能。 574 | 575 | ```bash 576 | xiaodong@codeland:~$ mycmd -- 577 | --help --version 578 | ``` 579 | 580 | 值得一提的是,zsh 本身还提供了一些辅助函数以用于补全,比如 `_arguments`、`_describe`、`_message` 等等,各位读者诸君不妨参考 zsh 的官方文档详加了解,以便用到自己的补全代码中。 581 | -------------------------------------------------------------------------------- /03-history.Rmd: -------------------------------------------------------------------------------- 1 | # 重温历史 2 | 3 | 在编程领域有一个十分重要的原则,那就是如何想办法来重复利用代码。比如,通过把具有相同逻辑的代码抽象成函数,从而能够加以反复调用。与之类似,在使用命令行时,我们也可以贯彻这个原则——重复利用已经执行过的命令。如果想要达到这样的效果,那么就该轮到 Shell 的历史功能出场了。 4 | 5 | 我们将先从设置历史变量谈起,接着讨论如何查看、搜索、以及前后移动历史命令,然后来看看怎样快速修改并执行历史命令,最后介绍快速引用历史命令的参数。 6 | 7 | 为了更好的重温历史,让我们首先来了解历史命令的保存位置和记录大小吧。 8 | 9 | ## 设置历史变量 10 | 11 | 无论是 bash 还是 zsh,都能够将我们已经执行过的命令存储到一个文件中。这样,便于我们以后对其加以重复使用。要查看 bash 或 zsh 的历史文件位置,不妨执行: 12 | 13 | ```bash 14 | xiaodong@codeland:~$ echo $HISTFILE 15 | ``` 16 | 17 | 在 bash 中,我们可以看到,这个文件默认是存储到 `~/.bash_history` 的。但是,因为 zsh 默认并没有设置该变量,所以内容为空。 18 | 19 | 通过向 `$HISTFILE` 变量赋予新值,从而能够更改历史文件的保存位置。下面,我们将 zsh 的历史文件设置为 `~/.zsh_history`。先使用文本编辑器(比如 `nvim`)打开 `~/.zshrc`,然后添加下行内容: 20 | 21 | ```bash 22 | HISTFILE=~/.zsh_history 23 | ``` 24 | 25 | 对 bash 而言,另外两个重要的历史变量是 `$HISTFILESIZE` 和 `$HISTSIZE`。其中,前者为 `$HISTFILE` 文件所能保存的最大行数,而后者则为 Shell 中记忆的最大历史命令数。这两个变量默认的设置都是 500,换句话说 `~/.bash_history` 文件最多保留 500 行,且最多 500 个命令。为了最大限度的利用历史文件的价值,我们不妨考虑把这两个变量的值设得更大一些,比如 5000: 26 | 27 | ```bash 28 | HISTFILESIZE=5000 29 | HISTSIZE=5000 30 | ``` 31 | 32 | 将以上两行内容追加到 `~/.bashrc` 中以便永久保存设置。通常将这两个变量设置的值保持一致,否则在 `$HISTFILE` 中保存的内容可能会被截断。比如,在 `$HISTSIZE` 设为 1000 的情况下,而 `$HISTFILESIZE` 却为 500。因为历史命令数大于文件的行数,所以有部分历史命令不能保存到历史文件中。 33 | 34 | `$HISTSIZE` 变量在 zsh 中同样有效,但与 `$HISTFILESIZE` 变量等价的却变成了 `$SAVEHIST`。类似的,我们将 `~/.zsh_history` 保留的最大行数和命令数也设为 5000: 35 | 36 | ```bash 37 | SAVEHIST=5000 38 | HISTSIZE=5000 39 | ``` 40 | 41 | 把上面两行内容添加到 `~/.zshrc` 中以便永久保存设置。 42 | 43 | 既然存储的这些历史命令如此重要,那么就很有必要维护一个整洁、有价值的命令清单了。比如,剔除掉那些重复的命令、开头包含空格的命令、以及常用的简单命令等等。要实现这个目的,在 bash 中我们可以使用 `$HISTCONTROL` 变量。 44 | 45 | `$HISTCONTROL` 采用冒号分隔的列表来决定是否将命令保存到历史文件中。例如,`erasedups` 表示去掉重复的命令,而 `ignorespace` 则意为除去开头具有空格的命令。 46 | 47 | ```bash 48 | HISTCONTROL='erasedups:ignorespace' 49 | ``` 50 | 51 | 在 zsh 中没有与 bash 对应的内置变量 `$HISTCONTROL`,不过可以通过设置选项来达到同样的效果: 52 | 53 | ```bash 54 | setopt HIST_IGNORE_ALL_DUPS # 去掉重复的命令 55 | setopt HIST_IGNORE_SPACE # 去掉开头具有空格的命令 56 | ``` 57 | 58 | ## 查看历史命令 59 | 60 | Shell 本身提供了 `history` 这个内置命令来让我们随时查看所记录的历史命令。当我们执行 `history` 后,Shell 记录的所有历史命令便被回显出来。如果历史命令太多,不妨将其管道给页面查看程序 `less`,这样可以分屏查看: 61 | 62 | ```bash 63 | xiaodong@codeland:~$ history | less 64 | 1 echo $HISTSIZE 65 | 2 sudo -i 66 | 3 cat .bashrc 67 | 4 cat .bash_profile 68 | 5* cat .bash_history 69 | ``` 70 | 71 | 每行命令前面的数字是该行命令的编号。数字后面带 `*` 号的行则说明已经被修改过。 72 | 73 | `history` 比较有用的一个选项是,它后面可以跟一个数字(比如 5)。这样,在 bash 中就可以看到倒数的 5 个历史命令。 74 | 75 | ```bash 76 | xiaodong@codeland:~$ history 5 77 | ``` 78 | 79 | 值得注意的是,zsh 中需要在 5 前面加个 `-` 号: 80 | 81 | ```bash 82 | xiaodong@codeland:~$ history -5 83 | ``` 84 | 85 | 另外,在 zsh 中,我们也可以给 `history` 两个负数,以便查看中间的一段历史命令: 86 | 87 | ```bash 88 | xiaodong@codeland:~$ history -10 -5 89 | ``` 90 | 91 | 这表示从倒数第 10 个到倒数第 5 个之间的历史命令。 92 | 93 | 对 zsh 来说,它还能向我们提供更多的历史命令细节,包括命令执行的日期和时间,以及每个命令持续运行的时间: 94 | 95 | ```bash 96 | xiaodong@codeland:~$ history -i -D 97 | ``` 98 | 99 | 这里的 `-i` 选项向我们展示了命令执行的日期及时间,而 `-D` 选项则说明了命令运行了好久。 100 | 101 | 除了 `history` 之外,另一个用来查看历史命令列表的是 `fc`。我们利用 `fc` 的 `-l` 选项可以将历史命令列出来。例如: 102 | 103 | ```bash 104 | xiaodong@codeland:~$ fc -l # 列出最后 16 条命令 105 | xiaodong@codeland:~$ fc -l -5 # 列出倒数 5 条命令 106 | xiaodong@codeland:~$ fc -l 20 30 # 列出编号 20 到 30 的命令 107 | xiaodong@codeland:~$ fc -l 100 # 列出编号为 100 后的所有命令 108 | xiaodong@codeland:~$ fc -l cat # 列出 cat 后的所有命令 109 | ``` 110 | 111 | 通过 `fc` 的 `-e` 选项,我们还能够编辑历史命令列表。比如: 112 | 113 | ```bash 114 | xiaodong@codeland:~$ fc -e vi 5 10 115 | ``` 116 | 117 | 这将打开 `vi` 来编辑 5 到 10 条历史命令。 118 | 119 | ## 搜索历史命令 120 | 121 | 在搜索历史命令时,大家平时用得比较多的是将 `history` 与 `grep` 联用,从而过滤出需要的命令: 122 | 123 | ```bash 124 | xiaodong@codeland:~$ history | grep 'xxx' 125 | ``` 126 | 127 | 我个人比较喜欢使用的方式是按 **Ctrl + r** 组合键,这样 Shell 会让我们逆向搜索历史命令,比用 `grep` 更加方便。 128 | 129 | 当我在 bash 中按 **Ctrl + r** 后,Shell 给我提示 reverse-i-search(在 zsh 中这个提示略有不同,为 bck-i-search),然后我可以在冒号后面键入要搜索的字符串,比如 `hi`。此时,Shell 从历史命令中找到了 `history`,按**回车键**可以立即执行该命令。如果想要对命令加以修改,则只需按**→️ (右方向键)**。如图 \@ref(fig:history-search) 所示。 130 | 131 | ```{r history-search, fig.cap='逆向搜索历史命令'} 132 | knitr::include_graphics('images/history-search.png') 133 | ``` 134 | 135 | 这是一个增量搜索引擎,我们每键入一个字符,Shell 便对历史命令列表进行匹配。若是匹配成功,则显出结果。要是匹配失败,我们还可以按**退格键**删除字符,然后重新输入来继续搜索。 136 | 137 | ## 前后移动历史命令 138 | 139 | 除 **Ctrl + r** 之外,我经常使用的另外两组快捷键是 **Ctrl + p** 和 **Ctrl + n**。这两组快捷键能够让我们在历史命令列表中前后移动。参考表 \@ref(tab:move-history)。 140 | 141 | Table: (\#tab:move-history) 前后移动历史命令 142 | 143 | | 按键 | 作用 | 144 | | -------- | -------------- | 145 | | Ctrl + p | 移到前一条命令 | 146 | | Ctrl + n | 移到后一条命令 | 147 | 148 | 如果我们多次按这两组快捷键,则可以连续前移或后移。这些快捷键 bash 和 zsh 都支持。 149 | 150 | ## 快速修改并执行上一条命令 151 | 152 | 平常在使用命令行时,我经常会遇到的情况是,要么不小心,要么手太快,总之命令没有输入正确就执行了。这时候,我可不想再次重新输入命令,只想对上一条命令稍微作一下修改。那么,Shell 有没有什么快速而简便的操作方法呢?回答是肯定的,且听我慢慢道来。 153 | 154 | ### 删掉多余内容 155 | 156 | 例如,我在使用 `grep` 过滤日志时,不幸多输入了一个 `o`(原本是 lolcat): 157 | 158 | ```bash 159 | xiaodong@codeland:~$ grep loolcat /var/log/pacman.log 160 | ``` 161 | 162 | 我们没有必要重新输入这条命令,只需执行 `^o` 即可将多余的 `o` 字符删除。 163 | 164 | ```bash 165 | xiaodong@codeland:~$ ^o 166 | xiaodong@codeland:~$ grep lolcat /var/log/pacman.log 167 | ``` 168 | 169 | Shell 在回显出正确的命令后立即执行了它。这里的 `^o` 将上一条命令中找到的第一个 `o` 字符删除,从而纠正了输错的命令。 170 | 171 | ### 替换内容 172 | 173 | 让我们来看另一个例子,我在查看 `file1` 这个文件的内容时错输成了 `flie1`: 174 | 175 | ```bash 176 | xiaodong@codeland:~$ cat flie1 177 | ``` 178 | 179 | 现在我们可以用 `^li^il` 来将输错的 `li` 替换为 `il`。同样,Shell 回显出正确的命令并予以执行。 180 | 181 | ```bash 182 | xiaodong@codeland:~$ ^li^il 183 | xiaodong@codeland:~$ cat file1 184 | ``` 185 | 186 | 即便在没有输错的情况下,`^old^new` 也是很实用的。假如我在查看 `file1` 后接着想查看 `file4`,那么只要执行 `^1^4`: 187 | 188 | ```bash 189 | xiaodong@codeland:~$ cat file1 190 | xiaodong@codeland:~$ ^1^4 191 | xiaodong@codeland:~$ cat file4 192 | ``` 193 | 194 | ### 全局替换 195 | 196 | 还有一种情况,有时候我们想不只替换一处,而是把上一条命令中的每处内容都替换掉。要实现这种效果,可以使用 `!:gs/old/new`,其中,`!` 表示引用上一条命令(在后续的章节中我们将详细讲解),`:` (冒号) 后边的 `gs` 意为全局(`g`)替换(`s`),`/old/new` 则与 `^old^new` 相似。 197 | 198 | ```bash 199 | xiaodong@codeland:~$ ansible nginx -a 'which nginx' 200 | ``` 201 | 202 | 这条命令让我通过 Ansible 了解 nginx 分组的所有机器是否都包含 nginx 程序。接下来,我想看看 haproxy 分组的情况,于是我执行: 203 | 204 | ```bash 205 | xiaodong@codeland:~$ !:gs/nginx/haproxy 206 | xiaodong@codeland:~$ ansible haproxy -a 'which haproxy' 207 | ``` 208 | 209 | 顺便提一句,在 zsh 中除了支持上述方式外,也可以使用: 210 | 211 | ```bash 212 | xiaodong@codeland:~$ ansible nginx -a 'which nginx' 213 | xiaodong@codeland:~$ ^nginx^haproxy^:G 214 | xiaodong@codeland:~$ ansible haproxy -a 'which haproxy' 215 | ``` 216 | 217 | ## 快速执行历史命令 218 | 219 | 既然我们把已经执行过的命令存储到 Shell 的历史文件中,那么自然想有一天能够再次用到它。正所谓“养兵千日,用兵一时”。下面,我们就来看一看如何快速的执行已有的历史命令。 220 | 221 | ### 重复执行上一条命令 222 | 223 | 一种常见的使用场景是,我在使用 `htop` 查看系统状态并退出后,过一会儿想再次查看它。此时,我们无需重新输入 `htop` 命令,只需按两下 `!!` 并敲**回车**即可。 224 | 225 | ```bash 226 | xiaodong@codeland:~$ htop 227 | xiaodong@codeland:~$ !! 228 | ``` 229 | 230 | `!!` 被称为 bang bang,是我最喜欢使用,同时也是使用频率极高的历史命令调用表示。`!!` 让我们以最快的方式重复执行上一条命令。 231 | 232 | `!!` 经常与 `sudo` 联用,用来解决缺少权限的问题。例如: 233 | 234 | ```bash 235 | xiaodong@codeland:~$ pacman -S figlet 236 | error: you cannot perform this operation unless you are root. 237 | ``` 238 | 239 | 在此,我用 `pacman` 来安装 figlet,但由于是普通账户,所以没有权限操作。要解决这个问题,我们只要输入: 240 | 241 | ```bash 242 | xiaodong@codeland:~$ sudo !! 243 | xiaodong@codeland:~$ sudo pacman -S figlet 244 | ``` 245 | 246 | ### 执行以某些字符打头的命令 247 | 248 | 利用 `!foo` 这种表示法允许我们执行以 `foo` 这三个字符打头的命令。Shell 将以逆序的方式搜索历史命令列表,一旦与给定的开头字符匹配到,便予以执行该条命令。例如: 249 | 250 | ```bash 251 | xiaodong@codeland:~$ !he 252 | xiaodong@codeland:~$ help 253 | ``` 254 | 255 | 该表示从历史命令列表中找到 `help` 后执行。 256 | 257 | ```{block2, type='rmdtip'} 258 | 如果不能确定所找到命令的完整内容,那么可以在其后追加 `:p`。这样,Shell 将打印出该命令,并不会执行。 259 | 260 | xiaodong@codeland:~$ !he:p 261 | 262 | 这里的 `:p` 为修饰符,在后面的内容中我们会详细讲解。 263 | ``` 264 | 265 | ### 执行历史列表中第 n 个命令 266 | 267 | 在 `!` 后面除了可以跟一个字符串之外,也可以跟一个数字。这个数字代表历史命令列表中的编号。当我们用 `history` 查看历史命令列表时,命令左边显示的即是该行命令的编号。例如: 268 | 269 | ```bash 270 | xiaodong@codeland:~$ history 5 271 | ``` 272 | 273 | 这里显示 `htop` 的编号为 52,如图 \@ref(fig:history-five) 所示。 274 | 275 | ```{r history-five, fig.cap='history 5 执行结果'} 276 | knitr::include_graphics('images/history-five.png') 277 | ``` 278 | 279 | 所以我们可以用: 280 | 281 | ```bash 282 | xiaodong@codeland:~$ !52 283 | ``` 284 | 285 | 来再次执行 `htop`。 286 | 287 | ```{block2, type='rmdtip'} 288 | 请想想看,如果在 `!` 后面加一个负数(如 -2)会怎样呢? 289 | 290 | xiaodong@codeland:~$ !-2 291 | 292 | 这同样是一种有效的表示,不过它是以倒序的方式去执行命令。`-2` 意味着倒数第 2 条历史命令。 293 | ``` 294 | 295 | 利用 `!-2`,我经常使用的一个场景是,先用文本编辑器编辑源代码,接着再编译源代码。如果我需要再次编辑和编译,那么只要反复执行 `!-2` 即可。如此不断循环。 296 | 297 | ```bash 298 | xiaodong@codeland:~$ nvim first.c 299 | xiaodong@codeland:~$ gcc -o first first.c 300 | xiaodong@codeland:~$ !-2 # 再编辑 301 | xiaodong@codeland:~$ !-2 # 再编译 302 | ``` 303 | 304 | 顺便说一句,因为 `!-1` 是如此常见,所以 Shell 提供了简写形式 `!!`。 305 | 306 | ## 快速引用上一条命令的参数 307 | 308 | 很多时候,我们即将执行的命令与之前的命令具有相同的参数,比如同样的文件名、路径名等等。所以,我们在执行新的命令时无需重新输入这些同样的参数,只要直接从其引用过来即可。 309 | 310 | ### 引用最后一位参数 311 | 312 | 我最常用的是 `!$`,它允许我直接复用上一条命令的最后一位参数。当我用 `mkdir` 创建目录后,使用它来立即转到该目录: 313 | 314 | ```bash 315 | xiaodong@codeland:~$ mkdir videos 316 | xiaodong@codeland:~$ cd !$ 317 | ``` 318 | 319 | 这里,`cd` 命令后的 `!$` 等同于上一条命令中的 `videos`。 320 | 321 | ```{block2, type='rmdtip'} 322 | 除了利用 `!$`,你也可以按 **Alt + .** 这组快捷键来达到同样的目的。 323 | ``` 324 | 325 | ### 引用最开头的参数 326 | 327 | 与最后一位参数相反,`!^` 能够让我们引用上一条命令中最开头的参数。这里的 `^` 和 `$` 与正则表达式中的锚点类似。请看例子: 328 | 329 | ```bash 330 | xiaodong@codeland:~$ ls /usr/share/doc /usr/share/man 331 | xiaodong@codeland:~$ cd !^ 332 | ``` 333 | 334 | 在该例中,`!^` 相当于上一条命令中的路径 `/usr/share/doc`。 335 | 336 | ```{block2, type='rmdtip'} 337 | 组合键 **Ctrl + Alt + y** 可以实现同样的效果。 338 | ``` 339 | 340 | ### 引用所有参数 341 | 342 | 不光是开头或结尾的参数,有时候我们想要引用的是上一条命令的所有参数。此时,我们可以使用 `!*`,这里的 `*` 意为全部。比如: 343 | 344 | ```bash 345 | xiaodong@codeland:~$ ls src code 346 | xiaodong@codeland:~$ cp -r !* 347 | ``` 348 | 349 | `cp` 命令中的 `!*` 跟 `src code` 同样,它表示两个参数都要引用。 350 | 351 | ### 引用第 n 个参数 352 | 353 | 对于引用上一条命令中的参数,我们甚至可以要求 Shell 精确到具体的第几个。因为 Shell 按照空白来解析命令行,所以它给命令本身编号为 0,后续的选项和参数按 1、2、3 等依次编号。如图 \@ref(fig:history-word) 所示。这就好比程序中的数组一样。在下面的例子中,假如我们想要引用 `bar.txt`,除开 `touch`,按顺序它应该是第 2 个参数,因此可以像这样表示: 354 | 355 | ```{r history-word, fig.cap='命令及选项参数编号'} 356 | knitr::include_graphics('images/history-word.png') 357 | ``` 358 | 359 | ```bash 360 | xiaodong@codeland:~$ touch foo.txt bar.txt baz.txt 361 | xiaodong@codeland:~$ nvim !:2 362 | ``` 363 | 364 | `nvim` 命令中的 `!:2` 就相当于上一条命令中的 `bar.txt` 文本文件。 365 | 366 | ```{block2, type='rmdnote'} 367 | 这里的 `:`(冒号)很重要,若没有的话,就等于执行历史命令列表中的编号为 2 的命令了。 368 | ``` 369 | 370 | ### 引用从 m 到 n 的参数 371 | 372 | 还有一种情况可能会遇到,即同时引用上一条命令的好几个参数。此时,我们可以使用 `!:m-n` 表示法,`m` 为开始端,`n` 为结束端。我们继续以上例来说明: 373 | 374 | ```bash 375 | xiaodong@codeland:~$ touch foo.txt bar.txt baz.txt 376 | xiaodong@codeland:~$ nvim !:1-2 377 | ``` 378 | 379 | 这里的 `!:1-2` 让我们引用 `touch` 命令中的前两个参数。 380 | 381 | ### 引用从 n 到最后的参数 382 | 383 | 我们最后再介绍一种情况,通过 `!:n*` 这种表示让我们能够从上一条命令中引用从第 n 个到最后的参数。例如: 384 | 385 | ```bash 386 | xiaodong@codeland:~$ cat /etc/resolv.conf /etc/hosts /etc/hostname 387 | xiaodong@codeland:~$ nvim !:2* 388 | ``` 389 | 390 | 此处的 `!:2*` 允许我将 `hosts` 和 `hostname` 同时打开进行编辑。 391 | 392 | 值得提及的是,我们在此主要介绍的是如何引用上一条命令的参数,因为这是最为常见的使用场景。结合我们前面所讲的快速执行历史命令,我们也可以引用历史列表中其它命令的参数。比如: 393 | 394 | ```bash 395 | xiaodong@codeland:~$ !hi:2 396 | ``` 397 | 398 | 这将引用以 `hi` 打头的命令的第 2 个参数。 399 | 400 | ```bash 401 | xiaodong@codeland:~$ !10:2-3 402 | ``` 403 | 404 | 而这将引用第 10 条命令的 2、3 两个参数。 405 | 406 | ## 快速引用参数的部分内容 407 | 408 | 在上一节我们介绍了如何引用历史命令中的参数,除此之外,Shell 甚至比我们想要得到的做得更多。利用 Shell 提供的历史展开模式修饰符,使我们得以快速引用参数中的部分内容。 409 | 410 | ### 引用路径开头 411 | 412 | 请看例子: 413 | 414 | ```bash 415 | xiaodong@codeland:~$ ls /usr/share/fonts/truetype 416 | xiaodong@codeland:~$ cd !$:h 417 | ``` 418 | 419 | 在此,我想引用该路径的开头部分 `/usr/share/fonts`。为了达到这个目的,我在 `!$`(最后一位参数)的基础上添加了 `:h`。此处的 `:h` 为修饰符,意味着截取路径的开头部分,正如 `dirname` 的效果一样。 420 | 421 | ```{block2, type='rmdtip'} 422 | 助记技巧,将 `:h` 想成 `head` 的开头字符。 423 | ``` 424 | 425 | ### 引用路径结尾 426 | 427 | 有头就有尾。通过 `:t` 修饰符,我们可以引用路径的结尾部分,其效果跟 `basename` 类似。 428 | 429 | ```bash 430 | xiaodong@codeland:~$ wget http://nginx.org/download/nginx-1.15.8.tar.gz 431 | xiaodong@codeland:~$ tar zxvf !$:t 432 | ``` 433 | 434 | 经过 `!$:t` 引用后,我们的命令变成了: 435 | 436 | ```bash 437 | xiaodong@codeland:~$ tar zxvf nginx-1.15.8.tar.gz 438 | ``` 439 | 440 | ```{block2, type='rmdtip'} 441 | 助记技巧,将 `:t` 想成 `tail` 的开头字符。 442 | ``` 443 | 444 | ### 引用文件名 445 | 446 | 对于存在文件名的情形,我们还可以利用 `:r` 修饰符来只引用文件名部分(这将排除掉扩展名)。例如: 447 | 448 | ```bash 449 | xiaodong@codeland:~$ unzip hello.zip 450 | xiaodong@codeland:~$ cd !$:r 451 | ``` 452 | 453 | 这里的 `!$:r` 将 `hello.zip` 去掉扩展名,只保留 `hello` 部分。 454 | 455 | ### 将引用部分更改为大写 456 | 457 | 下面介绍的两个修饰符为 zsh 所特有,bash 目前尚不支持。通过 `:u` 修饰符,我们能够将所引用的部分更改为大写字母。 458 | 459 | ```bash 460 | xiaodong@codeland:~$ echo histchars 461 | xiaodong@codeland:~$ echo !$:u 462 | ``` 463 | 464 | 这里的 `!$:u` 将 `histchars` 全部更改为大写字母。 465 | 466 | ```{block2, type='rmdtip'} 467 | 助记技巧,将 `:u` 想成 `uppercase` 的开头字符。 468 | ``` 469 | 470 | ### 将引用部分更改为小写 471 | 472 | 与 `:u` 相对的是,`:l` 则使我们能够将所引用的参数全部更改为小写字母。 473 | 474 | ```bash 475 | xiaodong@codeland:~$ echo SAVEHIST 476 | xiaodong@codeland:~$ echo !$:l 477 | ``` 478 | 479 | 这里的 `!$:l` 将 `SAVEHIST` 全部更改为小写字母。 480 | 481 | ```{block2, type='rmdtip'} 482 | 助记技巧,将 `:l` 想成 `lowercase` 的开头字符。 483 | ``` 484 | 485 | 需要特别指出的是,Shell 还支持将多个修饰符进行联用,在它们之间只需使用 `:` (冒号) 分隔即可。例如: 486 | 487 | ```bash 488 | xiaodong@codeland:~$ ls /usr/share/fonts/truetype 489 | xiaodong@codeland:~$ echo !$:t:u 490 | xiaodong@codeland:~$ echo TRUETYPE 491 | ``` 492 | 493 | 这里,我们先用 `:t` 引用了路径的结尾部分,然后又使用 `:u` 将其更改为了大写字母。 494 | 495 | ## 历史命令展开模式总结 496 | 497 | 最后,我们来总结一下历史命令展开的模式。从前面我们所讲的内容来看,历史展开模式包括以下三个部分: 498 | 499 | 1. `!! !foo !n`:用来调用历史列表中的命令 500 | 2. `$ ^ * n m-n n*`:引用命令参数的各个部分 501 | 3. `h t r u l`:修饰符,对所引用的内容进行修改 502 | 503 | 如图 \@ref(fig:history-mode-i) 所示。 504 | 505 | ```{r history-mode-i, fig.cap='历史命令展开模式'} 506 | knitr::include_graphics('images/history-mode-i.png') 507 | ``` 508 | 509 | 模式的每个部分之间都用 `:`(冒号)进行分隔。让我们来看一个包含三个部分的例子: 510 | 511 | ```bash 512 | xiaodong@codeland:~$ !ec:$:t 513 | ``` 514 | 515 | 这个模式的含义是,引用 `ec` 打头命令的最后一位参数,并只保留路径尾部。 516 | -------------------------------------------------------------------------------- /04-editor.Rmd: -------------------------------------------------------------------------------- 1 | # 编辑大法 2 | 3 | 当我们在 Vim、Emacs、Sublime、VS Code 等熟悉的编辑器中编辑文本时,通常会有一种十分舒服的感觉。这是因为我们已经习惯了这些编辑器的操作方法。要是 Shell 命令行也能像文本编辑器一样编辑命令,那样的话我们的命令行编辑效率一定会大大提升。相信我,产生这种想法的人绝不止你我。无论是 bash,还是 zsh 的开发者,他们都同样想到了这个问题。正因为如此,所以我们今天才能沿用 Emacs 和 vi 这两个经典的文本编辑器的编辑习惯来编辑命令行。 4 | 5 | 在本章内容中,我们将先介绍在 Shell 中如何选择 Emacs 或 vi 编辑模式。接着进入 Emacs 编辑模式实战,包括按字、“词”、行来移动和删除的操作方法。最后,我们再讲解怎样在 vi 编辑模式中移动操作、重复执行命令、添加文本、删除文本、替换文本、以及搜索字符等内容。在学完这些内容后,对于编辑命令行而言,你将变得更加游刃有余。 6 | 7 | ## 设置编辑模式 8 | 9 | 既然 bash 与 zsh 都提供了 Emacs 和 vi 两种编辑模式,那么如何在这两种编辑模式之间进行选择呢?一般而言,在 Emacs 模式下,编辑操作显得更加自然,上手起来相对也更快一些。如果你从来没有使用过 vi 编辑器,那么选用 vi 编辑模式,一开始将会有找不到北的感觉。在 vi 模式下,按键要么能插入文本,要么能执行编辑指令,你需要在两种状态间不断来回切换。Emacs 模式跟 vi 模式相比更加简单,在使用上也会更容易一些。因此,推荐大家优先选择使用 Emacs 编辑模式。这也是 bash 和 zsh 都将 Emacs 作为默认的命令行编辑模式的原因。但是,假如你对 vi 的操作方法非常感兴趣的话,那么不妨选择使用 vi 编辑模式,去做那个敢于吃螃蟹的人。 10 | 11 | bash 和 zsh 两个都支持使用 `set` 指令来设置命令行编辑模式。例如,假如我们想要设为 vi 编辑模式,只需执行: 12 | 13 | ```bash 14 | xiaodong@codeland:~$ set -o vi 15 | ``` 16 | 17 | 要重新设为 Emacs 编辑模式,则执行: 18 | 19 | ```bash 20 | xiaodong@codeland:~$ set -o emacs 21 | ``` 22 | 23 | 在 zsh 中,我们也可以通过 `bindkey` 来设置 Emacs 或 vi 编辑模式。 24 | 25 | ```bash 26 | xiaodong@codeland:~$ bindkey -e 27 | ``` 28 | 29 | 该命令将 Emacs 作为编辑模式。如果打算设置为 vi 编辑模式,那么使用 `-v` 选项即可: 30 | 31 | ```bash 32 | xiaodong@codeland:~$ bindkey -v 33 | ``` 34 | 35 | 为了永久保存设置,我们需要将 bash 的设置选项添加到 `~/.bashrc` 配置文件。 36 | 37 | 而 zsh 的设置选项则需添加到 `~/.zshrc` 配置文件。 38 | 39 | ## Emacs 编辑模式实战 40 | 41 | ### 按字移动和删除 42 | 43 | 让我们先在命令行输入一些字符,然后来看看如何一个一个的移动字符: 44 | 45 | ```bash 46 | xiaodong@codeland:~$ echo 像骇客一样 47 | ``` 48 | 49 | 糟糕,我将“黑客”错输成了“骇客”,现在我要将光标向左移到“客”字,接着删除“骇”字,然后再重新输入正确的“黑”字。 50 | 51 | 对于命令行新手来说,可能会使用**左方向键(←)**向左移动字符。好,我们来试一下。按 3 下**左方向键(←)**,于是光标停留在“客”字上。此时,我们再按**退格键**则删除光标左边的“骇”字。我们接着重新输入正确的“黑”字。因为我们还要继续输入新的内容,所以按 3 下**右方向键(→)**往右移动光标,直到行尾。之后,我们能够再输入新的内容“使用命令行”。 52 | 53 | 在上面的操作中,我们通过分别按**左方向键(←)**和**右方向键(→)**来向左或往右移动一个字符。利用 Emacs 编辑模式,其实有比按**左右方向键**更好的操作方法。因为**左右方向键**通常远离键盘中心区域,所以与 Emacs 编辑模式提供的操作方法比起来在效率上会有所降低。 54 | 55 | 下面,我们就用 Emacs 编辑模式所提供的操作方法来一个个的移动字符。在 Emacs 编辑模式中,向左移动一个字符可以按 **Ctrl + b**。因此,我们按 3 次 **Ctrl + b**,此时光标位于“客”字上。同样的,我们通过按**退格键**来删除“骇”字。在输入“黑”字后,我们需要将光标移到最右边。现在,我们可以按 **Ctrl + f**,以便往右移动一个字符。需要往右移动几个字符就按几下 **Ctrl + f**。顺便说一句,在后面的内容中,我们会讲到如何一下子就移到最右边的操作方法。此刻,就先让我们熟悉按字移动的操作吧。 56 | 57 | 上述操作中我们使用**退格键**删除光标左边的字符。假如我们向左多移动了一个字符,那么此时光标将在“骇”字上。要想删除“骇”字,难道再往右移动吗?当然不必。Emacs 编辑模式为我们提供了 **Ctrl + d** 来删除光标下的字符。 58 | 59 | ```{block2, type='rmdnote'} 60 | 在空的命令行按 **Ctrl + d** 将退出 Shell。 61 | ``` 62 | 63 | 在此,我们再介绍一组快捷键:**Ctrl + t**。这组按键的作用是将光标左边的两个字符交换顺序。例如,当我在输入 64 | 65 | ```bash 66 | xiaodong@codeland:~$ sl 67 | ``` 68 | 69 | 后,通过按下 **Ctrl + t** 便能够将其变成 `ls`。 70 | 71 | ```bash 72 | xiaodong@codeland:~$ ls 73 | ``` 74 | 75 | 总结起来,按字移动和删除的操作方法参考表 \@ref(tab:emacs-char)。 76 | 77 | Table: (\#tab:emacs-char) Emacs 模式按字移动和删除的操作方法 78 | 79 | | 按键 | 作用 | 80 | | -------- | ---------------------------- | 81 | | Ctrl + b | 向左移动一个字符 | 82 | | Ctrl + f | 往右移动一个字符 | 83 | | 退格键 | 删除光标左边的字符 | 84 | | Ctrl + d | 删除光标下的字符 | 85 | | Ctrl + t | 将光标左边的两个字符交换顺序 | 86 | 87 | ### 按“词”移动和删除 88 | 89 | 按字符移动和删除毕竟有些琐碎,为了完成某个操作,有时候需要我们按下很多次快捷键。有鉴于此,Emacs 编辑模式针对“词”这个更大的范围提供了移动和删除的操作方法。值得注意的是,我们在此给“词”添加了引号。换句话说,这里的“词”是 Shell 所理解的“词”的含义,与现实生活中所谓的词含义并不相同。在 bash 中,“词”即字母和数字的组合,例如 file1 这种,其中并不包含特殊字符在内。而在 zsh 中,对“词”的界定跟 bash 又有所不同,除了字母和数字之外,还包括比如 `*`、`?`、`_`、`-` 等这样的符号。通过变量 `WORDCHARS`,我们可以看到这类字符到底有哪些。在我的系统中,执行 90 | 91 | ```bash 92 | xiaodong@codeland:~$ echo $WORDCHARS 93 | 94 | ``` 95 | 96 | 后,可以见到有如下字符: 97 | 98 | ```bash 99 | *?_-.[]~=/&;!#$%^(){}<> 100 | ``` 101 | 102 | 如果你想要让 zsh 判定“词”的行为跟 bash 一样的话,那么不妨将 `WORDCHARS` 变量的值设为空: 103 | 104 | ```bash 105 | xiaodong@codeland:~$ WORDCHARS= 106 | ``` 107 | 108 | 下面我们来看一个按“词”移动和删除的操作例子: 109 | 110 | ```bash 111 | xiaodong@codeland:~$ grep 'figlet' /var/log/pacman.log 112 | ``` 113 | 114 | 在我输入该命令行后,忽然想到应该查询 `lolcat` 这个包。为了将 `figlet` 改成 `lolcat`,我按 **Alt + b** 向左一个“词”一个“词”的移动。按 5 下后,此时光标停留在 `figlet` 的 `f` 上。接着,我按 **Alt + d** 删除光标右边的“词”(也就是 `figlet`)。然后输入新的内容 `lolcat`。 115 | 116 | 现在,如果不需要再输入内容的话,则可以直接按**回车键**执行命令。但因为我还想将标准输出的内容保存起来,所以继续按 **Alt + f** 来往右按“词”移动。当抵达最右边时,再输入新的内容 `> /tmp/output.log`。此刻,我们的命令行是: 117 | 118 | ```bash 119 | xiaodong@codeland:~$ grep 'logcat' /var/log/pacman.log \ 120 | > /tmp/output.log 121 | ``` 122 | 123 | 最后,我按 **Alt + 退格键**(或 **Ctrl + w**)删除光标左边的 `log`,并输入 `txt` 来完成该命令行的编辑。 124 | 125 | 正如前面所述,在 zsh 中按“词”移动的行为跟 bash 略有不同。这是因为它们对“词”界定的含义不一样的缘故。我们仍旧以上面的例子来加以说明。在光标都位于该命令行最右边的情况下,bash 中按 **Alt + b** 向左移动到的是 `txt` 中开头的 `t` 上;而在 zsh 中同样的操作却移动到了 `/tmp/output.txt` 开头的 `/`。在此,我们可以发现 zsh 将 `/` 和 `.` 都看作“词”的一部分。两个 Shell 比较起来,zsh 移动更快,按键也更少,但是粒度却要粗一些。因为 zsh 对“词”的界定范围比 bash 更宽,所以对“词”的删除内容也更多。bash 中按 **Alt + d** 删除的是 `txt`,而 zsh 中删除的却是 `/tmp/output.txt`。 126 | 127 | 需要特别指出的是,通过 **Alt + 退格键** 和 **Alt + d** 删除的内容,Shell 并没有丢弃,而是将其保存在 kill ring 中。你可以将 kill ring 看作一个特殊的剪贴板。当然,它里面的内容我们也是可以获取的。我们只需按 **Ctrl + y** 即可获取上次删除的内容。 128 | 129 | 此外,与 **Ctrl + t** 交换光标左边的两个字符相似,**Alt + t** 能够用来交换光标左边两个“词”的顺序。例如: 130 | 131 | ```bash 132 | xiaodong@codeland:~$ echo bar foo 133 | ``` 134 | 135 | 当我们按 **Alt + t** 后,该命令行将变成: 136 | 137 | ```bash 138 | xiaodong@codeland:~$ echo foo bar 139 | ``` 140 | 141 | 以下介绍的几组快捷键用于更改“词”的大小写。例如,我们在命令行输入 142 | 143 | ```bash 144 | xiaodong@codeland:~$ echo foo 145 | ``` 146 | 147 | 并按 **Alt + b** 将光标移到 `f` 上后,此时,按 **Esc + c** 把 `foo` 变成 `Foo`;若是按 **Esc + u**,则将变为 `FOO`;最后,按 **Esc + l**,又将成为 `foo`。 148 | 149 | 按“词”移动和删除的操作方法参考表 \@ref(tab:emacs-word)。 150 | 151 | Table: (\#tab:emacs-word) Emacs 模式按“词”移动和删除的操作方法 152 | 153 | | 按键 | 作用 | 154 | | ------------ | ------------------------------------ | 155 | | Alt + b | 向左移动一个“词” | 156 | | Alt + f | 往右移动一个“词” | 157 | | Alt + 退格键 | 删除光标左边的“词” | 158 | | Ctrl + w | 同上 | 159 | | Alt + d | 删除光标右边的“词” | 160 | | Ctrl + y | 获取上次删除的内容 | 161 | | Alt + t | 交换光标左边两个“词”的顺序 | 162 | | Esc + c | 将光标右边的“词”的开头字母变成大写 | 163 | | Esc + u | 将光标右边的“词”全部更改为大写字母 | 164 | | Esc + l | 将光标右边的“词”全部更改为小写字母 | 165 | 166 | ### 按行移动和删除 167 | 168 | 比“词”范围更大的移动及删除操作是行。相对“词”而言,我们只需要更少的按键即可移到更广的区域。由此,也将删除更多的命令行内容。让我们通过一个例子来说明: 169 | 170 | ```bash 171 | xiaodong@codeland:~$ grep 'set' *.txt 172 | ``` 173 | 174 | 在此,我想找出当前目录中包含 `set` 的所有文件。假如我想将这行命令改成: 175 | 176 | ```bash 177 | xiaodong@codeland:~$ egrep 'set' *.md 178 | ``` 179 | 180 | 首先,我按 **Ctrl + a** 将光标移到该命令行的行首(也就是最左边)。接着输入 `e` 将命令改成 `egrep`。然后,我再按 **Ctrl + e** 将光标移到此行的结尾处(也就是最右边)。在通过 **Alt + 退格键** 删除 `txt` 后,重新输入 `md` 即可完成对该命令行的修改。 181 | 182 | 下面,我们来看看针对行的删除操作。依然请出我们的老朋友 `foo`、`bar`、`baz`: 183 | 184 | ```bash 185 | xiaodong@codeland:~$ echo foo bar baz 186 | ``` 187 | 188 | 我们先按两下 **Alt + b** 将光标移到 `bar` 的 `b` 上。现在,如果我们打算删除 `bar` 和 `baz`,那么只要按 **Ctrl + k**;反之,但假设我们想要删除 `echo` 及 `foo` 的话,则需按 **Ctrl + u**。值得说明的是,在 zsh 中,**Ctrl + u** 的行为与 bash 中并不相同,它是删除整行的全部内容。 189 | 190 | 按行移动和删除的操作方法参考表 \@ref(tab:emacs-line)。 191 | 192 | Table: (\#tab:emacs-line) Emacs 模式按行移动和删除的操作方法 193 | 194 | | 按键 | 作用 | 195 | | -------- | ------------------------ | 196 | | Ctrl + a | 将光标移到行首(最左边) | 197 | | Ctrl + e | 将光标移到行尾(最右边) | 198 | | Ctrl + k | 从光标处往右删除至行尾 | 199 | | Ctrl + u | 从光标处向左删除至行首 | 200 | 201 | ### Emacs 编辑模式总结 202 | 203 | 从前面我们所讲的内容来看,Emacs 编辑模式的内容编辑范围主要包括下列 3 种: 204 | 205 | 1. 字 206 | 2. “词” 207 | 3. 行 208 | 209 | 针对每一种范围,又包含两种编辑操作,分别为移动和删除。如图 \@ref(fig:emacs-mode) 所示。 210 | 211 | ```{r emacs-mode, fig.cap='Emacs 编辑模式图解'} 212 | knitr::include_graphics('images/editor-emacs-view-body.png') 213 | ``` 214 | 215 | ## vi 编辑模式实战 216 | 217 | 与 Emacs 编辑模式相比,vi 编辑模式为我们提供了更多的控制命令。相应地,在 vi 编辑模式下,我们能够操作的粒度也将更细。如果你以前从未使用过 vi 编辑模式的话,那么不妨在亲自体验一番之后再来作决定是否要继续用它。跟 vi 文本编辑器一样,Shell 的 vi 编辑模式也包含两种模式:插入模式和命令模式。在插入模式下,我们输入的字符为字符本身,并没有什么特殊含义,如:h 就是 h;而在命令模式中,我们所输入的字符则为用来执行编辑过程的命令,如:**h** 用来向左移动一个字符。默认情况下,我们进入的是插入模式。若是要进入命令模式,则需要我们按 **Esc 键**。在使用 vi 编辑模式时,我们有时候可能会感到迷糊,此刻到底处于哪种模式呢?遇到这种情况,不妨先按 **Esc 键** 回到命令模式再作进一步的操作。或许这算是选择 vi 编辑模式的小小代价吧。 218 | 219 | ### 移动命令 220 | 221 | 在 vi 编辑模式中,我们可以使用的光标移动命令参考表 \@ref(tab:vi-move)。 222 | 223 | Table: (\#tab:vi-move) vi 模式移动命令 224 | 225 | | 命令 | 作用 | 226 | | ------- | ----------------------------------------------- | 227 | | h | 向左移动一个字符 | 228 | | l | 往右移动一个字符 | 229 | | b | 向左移动一个单词 | 230 | | w | 往右移动一个单词 | 231 | | e | 移到单词结尾 | 232 | | B、W、E | 与 `b`、`w`、`e` 类似,按不同的单词定义进行移动 | 233 | | 0 | 移到行首 | 234 | | ^ | 移到行首,但第一个字符为非空白字符 | 235 | | $ | 移到行尾 | 236 | 237 | 让我们输入一行命令来试试这些 vi 编辑模式中的移动命令: 238 | 239 | ```bash 240 | xiaodong@codeland:~$ echo hello, this is a command 241 | ``` 242 | 243 | 首先,按 **Esc 键**进入命令模式,此时光标位于 `command` 结尾的 `d` 上。 244 | 245 | 其次,通过 **h** 和 **l** 按一个个字符左右移动很直白,无需我们多言。值得讨论的是 **b**、**w**、**e** 跟它们对应的大写形式的区别:简而言之,**b**、**w**、**e** 将停留在空白或标点符号处(如该命令行中的 `,`(逗号)),而 **B**、**W**、**E** 则仅仅停留在空白处。例如,我们按 **b** 会经过 `,`,而按 **B** 将跳过 `,`。假如你想要移动更快的话,那么可以用大写字母的命令。而小写字母命令则对于更细粒度的移动有用,比如路径名这种情形。 246 | 247 | 最后,**0** 和 **^** 都是移到命令行的开头,不过其差异是 **0** 的开头可以为空白,而 **^** 的开头则不允许。例如: 248 | 249 | ```bash 250 | xiaodong@codeland:~$ cd /var/log/nginx 251 | ``` 252 | 253 | 按 **0** 移到开头的空格,按 **^** 移到 `cd` 的 `c` 上,按 **$** 移到行尾的 `x`。 254 | 255 | ### 重复命令 256 | 257 | 在 vi 命令模式下,每个移动命令之前可以跟一个数字,用来将该命令重复执行多次。例如,**3b** 表示向左移动 3 个单词,**5l** 则表示往右移动 5 个字符。值得注意的是,因为 **0** 本身也是一个命令,所以将它放在命令前面是无效的重复计数。 258 | 259 | ### 添加文本 260 | 261 | 我们已经知道通过按 **Esc 键**可以进入 vi 命令模式,但是,在命令模式下又如何回到插入模式呢?你只需参考表 \@ref(tab:vi-add) 中的命令来执行即可。 262 | 263 | Table: (\#tab:vi-add) vi 模式添加文本的命令 264 | 265 | | 命令 | 作用 | 266 | | ---- | -------------------------- | 267 | | i | 在光标左边插入新的文本内容 | 268 | | a | 在光标右边追加新的文本内容 | 269 | | I | 在行开头插入新的文本内容 | 270 | | A | 在行结尾追加新的文本内容 | 271 | 272 | 请看例子: 273 | 274 | ```bash 275 | xiaodong@codeland:~$ hello vi 276 | ``` 277 | 278 | 假如我打算将该命令改为 279 | 280 | ```bash 281 | xiaodong@codeland:~$ echo hello, vi editing mode 282 | ``` 283 | 284 | 那么可按以下方式执行编辑操作: 285 | 286 | 按 **2h** 将光标移到空格处,接着按 **i** 进入插入模式,然后输入新的 `,`。按 **Esc 键**回到命令模式后,继续按 **I** 以便在命令行开头插入 `echo`。再次按 **Esc 键**进入命令模式,最后按 **A** 在命令行的尾部追加 `editing mode`。 287 | 288 | ### 删除文本 289 | 290 | 利用 vi 模式提供的删除命令,我们不仅可以删除字符,而且也能删除单词,甚至整个命令行。这些删除命令参考表 \@ref(tab:vi-del)。 291 | 292 | Table: (\#tab:vi-del) vi 模式删除文本的命令 293 | 294 | | 命令 | 作用 | 295 | | ---- | -------------------------------------------------- | 296 | | x | 删除光标下的字符 | 297 | | X | 删除光标左边的字符 | 298 | | dm | **m** 为某个移动指令,如 **db** 删除光标左边的单词 | 299 | | D | 从光标处删除到行尾 | 300 | | dd | 删除整行内容 | 301 | 302 | 跟移动命令一样,在上述删除命令之前也可以带一个数字,以便多次执行该命令。例如,**5x** 将删除 5 个字符,而 **3dw** 将删除 3 个单词,这里 3 的顺序并不重要,**d3w** 仍然同样有效。 303 | 304 | 通过删除命令删除的内容,Shell 并没有丢弃,而是将其保留在了删除缓冲器中。稍后,我们可以执行 **u** 命令来恢复这些删除的内容。如果想要恢复更早时间删除的内容,则只需按 **u 键**多次即可。 305 | 306 | 另外一种更有用的方式是复制和粘贴。这样,我们能够在保留原有内容的同时,再储存一份拷贝,以便后续使用。vi 模式中复制及粘贴的命令参考表 \@ref(tab:vi-copy)。 307 | 308 | Table: (\#tab:vi-copy) vi 模式复制及粘贴命令 309 | 310 | | 命令 | 作用 | 311 | | ---- | ----------------------------------------------------- | 312 | | ym | **m** 为某个移动命令,如**yw** 用来复制光标右边的单词 | 313 | | p | 在光标右边粘贴文本 | 314 | | P | **p** 的大写形式,在光标左边粘贴文本 | 315 | 316 | 在下面的例子中,我们将看到上述命令的用法: 317 | 318 | ```bash 319 | xiaodong@codeland:~$ echo command-line interface 320 | ``` 321 | 322 | 先按 **Esc 键**进入命令模式,此时光标位于结尾的 e 上。按 **x** 将删除 e,按 **X** 将删除 a,按 **db** 将删除 interface 剩下的部分(只剩下字符 c),按 **dd** 则把整行内容都删掉。按 **u** 又还原刚删除的内容。 323 | 324 | ### 替换文本 325 | 326 | 当我们需要替换命令行中的内容时,除了在删除该内容后再进入插入模式重新输入外,也可以使用 vi 编辑模式所提供的文本替换命令。这些命令组合了删除与插入操作,用起来将更加直接。vi 编辑模式提供的文本替换命令参考表 \@ref(tab:vi-replace)。 327 | 328 | Table: (\#tab:vi-replace) vi 模式替换文本的命令 329 | 330 | | 命令 | 作用 | 331 | | ---- | ---------------------------------- | 332 | | cm | **m** 为某个移动命令,如 `cw` | 333 | | C | 从光标处删除到行尾,并进入插入模式 | 334 | | cc | 删除整行,并进入插入模式 | 335 | | r | 替换光标下的字符 | 336 | | R | 进入替换文本模式 | 337 | | s | 利用输入的字符来替换光标下的字符 | 338 | 339 | 要想搞明白这些替换命令如何工作,不妨来试试以下编辑练习: 340 | 341 | ```bash 342 | xiaodong@codeland:~$ echo talk is cheap. show me the kode. 343 | ``` 344 | 345 | 同样的,我们按 **Esc 键**先进入命令模式,按 **cb** 将 kode 删除后进入了插入模式,我们输入新的内容 code。再次按 **Esc**,接着按 **4b** 左移到 show,按 **r** 将 s 替换成 S。 346 | 347 | ### 搜索字符 348 | 349 | vi 编辑模式还提供了一组命令用于搜索命令行中的字符。利用这些命令,我们可以移动光标到特定的字符上。此外,将其跟 **d** 和 **c** 命令组合使用,还能够删除或更改从光标处到该字符的这一段文本。这些用于搜索字符的命令参考表 \@ref(tab:vi-search)。 350 | 351 | Table: (\#tab:vi-search) vi 模式搜索字符的命令 352 | 353 | | 命令 | 作用 | 354 | | ---- | ------------------------------------------------ | 355 | | fc | 移动光标到 **c** 的下一处 | 356 | | Fc | 与 **f** 相反方向搜索,移动光标到 **c** 的上一处 | 357 | | tc | 移动光标到 **c** 左边的字符 | 358 | | Tc | 移动光标到 **c** 右边的字符 | 359 | | ; | 重复上次的 **f** 或 **F** 命令 | 360 | | , | 以相反的方向重复上次的 **f** 或 **F** 命令 | 361 | 362 | 在下面的练习中,我们可以尝试上述字符搜索命令的用法: 363 | 364 | ```bash 365 | xiaodong@codeland:~$ echo A program which handles the interface 366 | ``` 367 | 368 | 在按 **Esc 键**进入命令模式后,按 **fp** 光标移到了 p 上,按 **th** 移到了 h 左边的 w。按 **Fm** 光标左移到 m 上。我们还可以试试其它的命令,以便熟悉这些命令的用途。 369 | 370 | ### vi 编辑模式总结 371 | 372 | 从前面我们所讲的内容来看,vi 编辑模式比 Emacs 编辑模式提供了更多的编辑命令。乍一看,似乎很复杂。我们除了勤加练习以期熟悉这些编辑命令之外,还可以总结出以下规律,从而帮助我们更好的加深理解。如图 \@ref(fig:vi-mode-i) 所示。 373 | 374 | + 跟 Emacs 编辑模式一样,我们同样可以按照字、“词”、行这 3 个维度来梳理操作命令 375 | + 删除命令 **d** 和更改命令 **c** 能够与移动命令组合使用 376 | + 移动命令、删除命令、更改命令之前可以加数字用来多次执行 377 | 378 | ```{r vi-mode-i, fig.cap='vi 编辑模式图解'} 379 | knitr::include_graphics('images/editor-vi-view-body.png') 380 | ``` 381 | -------------------------------------------------------------------------------- /05-tricks.Rmd: -------------------------------------------------------------------------------- 1 | # 必备锦囊 2 | 3 | 在本章内容中,我们将向大家介绍几个实用的命令行使用妙招,包括如何快速导航文件系统、使用别名来省时省力、通过 `{}` (花括号) 构造命令参数、使用命令替换和变量减少操作步骤、以及重复执行命令等内容。学完本章内容后,我们希望大家在使用命令行时都能够做到常备锦囊,心中不慌。 4 | 5 | ## 快速导航 6 | 7 | 在命令行下,你如何穿越文件系统的“丛林”而不致迷路?你又如何快速定位所需的文件和目录?如果你仅仅了解导航的基本用法,那么恐怕是不够的。在本节,我将教你几个必备的技能,使你能够轻车熟路的驾驭命令行导航。 8 | 9 | ### 回到用户主目录 10 | 11 | 也许你已经知道 `~` (波浪线) 这个特殊字符代表用户的主目录,若是想要回到自己的主目录,那么我们可以执行: 12 | 13 | ```bash 14 | xiaodong@codeland:~$ cd ~ 15 | ``` 16 | 17 | 但是且慢,我想在此告诉你的是,不带任何参数的 `cd` 命令同样能够将你带回主目录: 18 | 19 | ```bash 20 | xiaodong@codeland:~$ cd 21 | ``` 22 | 23 | 换句话说,这两个命令行所达到的效果是相同的。然而,两相比较起来,后者比前者可以少输两个字符,和乐而不为呢? 24 | 25 | 说到 `~` (波浪线),你还应该了解的一个技巧是,我们可以利用它来转到别的用户的主目录。例如: 26 | 27 | ```bash 28 | xiaodong@codeland:~$ cd ~mingji 29 | ``` 30 | 31 | 我们在 `~` (波浪线) 后面直接跟上 `mingji` 这个用户名,于是 `cd` 将我们带到了该用户的主目录。值得注意的是,以下命令行与它并不相同: 32 | 33 | ```bash 34 | xiaodong@codeland:~$ cd ~/mingji 35 | ``` 36 | 37 | 请注意 `~` (波浪线) 后的 `/` (斜杠),这行命令的作用是转到当前用户主目录下的子目录 `mingji`。 38 | 39 | ### 回到上次工作的目录 40 | 41 | 我经常使用的一个导航场景是,在目录 A 中处理了任务之后,接着转到目录 B 中处理任务,一旦完成,我需要再次回到目录 A 继续工作。此时,我们可以执行下面的命令来回到上次工作的目录: 42 | 43 | ```bash 44 | xiaodong@codeland:~$ cd ~/prj/usingcli 45 | xiaodong@codeland:~/prj/usingcli$ pwd 46 | /home/xiaodong/prj/usingcli 47 | xiaodong@codeland:~/prj/usingcli$ cd ~/cli 48 | xiaodong@codeland:~/cli$ pwd 49 | /home/xiaodong/cli 50 | xiaodong@codeland:~/cli$ cd - 51 | /home/xiaodong/prj/usingcli 52 | xiaodong@codeland:~/prj/usingcli$ pwd 53 | /home/xiaodong/prj/usingcli 54 | ``` 55 | 56 | 在此,我第一次工作的目录是 `~/prj/usingcli`,第二次工作的目录是 `~/cli`。通过执行 `cd` 后跟一个 `-` (减号),我们快速的回到了第一次工作的目录。`cd -` 命令相当于执行 `cd "$OLDPWD"` 及 `pwd` 两条命令。 57 | 58 | ```bash 59 | xiaodong@codeland:~$ cd "$OLDPWD" && pwd 60 | ``` 61 | 62 | 我们还可以继续重复执行 `cd -`,这样就会在 `~/prj/usingcli` 和 `~/cli` 两个目录之间反复切换。 63 | 64 | ### 访问常用目录 65 | 66 | 对于需要频繁访问的深层次目录,直接导航起来感觉还是比较麻烦。幸运的是,bash 和 zsh 两个都为我们提供了 `$CDPATH` 变量。这是一个与 `$PATH` 类似的变量,它由 `:` (冒号) 分隔的路径列表组成。利用 `$CDPATH`,我们能够将常用的目录保存起来,以便 `cd` 为我们直接转到这些目录。比如: 67 | 68 | ```bash 69 | xiaodong@codeland:~$ CDPATH=:~:~/src:~/prj/usingcli 70 | ``` 71 | 72 | 在这里,我们将 `~` (用户主目录)、`~/src`、以及 `~/prj/usingcli` 等目录加到了 `$CDPATH` 中。注意 `=` (等号) 后面 `:` (冒号) 的左边为空,它表示当前目录,你应当予以保留。否则,在相对路径的情况下 ,`cd` 就不能转到当前目录下的子目录了。 73 | 74 | 现在,假如我们打算转到 `~/prj/usingcli/build` 目录下的话,那么只要执行下列命令即可: 75 | 76 | ```bash 77 | xiaodong@codeland:~$ cd build 78 | xiaodong@codeland:~/prj/usingcli/build$ 79 | ``` 80 | 81 | 这个例子同时也告诉我们,加到 `$CDPATH` 路径列表的目录为待导航的目标目录的父目录。 82 | 83 | 除了 `$CDPATH` 变量,zsh 也支持 `$cdpath` 变量。 84 | 85 | ### 自动纠正错误 86 | 87 | 在用 `cd` 导航目录时,我们免不了偶尔会输错目录的名称。bash 有一个名为 `cdspell` 的选项可以帮助我们自动纠正拼写错误,并导航到正确的目录。像是不正确的字母顺序、缺少或者多余的字符等错误,`cdspell` 都能纠正。 88 | 89 | 我们在享用如此好的功能之前,需要首先开启 bash 的控制选项: 90 | 91 | ```bash 92 | xiaodong@codeland:~$ shopt -s cdspell 93 | ``` 94 | 95 | `shopt` 命令的 `-s` 用于启用 `cdspell` 选项。 96 | 97 | 现在我们来试一下 `cdspell` 的效果,我们原本是想要导航到 `/etc` 目录,但是我们却错输成了 `/ect`。不过没有关系,bash 已经帮我们自动纠正了错误,并且转到了正确的目录。 98 | 99 | ```bash 100 | xiaodong@codeland:~$ cd /ect 101 | /etc 102 | xiaodong@codeland:/etc$ cd - 103 | xiaodong@codeland:~$ cd /et 104 | /etc 105 | xiaodong@codeland:/etc$ cd - 106 | xiaodong@codeland:~$ cd /etcd 107 | /etc 108 | xiaodong@codeland:/etc$ 109 | ``` 110 | 111 | 在 zsh 中,我们可以给 `cd` 命令两个参数,它们分别是搜索与替换字符串。zsh 将根据搜索字符串来查看当前工作目录,然后使用第二个字符串替换它,并转到替换后的目录。 112 | 113 | ```bash 114 | xiaodong@codeland:~/cli/1.15.8/src$ pwd 115 | /home/xiaodong/cli/1.15.8/src 116 | xiaodong@codeland:~/cli/1.15.8/src$ cd 1.15.8 1.15.9 117 | xiaodong@codeland:~/cli/1.15.9/src$ pwd 118 | /home/xiaodong/cli/1.15.9/src 119 | ``` 120 | 121 | 本例中,我们的当前工作目录为 `/home/xiaodong/cli/1.15.8/src`,`cd` 命令的第一个参数 `1.15.8` 跟当前工作目录相匹配。zsh 用 `1.15.9` 替换了 `1.15.8`,然后转到了新的目录 `/home/xiaodong/cli/1.15.9/src`。 122 | 123 | ### 自动导航 124 | 125 | 因为 `cd` 命令是如此常用,我们使用它的频率又那么高,所以 bash 和 zsh 两个都为 `cd` 命令提供了一个捷径,对像我一样的“懒人”来说,`autocd` 选项极其有用。 126 | 127 | 在 bash 中,我们可以通过下面的命令来启用 `autocd` 选项: 128 | 129 | ```bash 130 | xiaodong@codeland:~$ shopt -s autocd 131 | ``` 132 | 133 | 与此对应的 zsh 指令为: 134 | 135 | ```bash 136 | xiaodong@codeland:~$ setopt autocd 137 | ``` 138 | 139 | 现在假设我们想导航到 `~/prj` 目录,代替执行: 140 | 141 | ```bash 142 | xiaodong@codeland:~$ cd prj 143 | ``` 144 | 145 | 我们可以省略 `cd` 命令,直接执行: 146 | 147 | ```bash 148 | xiaodong@codeland:~$ prj 149 | xiaodong@codeland:~/prj$ pwd 150 | /home/xiaodong/prj 151 | ``` 152 | 153 | ### 使用目录栈 154 | 155 | 前面我们讲到的 `cd -` 允许我们在两个目录之间进行切换,如果我们想要在更多个目录之间切换,那么它就无能为力了。不过,为了帮助我们解决这个问题,bash 和 zsh 提供了目录栈功能。 156 | 157 | 两个最基本的目录栈命令是 `pushd` 和 `popd`。其中,`pushd` 命令将一个目录添加到目录栈中,而 `popd` 命令则从目录栈中移除上次添加的目录。 158 | 159 | ```bash 160 | xiaodong@codeland:~$ pwd 161 | /home/xiaodong 162 | xiaodong@codeland:~$ pushd ~/cli 163 | ~/cli ~ 164 | xiaodong@codeland:~/cli$ pushd ~/prj 165 | ~/prj ~/cli ~ 166 | xiaodong@codeland:~/prj$ popd 167 | ~/cli ~ 168 | xiaodong@codeland:~/cli$ popd 169 | ~ 170 | xiaodong@codeland:~$ 171 | ``` 172 | 173 | 在这个例子中,我们第一次执行 `pushd ~/cli` 后,将 `~/cli` 添加到目录栈的同时,并且转到了该目录。接着,我们继续执行 `pushd ~/prj`,又将 `~/prj` 目录添加到了目录栈。此刻,我们位于 `~/prj` 目录,目录栈中包括 3 个条目: 174 | 175 | ```bash 176 | ~/prj ~/cli ~ 177 | ``` 178 | 179 | 出栈的顺序跟入栈的顺序相反,第一次执行 `popd` 命令后,移除了目录栈中的最左边的条目 `~/prj`,并转到了相邻的 `~/cli` 目录。再次执行 `popd` 命令,则又移除 `~/cli` 条目,然后转到 `~` 目录。 180 | 181 | 如果你在执行多次入栈与出栈后忘了目录栈中还有哪些条目的话,那么可以执行 `dirs -v` 命令来查看: 182 | 183 | ```bash 184 | xiaodong@codeland:~$ dirs -v 185 | 0 ~/prj 186 | 1 ~/cli 187 | 2 ~ 188 | ``` 189 | 190 | `dirs -v` 为我们列出了目录栈中的所有条目,每行一条,而且开头具有编号,以便我们引用。例如: 191 | 192 | ```bash 193 | xiaodong@codeland:~/prj$ pushd +1 194 | ~/cli ~ ~/prj 195 | xiaodong@codeland:~/cli$ 196 | ``` 197 | 198 | 执行 `pushd +1` 将使 `~/cli` 成为目录栈的顶端,并变成当前工作目录。我们可以通过再次执行 `dirs -v` 命令来确认这一点: 199 | 200 | ```bash 201 | xiaodong@codeland:~/cli$ dirs -v 202 | 0 ~/cli 203 | 1 ~ 204 | 2 ~/prj 205 | xiaodong@codeland:~/cli$ pwd 206 | /home/xiaodong/cli 207 | ``` 208 | 209 | `pushd` 命令中的 `+` (加号) 用于从上往下计数。我们也可以使用 `-` (减号) 来从下往上计数。比如: 210 | 211 | ```bash 212 | xiaodong@codeland:~/cli$ popd -1 213 | ~/cli ~/prj 214 | xiaodong@codeland:~/cli$ dirs -v 215 | 0 ~/cli 216 | 1 ~/prj 217 | ``` 218 | 219 | 执行 `popd -1` 命令后从目录栈中移除了倒数第二个条目 `~`。 220 | 221 | ## 使用别名 222 | 223 | 别名是命令行下最常用的省时技巧之一。它通过对频繁使用的命令及选项重新定义一个较短的名称,从而使我们能够减少输入,最终达到提高操作效率的目的。让我们先来看看如何定义别名。 224 | 225 | ### 定义别名 226 | 227 | 不管是 bash,还是 zsh,它们都能使用 `alias` 命令来定义别名。例如,假如我们要将 `ls -lah --color=auto` 定义成 `l` 的话,那么可以执行下列命令: 228 | 229 | ```bash 230 | xiaodong@codeland:~$ alias l='ls -lah --color=auto' 231 | ``` 232 | 233 | 代替输入长长的 `ls -lsh` 命令,现在我们只需直接执行 `l` 即可。 234 | 235 | ```bash 236 | xiaodong@codeland:~$ l 237 | ``` 238 | 239 | 比较常见的别名定义包括下面这些: 240 | 241 | ```bash 242 | alias ..='cd ..' 243 | alias ...='cd ../..' 244 | alias ....='cd ../../..' 245 | alias ls='ls --color=auto' 246 | alias l='ls -lah --color=auto' 247 | alias la='ls -AF --color=auto' 248 | alias ll='ls -lFh --color=auto' 249 | ``` 250 | 251 | 除了节省时间,利用别名我们也可以避免经常性的输入错误。如果你常常将 `ls` 错输成 `sl`,那么不妨为它定义一个别名: 252 | 253 | ```bash 254 | xiaodong@codeland:~$ alias sl='ls' 255 | ``` 256 | 257 | 在 zsh 中,`alias` 命令还能用 `-s` 选项来定义后缀别名。例如,当我们将文件扩展名 `pdf` 定义成 `zathura` 后缀别名后,直接执行 `pdf` 文件名,就会调用 `zathura` 打开该 `pdf` 文件。 258 | 259 | ```bash 260 | xiaodong@codeland:~$ alias -s pdf=zathura 261 | xiaodong@codeland:~$ cheat_sheet_ssh_v4.pdf 262 | ``` 263 | 264 | 在这个例子中,我们通过执行 `cheat_sheet_ssh_v4.pdf` 来代替执行 `zathura cheat_sheet_ssh_v4.pdf` 命令。 265 | 266 | ### 查看别名 267 | 268 | 时间久了,也许你将忘记所定义别名的具体内容。为了查看别名 `sd` 的内容,我们将它作为参数传递给 `alias` 命令: 269 | 270 | ```bash 271 | xiaodong@codeland:~$ alias sd 272 | alias sd='shutdown -h now' 273 | ``` 274 | 275 | 从 `alias` 列出的内容,我们知道 `sd` 是 `shutdown -h now` 的别名。 276 | 277 | `alias` 命令也可以不带任何参数,直接予以执行则会列出当前 Shell 中的所有别名。 278 | 279 | ```bash 280 | xiaodong@codeland:~$ alias 281 | ``` 282 | 283 | ### 取消别名 284 | 285 | 如果某个别名不再适用,那么我们可以使用 `unalias` 命令来取消它。 286 | 287 | ```bash 288 | xiaodong@codeland:~$ unalias sl 289 | ``` 290 | 291 | 执行 `unalias sl` 命令后,将取消我们先前定义的 `sl` 别名。 292 | 293 | ```bash 294 | xiaodong@codeland:~$ sl 295 | ``` 296 | 297 | 此时我们再执行 `sl` 就不是列出当前工作目录的内容了。 298 | 299 | 比永久取消别名更加有用的一个技巧是临时取消别名。 300 | 301 | ```bash 302 | xiaodong@codeland:~$ ls 303 | xiaodong@codeland:~$ \ls 304 | ``` 305 | 306 | 第一次 `ls` 执行的是别名,第二次我们在 `ls` 前面放了一个 `\` (反斜杠),用来临时取消别名。请比较两个命令先后执行的输出结果。 307 | 308 | 此外,`unalias` 命令还支持 `-a` 选项,利用这个选项可以移除所有定义的别名。 309 | 310 | ```bash 311 | xiaodong@codeland:~$ unalias -a 312 | ``` 313 | 314 | ### 别名的缺憾 315 | 316 | 使用别名固然好,但它也有一些缺憾。一方面,别名无法参数化,打算实现一个参数化别名的想法注定会失败。这时候,你应当考虑使用的是函数。另一方面,别名可能覆盖真实的命令,从而误导你原本想要执行命令的意图。 317 | 318 | 通过 `type -a` 我们可以确定别名极其真实的命令: 319 | 320 | ```bash 321 | xiaodong@codeland:~$ type -a sl 322 | sl is aliased to `ls' 323 | sl is /usr/bin/sl 324 | sl is /sbin/sl 325 | sl is /usr/sbin/sl 326 | ``` 327 | 328 | 这里,我们既能看到 `sl` 是 `ls` 的别名,也能看到它有一个真实的命令。 329 | 330 | ## 利用 `{}` 构造参数 331 | 332 | 在命令行下,我们经常会遇到针对多个参数条目执行操作的使用场景,其中文件名算得上是最常见的情形。为了应对这种情况,bash 和 zsh 都提供了逗号分隔的花括号列表,例如: 333 | 334 | ```bash 335 | xiaodong@codeland:~$ echo {one,two,three} 336 | ``` 337 | 338 | 将参数传递给 `echo` 命令之前,Shell 会首先展开花括号列表,并生成以下 3 个参数条目: 339 | 340 | ```bash 341 | one two three 342 | ``` 343 | 344 | 利用 `{}` (花括号),我们可以实现许多有意思的功能,下面是我常用的几个。大家在学习时不妨举一反三,以便灵活运用。 345 | 346 | ### 备份文件 347 | 348 | 我发现很多朋友在备份文件时执行的命令是: 349 | 350 | ```bash 351 | xiaodong@codeland:~$ cp file file.bak 352 | ``` 353 | 354 | 这条命令将 `file` 备份为 `file.bak`。通过 `{}` (花括号),我们可以将该命令改写成: 355 | 356 | ```bash 357 | xiaodong@codeland:~$ cp file{,.bak} 358 | ``` 359 | 360 | `cp` 命令的参数 `file{,.bak}` 展开后将变成 `file file.bak`。这里的 `,` (逗号) 必不可少,否则 Shell 就不会将其展开了。 361 | 362 | 类似的,我们也可以利用 `tar` 结合 `{}` (花括号) 来创建存档: 363 | 364 | ```bash 365 | xiaodong@codeland:~$ tar cf docs{.tar,} 366 | ``` 367 | 368 | 这里,`tar` 命令将 `docs` 目录存档为 `docs.tar`。 369 | 370 | ### 生成序列 371 | 372 | 对于按照一定顺序排列的条目,代替 `,` (逗号),Shell 也支持通过 `..` (两点) 来指定一个区间。比如: 373 | 374 | ```bash 375 | xiaodong@codeland:~$ echo {a..z} 376 | a b c d e f g h i j k l m n o p q r s t u v w x y z 377 | ``` 378 | 379 | `echo` 将回显从 a 到 z 所有的小写字母。 380 | 381 | 又如: 382 | 383 | ```bash 384 | xiaodong@codeland:~$ echo {0..9} 385 | 0 1 2 3 4 5 6 7 8 9 386 | ``` 387 | 388 | `echo` 将回显 0 到 9 的数字。 389 | 390 | 在区间开头数字的前面也可以添加前导的 0,比如: 391 | 392 | ```bash 393 | xiaodong@codeland:~$ echo {01..10} 394 | ``` 395 | 396 | 这将罗列出以下数字序列: 397 | 398 | ```bash 399 | 01 02 03 04 05 06 07 08 09 10 400 | ``` 401 | 402 | 我们甚至还可以在区间的尾端添加一个步进值: 403 | 404 | ```bash 405 | xiaodong@codeland:~$ echo {1..9..2} 406 | ``` 407 | 408 | 末尾的 `2` 为步进值,这样就只会罗列奇数: 409 | 410 | ```bash 411 | 1 3 5 7 9 412 | ``` 413 | 414 | 在 zsh 中,步进值可以为负数,这种情况下将按倒序罗列数字,例如: 415 | 416 | ```bash 417 | xiaodong@codeland:~$ echo {1..9..-2} 418 | 9 7 5 3 1 419 | ``` 420 | 421 | bash 中想要达到同样的效果需要将区间的首尾端对调,比如: 422 | 423 | ```bash 424 | xiaodong@codeland:~$ echo {9..1..2} 425 | 9 7 5 3 1 426 | ``` 427 | 428 | 最后,让我们来看一个实际使用序列的例子。通过生成的序列,将其与路径组合,在下载多个文件时尤其有用。 429 | 430 | ```bash 431 | xiaodong@codeland:~$ wget https://linuxtoy.org/img/{1..5}.png 432 | ``` 433 | 434 | 上述命令中,`wget` 将从 依次下载 1.png、2.png、3.png ... 等图片文件。 435 | 436 | 值得一提的是,除了 `..` (两点) 的区间表示法,zsh 也支持 `-` (减号) 这种区间表示,不过需要启用 `braceccl` 选项。 437 | 438 | ```bash 439 | xiaodong@codeland:~$ setopt braceccl 440 | xiaodong@codeland:~$ echo {A-Za-z} 441 | A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 442 | a b c d e f g h i j k l m n o p q r s t u v w x y z 443 | ``` 444 | 445 | 我们在使用 `setopt` 开启 `braceccl` 选项后,执行 `echo {A-Za-z}` 罗列出了所有大写和小写字母。 446 | 447 | ### 连用与嵌套 448 | 449 | Shell 的 `{}` (花括号) 结构非常灵活和强大,特别是在连用和嵌套时更是威力无穷。先来让我们看一个 `{}` (花括号) 连用的例子。 450 | 451 | ```bash 452 | xiaodong@codeland:~$ mkdir -p 2019/{01..12}/{baby,photo} 453 | ``` 454 | 455 | 在本例中,我们连用两个 `{}` (花括号),这样在每个月份的目录下又分别创建了 `baby` 和 `photo` 两个子目录。这条命令实际上执行的是以下命令: 456 | 457 | ```bash 458 | xiaodong@codeland:~$ mkdir -p 2019/01/baby 2019/01/photo \ 459 | 2019/02/baby 2019/02/photo \ 460 | 2019/03/baby 2019/03/photo \ 461 | 2019/04/baby 2019/04/photo \ 462 | 2019/05/baby 2019/05/photo \ 463 | 2019/06/baby 2019/06/photo \ 464 | 2019/07/baby 2019/07/photo \ 465 | 2019/08/baby 2019/08/photo \ 466 | 2019/09/baby 2019/09/photo \ 467 | 2019/10/baby 2019/10/photo \ 468 | 2019/11/baby 2019/11/photo \ 469 | 2019/12/baby 2019/12/photo 470 | ``` 471 | 472 | 我们不妨将两个命令比较一下,如果直接手动输入后者该是多么枯燥和乏味的事情。但是,有了 `{}` (花括号) 的帮助,我们就变轻松了不少。 473 | 474 | `{}` (花括号) 结构不仅可以连用,而且能够嵌套。例如: 475 | 476 | ```bash 477 | xiaodong@codeland:~$ echo {{A..Z},{a..z},{0..9}} 478 | A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 479 | a b c d e f g h i j k l m n o p q r s t u v w x y z 480 | 0 1 2 3 4 5 6 7 8 9 481 | ``` 482 | 483 | `echo` 命令的外层 `{}` (花括号) 中包含 3 个内层 `{}` (花括号),这样就将所有的大写字母、小写字母以及从 0 到 9 的数字都罗列出来了。 484 | 485 | ## 其它妙招 486 | 487 | 在本节中,我们介绍其它几个必备的命令行使用妙招。 488 | 489 | ### 命令替换 490 | 491 | 假设我们想要编辑包含 `error` 的 Python 源代码文件 (`*.py`),在此之前,我们首先需要确定哪些 Python 文件具有这些字符串。为此,我们可以使用下面的命令将其找出来: 492 | 493 | ```bash 494 | xiaodong@codeland:~$ grep -l error *.py 495 | ``` 496 | 497 | 这里的 `-l` 选项让 `grep` 命令将找出的文件名输出到终端,然后我们就可以用文本编辑器 (如 `nvim`) 来编辑这些文件: 498 | 499 | ```bash 500 | xiaodong@codeland:~$ nvim godns.py 501 | ``` 502 | 503 | 代替这种需要两步才能完成的操作,我们也可以要求 Shell 在执行 `grep` 命令后直接将文件名传递给文本编辑器 (如 `nvim`): 504 | 505 | ```bash 506 | xiaodong@codeland:~$ nvim `grep -l error *.py` 507 | ``` 508 | 509 | Shell 对上述命令 ``` `` ``` (反引号) 中的内容执行命令替换,也就是说,把 `grep` 命令的输出作为 `nvim` 命令的参数。我们从而将两步操作得以合并成一步操作来完成。 510 | 511 | 使用 ``` `` ``` (反引号) 算是老式的命令替换用法,更新式的用法是 `$()`。所以上面的命令也能改写成如下形式: 512 | 513 | ```bash 514 | xiaodong@codeland:~$ nvim $(grep -l error *.py) 515 | ``` 516 | 517 | 我们推荐使用 `$()` 这种新用法,因为在嵌套命令替换时具有更好的可读性。例如: 518 | 519 | ```bash 520 | xiaodong@codeland:~$ nvim $(grep -l failed $(date +'%Y%m%d').log) 521 | xiaodong@codeland:~$ nvim `grep -l failed \`date +'%Y%m%d'\`.log` 522 | ``` 523 | 524 | 在嵌套时,`$()` 看起来一目了然,而 ``` `` ``` (反引号) 则需要转义,其可读性较差。 525 | 526 | ### 使用变量 527 | 528 | 虽然 Shell 本身具有内置变量,而且我们在执行程序时还会碰到环境变量,但是我们在此要讲的却是另一种变量,即用户变量。 529 | 530 | 用户变量是由我们自己所设置的变量,其目的在于临时保存需要多次使用的数据。这样,当我们需要使用数据时就可以通过变量名来引用它了。因为通常变量名比我们要引用的数据要短很多,所以也让我们减少了不必要的重复输入。 531 | 532 | 让我们看一个例子: 533 | 534 | ```bash 535 | xiaodong@codeland:~$ LOG=/var/log/pacman.log 536 | xiaodong@codeland:~$ head $LOG 537 | xiaodong@codeland:~$ grep -i error $LOG 538 | ``` 539 | 540 | 我们将变量 `LOG` 的值设置为 `/var/log/pacman.log`,接着通过 `$LOG` 的形式分别在 `head` 和 `grep` 命令中引用它。 541 | 542 | ### 重复执行命令 543 | 544 | 当我为演讲主题准备材料时,我想用 `figlet` 这个工具来制作一些有趣的 ASCII 艺术字。虽然 `figlet` 提供了很多艺术样式,但是我并没有见过每一种。要想选择最酷的 ASCII 艺术字,所以我必须把每种样式都浏览一遍。于是,我执行了下面的命令: 545 | 546 | ```bash 547 | xiaodong@codeland:~$ figlet -f ascii9 Linux 548 | xiaodong@codeland:~$ figlet -f bigmono9 Linux 549 | xiaodong@codeland:~$ figlet -f emboss Linux 550 | ... 551 | ``` 552 | 553 | 可是,`figlet` 还有很多样式,这样一个一个的查看实在枯燥乏味。Shell 有没有什么重复执行命令的快捷方法呢?回答是肯定的。利用 Shell 提供的 `for` 循环,我们可以一遍又一遍的重复执行命令。下面让我们来一次性查看 `figlet` 的所有样式。 554 | 555 | ```bash 556 | xiaodong@codeland:~$ cd /usr/share/figlet 557 | xiaodong@codeland:~$ for font in *.tlf 558 | > do 559 | > echo "Font: $font" 560 | > figlet -f $(basename $font .tlf) Linux 561 | > done 562 | ``` 563 | 564 | `for` 和 `in` 中间的 `font` 为循环的变量,`*.tlf` 表示扩展名为 `tlf` 的所有文件。`do` 与 `done` 之间的内容为循环体,针对当前目录下的每个 `tlf` 文件都会执行这两条命令。 565 | 566 | 除了上面多行形式的 `for ... in` 循环结构外,我们也可以使用其单行形式,例如: 567 | 568 | ```bash 569 | for font in *.tlf; do figlet -f $(basename $font .tlf) Linux; done 570 | ``` -------------------------------------------------------------------------------- /06-goodies.Rmd: -------------------------------------------------------------------------------- 1 | # 周边好品 2 | 3 | 在前面的章节中,我们主要讨论的是 Shell 本身自带的特性。因为 bash 和 zsh 都是开源软件,所以有许多热心的用户为其添砖加瓦,以便增强它们的功能,使其变得更加好用。本章我们将眼光从 Shell 自身移到周边,来看看由社区贡献的一些好工具。 4 | 5 | ## 配置框架 6 | 7 | 无论是 bash,还是 zsh,我们都能根据自己的好恶来进行配置。这样的好处是配置完全由自己所掌控。但其缺点也很明显,那就是不便于公开分享和贡献。正因为如此,一些 Shell 配置框架应运而生。所谓“众人拾柴火焰高”,积社区之力,Shell 配置框架的内容极其丰富。这使我们得以能够直接将其拿来享用。 8 | 9 | 虽然现在有很多 Shell 配置框架可以选择,有的宣称很轻量,也有的号称速度快,但是我们将根据流行度和活跃性来进行选择。经过综合比较,我们选择的 bash 配置框架是 Bash-it,zsh 配置框架是 Oh My Zsh。下面,我们将分别进行介绍。 10 | 11 | ### bash 配置框架 12 | 13 | Bash-it 配置框架从社区收集了许多实用的命令和脚本,主要包括别名、自动补全代码、定制函数、以及提示符主题等四大类型。得益于 Bash-it 良好的模块化架构,我们也可以添加自己的定制内容。 14 | 15 | #### 安装 16 | 17 | 因为 [Bash-it 位于 GitHub](https://github.com/Bash-it/bash-it) 上,所以在安装 Bash-it 之前,首先需要确认的是系统中是否含有 `git` 命令: 18 | 19 | ```bash 20 | xiaodong@codeland:~$ which git 21 | ``` 22 | 23 | 如果输出内容为: 24 | 25 | ```bash 26 | /usr/bin/git 27 | ``` 28 | 29 | 则可进行下一个安装步骤。否则,可通过所用操作系统的软件包管理器(如 `apt`、`yum`、`pacman`、`emerge` 等)来安装。 30 | 31 | 接着,使用 `git` 命令将 Bash-it 克隆到用户主目录下的 `.bash_it` 子目录: 32 | 33 | ```bash 34 | xiaodong@codeland:~$ git clone --depth=1 \ 35 | https://github.com/Bash-it/bash-it.git \ 36 | ~/.bash_it 37 | ``` 38 | 39 | 然后,执行 `install.sh` 安装脚本来安装 Bash-it: 40 | 41 | ```bash 42 | xiaodong@codeland:~$ cd ~/.bash_it 43 | xiaodong@codeland:~/.bash_it$ ./install.sh -h 44 | xiaodong@codeland:~/.bash_it$ ./install.sh 45 | ``` 46 | 47 | `install.sh` 脚本包括下列 3 个选项,大家可根据需要使用: 48 | 49 | 1. `interactive (-i)`:这个选项允许我们交互式选择要启用哪些别名、自动补全和插件。 50 | 2. `--silent (-s)`:静默安装,没有任何输入提示。 51 | 3. `--no-modify-config (-n)`:不修改现有的 bash 配置文件 `.bashrc` 或 `.bash_profile`。 52 | 53 | 我们不加任何选项,采用默认安装。在安装脚本询问是否保留 `.bashrc` 并追加 Bash-it 模板内容时,回答“N”。这样,我们原有的 `.bashrc` 配置文件将备份为 `.bashrc.bak`。 54 | 55 | 当看到“安装成功完成”的消息时,则说明 Bash-it 安装成功。如图 \@ref(fig:bashit-install) 所示。 56 | 57 | ```{r bashit-install, fig.cap='Bash-it 安装过程'} 58 | knitr::include_graphics('images/bash-it-install.png') 59 | ``` 60 | 61 | 最后,我们关闭并重新打开终端(远端机器则注销并重新登录)或者执行下面的命令就可以开始使用 Bash-it 了: 62 | 63 | ```bash 64 | xiaodong@codeland:~/.bash_it$ source ~/.bashrc 65 | ``` 66 | 67 | #### 查看别名、补全和插件 68 | 69 | Bash-it 的 `install.sh` 脚本在默认情况下只会启用少量的别名、自动补全和插件,下面就让我们找出来。 70 | 71 | 首先,我们来看看启用了哪些别名: 72 | 73 | ```bash 74 | xiaodong@codeland:~$ bash-it show aliases | less 75 | ``` 76 | 77 | 该命令的输出分为 3 列,第一列为别名的名称,第二列显示该别名是否启用(启用的别名在 [] 中有 X),最后一列是有关别名的说明。如图 \@ref(fig:bashit-show-alias) 所示。 78 | 79 | ```{r bashit-show-alias, fig.cap='在 Bash-it 中查看别名'} 80 | knitr::include_graphics('images/bashit-show-alias.png') 81 | ``` 82 | 83 | 在此我们可以看到 Bash-it 启用了 general、apt、ansible 等别名。 84 | 85 | 接着,我们看看启用了哪些自动补全: 86 | 87 | ```bash 88 | xiaodong@codeland:~$ bash-it show completions | less 89 | ``` 90 | 91 | 我们发现 system、bash-it 等自动补全已经启用了。如图 \@ref(fig:bashit-show-comp) 所示。 92 | 93 | ```{r bashit-show-comp, fig.cap='在 Bash-it 中查看补全'} 94 | knitr::include_graphics('images/bashit-show-comp.png') 95 | ``` 96 | 97 | 最后,我们再看看启用了哪些插件: 98 | 99 | ```bash 100 | xiaodong@codeland:~$ bash-it show plugins | less 101 | ``` 102 | 103 | 在输出中显示 base、alias-completion 等插件已经启用。如图 \@ref(fig:bashit-show-plug) 所示。 104 | 105 | ```{r bashit-show-plug, fig.cap='在 Bash-it 中查看插件'} 106 | knitr::include_graphics('images/bashit-show-plug.png') 107 | ``` 108 | 109 | #### 搜索内容 110 | 111 | 除了直接列出所有的别名、自动补全和插件之外,Bash-it 还提供了一个非常快捷的方式来查找所需的内容。比如,我们想要看看有关 tmux 和 ansible 的情况,不妨执行以下命令: 112 | 113 | ```bash 114 | xiaodong@codeland:~$ bash-it search tmux ansible 115 | aliases: ansible tmux 116 | plugins: tmux tmuxinator 117 | completions: tmux 118 | ``` 119 | 120 | 从命令输出中我们看到 tmux 的别名、补全和插件都没有启用,而 ansible 的别名已经启用,因为其颜色为绿色。 121 | 122 | #### 启用别名、补全和插件 123 | 124 | 既然 Bash-it 为我们提供了如此丰富的别名、自动补全和插件,那么不用的话就太可惜了。下面,我们就来分别看看如何启用别名、自动补全和插件。 125 | 126 | 时下非常流行的版本控制系统 Git,相信大家都有过接触和使用。让我们先启用 git 别名: 127 | 128 | ```bash 129 | xiaodong@codeland:~$ bash-it enable alias git 130 | git enabled with priority 150. 131 | ``` 132 | 133 | Bash-it 为我们回显出启用的结果,并设置优先级 150。 134 | 135 | 接着,我们执行以下命令: 136 | 137 | ```bash 138 | xiaodong@codeland:~$ bash-it reload 139 | ``` 140 | 141 | 以便让 Bash-it 自动为我们重新载入配置。这样,我们就能够立即开始使用刚才启用的 git 别名。但是,到底有哪些 git 别名呢?别着急,我们可以通过下面的命令了解: 142 | 143 | ```bash 144 | xiaodong@codeland:~$ bash-it help aliases git | less 145 | ``` 146 | 147 | 大致估算一下,Bash-it 提供的 git 别名差不多有近 80 个,实在是非常多。如图 \@ref(fig:bashit-alias-git) 所示。 148 | 149 | ```{r bashit-alias-git, fig.cap='Bash-it 提供的 git 别名'} 150 | knitr::include_graphics('images/bashit-alias-git.png') 151 | ``` 152 | 153 | 最后,我们拿 Bash-it 的源代码目录来验证 git 别名是否生效: 154 | 155 | ```bash 156 | xiaodong@codeland:~$ cd ~/.bash_it 157 | xiaodong@codeland:~/.bash_it$ gs 158 | On branch master 159 | Your branch is up-to-date with 'origin/master'. 160 | nothing to commit, working tree clean 161 | ``` 162 | 163 | 从 `gs` 所显示出的 git 仓库状态,我们可以确信 git 别名一切正常。 164 | 165 | 再来让我们启用 git 自动补全: 166 | 167 | ```bash 168 | xiaodong@codeland:~$ bash-it enable completion git 169 | git enabled with priority 350. 170 | ``` 171 | 172 | 与启用 git 别名类似,在启用 git 自动补全时,Bash-it 也为其分配了一个优先级 350。 173 | 174 | 还有 git 插件,我们也将其启用: 175 | 176 | ```bash 177 | xiaodong@codeland:~$ bash-it enable plugin git 178 | git enabled with priority 250. 179 | ``` 180 | 181 | Bash-it 命令的输出仍然与启用别名和自动补全时相似。 182 | 183 | 除了通过 `bash-it enable` 命令来启用别名、自动补全和插件外,我们也可以在搜索模块和组件时加以启用。例如: 184 | 185 | ```bash 186 | xiaodong@codeland:~$ bash-it search git --enable 187 | aliases: git gitsvn 188 | plugins: autojump fasd git git-subrepo jgitflow jump 189 | completions: git git_flow git_flow_avh 190 | ``` 191 | 192 | 这样,将把包含 git 关键字的所有别名、自动补全和插件全都启用。本例中的 gitsvn、jgitflow、git_flow 也一并启用了。 193 | 194 | 此外,我们也可以通过下列命令来分别启用所有的别名、自动补全和插件: 195 | 196 | ```bash 197 | xiaodong@codeland:~$ bash-it enable alias all 198 | xiaodong@codeland:~$ bash-it enable completion all 199 | xiaodong@codeland:~$ bash-it enable plugin all 200 | ``` 201 | 202 | 禁用别名、自动补全和插件跟启用时类似,只是把 enable 换成 disable 即可。比如,假设我们想要禁用 gitsvn 别名,则可以执行: 203 | 204 | ```bash 205 | xiaodong@codeland:~$ bash-it disable alias gitsvn 206 | ``` 207 | 208 | 如果禁用成功,Bash-it 命令输出如下结果: 209 | 210 | ```bash 211 | gitsvn disabled. 212 | ``` 213 | 214 | #### 更改主题 215 | 216 | Bash-it 随附了大约 50 多个提示符主题样式,如果想要看看这些主题的真实外观,那么我们可以执行下面的命令: 217 | 218 | ```bash 219 | xiaodong@codeland:~$ BASH_PREVIEW=true bash-it reload 220 | ``` 221 | 222 | Bash-it 的默认主题为 bobby。要是你不喜欢的话,那么可以将其更改为别的主题。不过,Bash-it 并没有提供相关的更改命令,我们需要直接编辑 `.bashrc` 配置文件。 223 | 224 | 使用文本编辑器(如 vim)打开 `~/.bashrc`: 225 | 226 | ```bash 227 | xiaodong@codeland:~$ vim ~/.bashrc 228 | ``` 229 | 230 | 然后找到下行内容: 231 | 232 | ```bash 233 | export BASH_IT_THEME='bobby' 234 | ``` 235 | 236 | 将单引号中的内容(bobby)替换成别的主题名称(如 powerline),并保存即可。 237 | 238 | 为了使新设置的提示符主题生效,你需要关闭并重新打开终端,或者注销并重新登录。如图 \@ref(fig:bashit-theme) 所示。 239 | 240 | ```{r bashit-theme, fig.cap='Bash-it 提示符主题'} 241 | knitr::include_graphics('images/bashit-theme.png') 242 | ``` 243 | 244 | #### 定制别名、插件和主题 245 | 246 | Bash-it 的确为我们提供了不少好用的别名、自动补全和插件,然而,我们还是有不能满足的时候。为此,Bash-it 提供了一种方便我们进行定制的机制,可以定制的内容包括别名、自动补全、插件、主题样式等等,它们的路径和名称如下: 247 | 248 | + `aliases/custom.aliases.bash`:别名 249 | + `completion/custom.completion.bash`:自动补全 250 | + `lib/custom.bash`:库 251 | + `plugins/custom.plugins.bash`:插件 252 | + `custom/themes//.theme.bash`:主题样式 253 | 254 | 在此,我们以如何定制别名为例来说明,其它类型的定制方法类似,无非就是以特定的名称命名并放在确定的目录。 255 | 256 | 首先,我们在 `aliases` 目录下使用文本编辑器(如 vim)创建 `custom.aliases.bash` 文件: 257 | 258 | ```bash 259 | xiaodong@codeland:~$ cd ~/.bash_it/aliases 260 | xiaodong@codeland:~$ vim custom.aliases.bash 261 | ``` 262 | 263 | 接着,添加具体的别名内容: 264 | 265 | ```bash 266 | alias sd='shutdown -h now' 267 | alias up='uptime' 268 | ``` 269 | 270 | 编辑完成后保存。 271 | 272 | 然后,我们可以利用以下命令来查看: 273 | 274 | ```bash 275 | xiaodong@codeland:~$ bash-it help aliases 276 | custom: 277 | sd='shutdown -h now' 278 | up='uptime' 279 | ``` 280 | 281 | 末尾显示的 custom 正是我们添加的内容。 282 | 283 | 我们再重新加载一下配置: 284 | 285 | ```bash 286 | xiaodong@codeland:~$ bash-it reload 287 | ``` 288 | 289 | 现在,我们刚刚添加的定制别名就可以开始使用了: 290 | 291 | ```bash 292 | xiaodong@codeland:~$ up 293 | 07:54:08 up 5:23, 1 user, load average: 0.00, 0.00, 0.00 294 | ``` 295 | 296 | #### 更新 Bash-it 297 | 298 | Bash-it 是社区化项目,隔段时间便会增添新的模块和组件,或是修正过往版本中的缺陷。我们可以通过将 Bash-it 更新到最新版本,以保持同步。为了更新 Bash-it,我们可执行以下命令: 299 | 300 | ```bash 301 | xiaodong@codeland:~$ bash-it update 302 | ``` 303 | 304 | ### zsh 配置框架 305 | 306 | 与 Bash-it 一样,[Oh My Zsh](https://github.com/robbyrussell/oh-my-zsh) 也是开源的社区性项目。在所有的 zsh 配置框架中,Oh My Zsh 算得上是最流行的。Oh My Zsh 包含了许多来自 zsh 社区的好东东,它主要体现在插件、实用的函数、辅助例程、提示符主题样式等方面。 307 | 308 | #### 安装 Oh My Zsh 309 | 310 | 因为 Oh My Zsh 的安装依赖 git 和 curl(或 wget),所以在安装它之前,我们先检查一下系统中是否存在它们: 311 | 312 | ```bash 313 | xiaodong@codeland:~$ which git curl wget 314 | ``` 315 | 316 | 如果以上命令没有返回结果,那么我们需要先把它们安装到系统中。 317 | 318 | 好了,若是一切准备就绪,现在就可以执行下面的命令来安装 Oh My Zsh: 319 | 320 | ```bash 321 | xiaodong@codeland:~$ sh -c \ 322 | "$(curl -fsSL 323 | https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" 324 | ``` 325 | 326 | 安装过程要花一会儿功夫,我们需耐心等待。Oh My Zsh 在安装时将对原来的 `.zshrc` 配置文件备份,若是出现“....is now installed!”的字样时,就说明 Oh My Zsh 安装成功了。如图 \@ref(fig:ohmyzsh-install) 所示。 327 | 328 | ```{r ohmyzsh-install, fig.cap='Oh My Zsh 安装过程'} 329 | knitr::include_graphics('images/ohmyzsh-install.png') 330 | ``` 331 | 332 | #### 启用插件 333 | 334 | 在安装时,Oh My Zsh 只启用了一个 git 插件。若想用得更舒服,我们就得启用其它插件。对 Oh My Zsh 而言,所谓插件通常包括别名、实用函数、自动补全等内容。在启用其它插件之前,也许你想要了解一下 Oh My Zsh 到底有哪些插件?为此,我们可以转到 Oh My Zsh 安装目录下的 `plugins` 子目录查看: 335 | 336 | ```bash 337 | xiaodong@codeland:~$ cd ~/.oh-my-zsh/plugins 338 | xiaodong@codeland:~/.oh-my-zsh/plugins$ ls 339 | ``` 340 | 341 | 我们可以看到,在 Oh My Zsh 中,每个插件都有一个单独的目录。例如,我们继续进入 `systemd` 目录: 342 | 343 | ```bash 344 | xiaodong@codeland:~/.oh-my-zsh/plugins$ cd systemd 345 | xiaodong@codeland:~/.oh-my-zsh/plugins/systemd$ ls 346 | ``` 347 | 348 | 其下包含 `README.md` 和 `systemd.plugin.zsh` 两个文件。前者为有关该插件的使用说明,而后者为 `systemd` 插件的源代码。如图 \@ref(fig:ohmyzsh-view-plug)。 349 | 350 | ```{r ohmyzsh-view-plug, fig.cap='Oh My Zsh 插件目录'} 351 | knitr::include_graphics('images/ohmyzsh-view-plug.png') 352 | ``` 353 | 354 | 另外,我们也可以通过浏览器查看 [Oh My Zsh 的 Wiki 页面](https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins)来了解它有哪些插件。 355 | 356 | 在知晓了有哪些插件及其插件的用途后,现在我们就来启用插件。我们先使用文本编辑器打开 `.zshrc` 配置文件: 357 | 358 | ```bash 359 | xiaodong@codeland:~$ vim ~/.zshrc 360 | ``` 361 | 362 | 然后找到下行内容: 363 | 364 | ```bash 365 | plugins=(git) 366 | ``` 367 | 368 | 将需要启用的插件追加到 `git` 后面即可。 369 | 370 | ```bash 371 | plugins=(git colored-man-pages systemd) 372 | ``` 373 | 374 | 我们在此启用了 `colored-man-pages` 和 `systemd` 插件。 375 | 376 | 为了使启用的插件生效,我们还需要执行下面的命令: 377 | 378 | ```bash 379 | xiaodong@codeland:~$ source ~/.zshrc 380 | ``` 381 | 382 | 现在让我们来验证一下启用的插件,如图 \@ref(fig:man) 和 \@ref(fig:systemd) 所示。 383 | 384 | ```bash 385 | xiaodong@codeland:~$ man zsh 386 | xiaodong@codeland:~$ sc-status sshd 387 | xiaodong@codeland:~$ sc-list-units 388 | ``` 389 | 390 | ```{r man, fig.cap='执行 man zsh 的输出结果'} 391 | knitr::include_graphics('images/man.png') 392 | ``` 393 | 394 | ```{r systemd, fig.cap='执行 sc-status sshd 的输出结果'} 395 | knitr::include_graphics('images/systemd.png') 396 | ``` 397 | 398 | #### 更换主题 399 | 400 | Oh My Zsh 的默认提示符主题样式是 robbyrussell,若是你不喜欢,那么可以更换成别的主题样式。在 Oh My Zsh 中包含有上百个主题样式,每种主题样式的外观可通过其 [Wiki 页面](https://github.com/robbyrussell/oh-my-zsh/wiki/Themes)查看。 401 | 402 | 一旦选定了 Oh My Zsh 的主题样式,便可通过编辑 `.zshrc` 配置文件的方式来更换。 403 | 404 | ```bash 405 | xiaodong@codeland:~$ vim ~/.zshrc 406 | ``` 407 | 408 | 在 `zshrc` 文件中找到下面这行: 409 | 410 | ```bash 411 | ZSH_THEME="robbyrussell" 412 | ``` 413 | 414 | 将双引号中的内容替换成另外的主题样式名称(如 simple)。如果你拿不定主意选哪个主题样式,那么不妨将其设置为 `random`。这样,Oh My Zsh 就会为你随机选择一种主题样式。 415 | 416 | 保存编辑后执行以下命令以便使更改生效,如图 \@ref(fig:ohmyzsh-theme) 所示。 417 | 418 | ```bash 419 | xiaodong@codeland:~$ source ~/.zshrc 420 | ``` 421 | 422 | ```{r ohmyzsh-theme, fig.cap='Oh My Zsh 的 simple 主题样式'} 423 | knitr::include_graphics('images/ohmyzsh-theme.png') 424 | ``` 425 | 426 | #### 定制插件和主题 427 | 428 | 虽然 Oh My Zsh 已经涵盖了不少插件和主题,但是我们还是有可能去添加一些个性化的东西。直接修改原有内容的方法明显不可取。为了解决这个问题,Oh My Zsh 提供了一个名为 `custom` 的目录。我们只需把要定制的内容保存到 `.zsh` 结尾的文件即可。例如,如果我们打算添加一些新的别名,那么在创建 `myaliases.zsh` 文件后,再加入下列内容: 429 | 430 | ```bash 431 | xiaodong@codeland:~$ cd ~/.oh-my-zsh/custom 432 | xiaodong@codeland:~$ vim myaliases.zsh 433 | alias sd='shutdown -h now' 434 | alias up='uptime' 435 | ``` 436 | 437 | 然后保存编辑结果,并执行: 438 | 439 | ```bash 440 | xiaodong@codeland:~$ source ~/.zshrc 441 | ``` 442 | 443 | 现在我们就可以使用新加的别名了: 444 | 445 | ```bash 446 | xiaodong@codeland:~$ up 447 | ``` 448 | 449 | 类似的,如果要定制插件和主题,它们有专门存放的目录。定制的插件代码需放在以下目录: 450 | 451 | ```bash 452 | ~/.oh-my-zsh/custom/plugins 453 | ``` 454 | 455 | 除了我们自己的插件外,这个目录也可以用来安装外部插件。[zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting) 是一个为 zsh 提供语法加亮的插件,其实现灵感来自于 fish shell。遗憾的是,该插件并没有包含到 Oh My Zsh 中。为了使用它,我们可以把它安装到上述目录: 456 | 457 | ```bash 458 | xiaodong@codeland:~$ cd ~/.oh-my-zsh/custom/plugins 459 | xiaodong@codeland:~/.oh-my-zsh/custom/plugins$ git clone \ 460 | https://github.com/zsh-users/zsh-syntax-highlighting.git 461 | ``` 462 | 463 | 然后在 `.zshrc` 配置文件中启用: 464 | 465 | ```bash 466 | plugins=(<原有插件> zsh-syntax-highlighting) 467 | ``` 468 | 469 | 添加 zsh-syntax-highlighting 前,如图 \@ref(fig:synhigh-b) 所示。 470 | 471 | ```bash 472 | xiaodong@codeland:~$ echo $'hello, world\x21' 473 | ``` 474 | 475 | ```{r synhigh-b, fig.cap='未启用 zsh-syntax-highlighting 时'} 476 | knitr::include_graphics('images/synhigh-b.png') 477 | ``` 478 | 479 | 启用 zsh-syntax-highlighting 后,如图 \@ref(fig:synhigh-a) 所示。 480 | 481 | ```bash 482 | xiaodong@codeland:~$ source ~/.zshrc 483 | xiaodong@codeland:~$ echo $'hello, world\x21' 484 | ``` 485 | 486 | ```{r synhigh-a, fig.cap='启用 zsh-syntax-highlighting 后'} 487 | knitr::include_graphics('images/synhigh-a.png') 488 | ``` 489 | 490 | 此外,要把定制的主题代码放在下面的目录: 491 | 492 | ```bash 493 | ~/.oh-my-zsh/custom/themes 494 | ``` 495 | 496 | #### 其它命令和配置 497 | 498 | Oh My Zsh 还带了其它几个有趣而实用的命令。下面简单对其作个介绍: 499 | 500 | + `take`:创建新目录,并转到该目录,而且支持多级目录,如:`take www/oops` 501 | + `zsh_stats`:列出使用次数最多的 20 个命令,包含使用次数和所占百分比统计,如图 \@ref(fig:zsh-stats) 所示。 502 | 503 | ```{r zsh-stats, fig.cap='zsh stats'} 504 | knitr::include_graphics('images/zsh-stats.png') 505 | ``` 506 | 507 | 此外,Oh My Zsh 也包含其它一些有用的配置,我们可根据自己的需要设置: 508 | 509 | ```bash 510 | xiaodong@codeland:~$ vim ~/.zshrc 511 | 512 | ENABLE_CORRECTION="true" # 启用命令自动纠正 513 | ``` 514 | 515 | #### 获取更新 516 | 517 | Oh My Zsh 在默认情况下将每隔几周检查是否有更新。若有更新,则予以提示并进行升级。如果你觉得提示烦人的话,那么可以将其关闭。关闭提示更新的方法是,把下行内容追加到 `~/.zshrc` 配置文件中: 518 | 519 | ```bash 520 | DISABLE_UPDATE_PROMPT=true 521 | ``` 522 | 523 | 我们也可以手动更新 Oh My Zsh,为此,可以执行以下命令,结果如图 \@ref(fig:upgrade-zsh) 所示。 524 | 525 | ```bash 526 | xiaodong@codeland:~$ upgrade_oh_my_zsh 527 | ``` 528 | 529 | ```{r upgrade-zsh, fig.cap='更新 Oh My Zsh'} 530 | knitr::include_graphics('images/upgrade-zsh.png') 531 | ``` 532 | 533 | ## 增强工具 534 | 535 | 除了像 Bash-it 和 Oh My Zsh 这种大而全的 Shell 配置框架外,市面上也包括一些单独的工具。利用这些工具,我们不仅能够增强 Shell 的功能,而且可以达到更加愉悦的 Shell 使用体验。下面,我们就来介绍既实用又有意思的第三方增强工具。 536 | 537 | ### 快速路径切换:z.lua 538 | 539 | [z.lua](https://github.com/skywind3000/z.lua) 是一个使用 Lua 编程语言实现的类似 z.sh、autojump、fasd 等功能的快速路径切换工具。其特点包括: 540 | 541 | + 对所有访问路径进行跟踪,具备学习功能,使用正则匹配从而能够更加准确的带你到想去的地方; 542 | + 与 z.sh、autojump、fasd 相比,速度更快; 543 | + 支持广泛的 shell,包含 bash、zsh、fish 等等。 544 | 545 | #### 安装 z.lua 546 | 547 | 因为 z.lua 是通过 Lua 编程语言实现的,所以在安装它之前,确保我们的系统中已经包含 `lua`。 548 | 549 | ```bash 550 | xiaodong@codeland:~$ which lua lua5.1 lua5.2 lua5.3 551 | ``` 552 | 553 | z.lua 支持 Lua 5.1、5.2、5.3 以上版本,上述命令只要有一个正确输出便可。 554 | 555 | 接下来,我们将 z.lua 的源代码克隆到用户主目录的 `~/.z.lua` 子目录: 556 | 557 | ```bash 558 | xiaodong@codeland:~$ git clone \ 559 | https://github.com/skywind3000/z.lua.git \ 560 | ~/.z.lua 561 | ``` 562 | 563 | 待克隆完毕,我们将 z.lua 的初始化语句添加到相应的 Shell 配置文件。 564 | 565 | 对 bash 来说,将下面这行追加到 `~/.bashrc` 配置文件中: 566 | 567 | ```bash 568 | eval "$(lua ~/.z.lua/z.lua --init bash) 569 | ``` 570 | 571 | 若是 zsh,则添加以下内容到 `~/.zshrc` 配置文件: 572 | 573 | ```bash 574 | eval "$(lua ~/.z.lua/z.lua --init zsh) 575 | ``` 576 | 577 | 另外,我们也可以在 `--init` 选项后面添加 `once enhanced` 参数,以便执行增强匹配模式。我们推荐使用这种匹配模式,从而让 z.lua 更准确的切换到我们想去的路径。 578 | 579 | 保存编辑后,分别执行下列命令以便使 z.lua 即时生效: 580 | 581 | ```bash 582 | xiaodong@codeland:~$ source ~/.bashrc # bash 583 | xiaodong@codeland:~$ source ~/.zshrc # zsh 584 | ``` 585 | 586 | #### 使用 z.lua 587 | 588 | z.lua 在安装成功后会创建一个名为 `z` 的别名。让我们先用 `z` 来切换一些目录: 589 | 590 | ```bash 591 | xiaodong@codeland:~$ z cli 592 | xiaodong@codeland:~/cli$ z ~/prj 593 | xiaodong@codeland:~/prj$ z usingcli 594 | xiaodong@codeland:~/prj/usingcli$ z ~/tmp 595 | xiaodong@codeland:~/tmp$ z ~/.z.lua 596 | xiaodong@codeland:~/.z.lua$ 597 | ``` 598 | 599 | 在默认设置下,z.lua 将所有访问的路径都保存到了 `~/.zlua` 文件中。我们可以用 `cat` 来查看它的内容: 600 | 601 | ```bash 602 | xiaodong@codeland:~$ cat ~/.zlua 603 | /home/xiaodong/cli|6|1551946470 604 | /home/xiaodong/.z.lua|3|1551946500 605 | /home/xiaodong/prj|8|1551944681 606 | /home/xiaodong/tmp|3|1551944599 607 | /home/xiaodong/prj/usingcli|1|1551944549 608 | ``` 609 | 610 | 从上面命令的输出中,我们可以看到每个访问路径的次数和时间都有记录。它们使用 `|` 分隔。z.lua 正是通过这个访问路径数据库来帮助我们快速切换到想去的目录。 611 | 612 | 另外,我们也可以直接执行 `z`,这将使 z.lua 列出所访问的路径条目: 613 | 614 | ```bash 615 | xiaodong@codeland:~$ z 616 | 6 /home/xiaodong/.z.lua 617 | 12 /home/xiaodong/cli/1.15.8/src/event/modules 618 | 12 /home/xiaodong/cli/1.15.8/src/event 619 | 16 /home/xiaodong/prj/usingcli 620 | 20 /home/xiaodong/cli/1.15.8/src 621 | 28 /home/xiaodong/tmp 622 | 32 /home/xiaodong/cli 623 | 52 /home/xiaodong/prj 624 | ``` 625 | 626 | 其中,开头的数字为每个访问路径的权重,该权重由 z.lua 根据算法计算得到。 627 | 628 | 了解了 z.lua 的基本原理,下面我们就来进行实战: 629 | 630 | ```bash 631 | xiaodong@codeland:~$ z p 632 | xiaodong@codeland:~/prj$ 633 | ``` 634 | 635 | 在执行该命令后,z.lua 通过参数 `p` 匹配到了我们先前访问的路径: 636 | 637 | ```bash 638 | /home/xiaodong/prj 639 | ``` 640 | 641 | 并转到了该目录。 642 | 643 | 我们再来试试: 644 | 645 | ```bash 646 | xiaodong@codeland:~/prj$ z p us 647 | xiaodong@codeland:~/prj/usingcli$ 648 | ``` 649 | 650 | 这次,z.lua 将我们带到了下面的目录: 651 | 652 | ```bash 653 | /home/xiaodong/prj/usingcli 654 | ``` 655 | 656 | 在这种情况下,z.lua 必须同时匹配 `p`(匹配 prj)和 `us`(匹配 usingcli),只有两个条件都满足,才会转到相应的目录。 657 | 658 | 此外,`z` 命令还包含一些选项以实现其它的功能。比如: 659 | 660 | + `-i`:交互模式,如果有多个匹配结果的话,那么 z.lua 将展示一个列表: 661 | 662 | xiaodong@codeland:~$ z -i p 663 | 2: 24 /home/xiaodong/tmp 664 | 1: 48 /home/xiaodong/prj 665 | > 666 | 667 | 从列表中,我们可以看到有两个路径条目,中间列为权重。在提示符 `>` 后输入编号,z.lua 将带我们到相应的目录。直接按**回车键**将不进行跳转。 668 | 669 | + `-b`:这个选项在深层次目录中跳转特别有用,它可以将我们快速带回某一级的父目录: 670 | 671 | xiaodong@codeland:~$ z mod 672 | xiaodong@codeland:~/cli/1.15.8/src/event/modules$ z -b sr 673 | xiaodong@codeland:~/cli/1.15.8/src$ 674 | 675 | 第一次,z.lua 跳转到了以下目录: 676 | 677 | ~/cli/1.15.8/src/event/modules 678 | 679 | 在添加 `-b` 选项后,z.lua 根据给定的匹配关键字 `sr` 匹配到了 `src` 这级父目录,并跳转到了该目录: 680 | 681 | ~/cli/1.15.8/src 682 | 683 | 值得一提的是,z.lua 还支持自动补全。如果我们在执行 `z p` 时按 **Tab 键**,z.lua 则会显示一个列表: 684 | 685 | ```bash 686 | xiaodong@codeland:~$ z p 687 | xiaodong@codeland:~$ z /home/xiaodong/prj 688 | /home/xiaodong/prj /home/xiaodong/tmp 689 | ``` 690 | 691 | 经过一段时间的使用,我们认为 z.lua 确实是相当不错的路径切换工具,每一个使用命令行的用户都应当将其纳入自己的工具箱。 692 | 693 | ### 高效查询 Shell 历史:HSTR 694 | 695 | 虽然在 bash 和 zsh 中,我们可以使用 **Ctrl + r** 来搜索历史命令列表,但是它还不够高效。为此,程序员又开发出了比 **Ctrl + r** 更好用的工具:[HSTR](https://github.com/dvorka/hstr)。我们可以把 HSTR 看作 **Ctrl + r** 的增强版本,它使用起来也更加方便。 696 | 697 | #### 安装 HSTR 698 | 699 | HSTR 支持 bash 和 zsh,其本身是使用 C 编写而成,在使用它之前,我们必须先安装它。HSTR 为常见的 Linux 发行版都提供有二进制包,包括 Debian、Ubuntu、Fedora、CentOS 等等。 700 | 701 | 在 Debian 和 Ubuntu 中,可按如下方式安装: 702 | 703 | ```bash 704 | xiaodong@codeland:~$ sudo -i 705 | root@codeland:~# echo -e "\ndeb https://www.mindforger.com/debian \ 706 | stretch main" >> /etc/apt/sources.list 707 | root@codeland:~# wget -qO - https://www.mindforger.com/gpgpubkey.txt \ 708 | | apt-key add - 709 | root@codeland:~# apt update && apt install hstr 710 | ``` 711 | 712 | 如果要在 Fedora 和 CentOS 中安装 HSTR,那么可以执行: 713 | 714 | ```bash 715 | xiaodong@codeland:~$ sudo yum install hstr 716 | ``` 717 | 718 | Arch Linux 可从 [AUR](https://aur.archlinux.org/packages/hstr/) 安装 HSTR。 719 | 720 | #### 配置 HSTR 721 | 722 | 一旦安装完毕 HSTR,我们便可通过 `hstr` 命令来调用它。不过,在此之前,我们需要先对其进行配置。 723 | 724 | 对 bash 来说,执行以下命令: 725 | 726 | ```bash 727 | xiaodong@codeland:~$ hstr -s 728 | alias hh=hstr # hh to be alias for hstr 729 | export HSTR_CONFIG=hicolor # get more colors 730 | shopt -s histappend # append new history items to 731 | # .bash_history 732 | export HISTCONTROL=ignorespace # leading space hides commands 733 | # from history 734 | export HISTFILESIZE=10000 # increase history file size 735 | # (default is 500) 736 | export HISTSIZE=${HISTFILESIZE} # increase history size 737 | # (default is 500) 738 | # ensure synchronization between Bash memory and history file 739 | export PROMPT_COMMAND="history -a; history -n; ${PROMPT_COMMAND}" 740 | # if this is interactive shell, then bind hstr to Ctrl-r 741 | # (for Vi mode check doc) 742 | if [[ $- =~ .*i.* ]]; then bind '"\C-r": "\C-a hstr -- \C-j"'; fi 743 | # if this is interactive shell, then bind 'kill last command' to 744 | # Ctrl-x k 745 | if [[ $- =~ .*i.* ]]; then bind '"\C-xk": "\C-a hstr -k \C-j"'; fi 746 | ``` 747 | 748 | ```bash 749 | xiaodong@codeland:~$ hstr -s >> ~/.bashrc 750 | ``` 751 | 752 | 如果是 zsh,则执行: 753 | 754 | ```bash 755 | xiaodong@codeland:~$ hstr -z 756 | alias hh=hstr # hh to be alias for hstr 757 | export HISTFILE=~/.zsh_history # ensure history file visibility 758 | export HSTR_CONFIG=hicolor # get more colors 759 | bindkey -s "\C-r" "\eqhstr\n" # bind hstr to Ctrl-r 760 | # (for Vi mode check doc) 761 | ``` 762 | 763 | ```bash 764 | xiaodong@codeland:~$ hstr -z >> ~/.zshrc 765 | ``` 766 | 767 | 这段配置不仅为 HSTR 定义了别名 `hh`,而且为其绑定了快捷键 **Ctrl + r**。为了使其生效,分别重新载入 bash 和 zsh 的配置文件: 768 | 769 | ```bash 770 | xiaodong@codeland:~$ source ~/.bashrc 771 | xiaodong@codeland:~$ source ~/.zshrc 772 | ``` 773 | 774 | #### HSTR 的用法 775 | 776 | HSTR 已经准备就绪,现在我们可以开始使用它了。有两种启动 HSTR 的方式,一种是执行命令,另一种是按快捷键。下面,我们分别对其进行介绍。 777 | 778 | 首先,我们来看看如何通过命令的方式来启动 HSTR。前面在配置 HSTR 时,我们为其定义了别名 `hh`。所以我们在命令行直接输入 `hh` 并按**回车键**: 779 | 780 | ```bash 781 | xiaodong@codeland:~$ cd ~/cli 782 | xiaodong@codeland:~/cli$ hh 783 | ``` 784 | 785 | HSTR 为我们呈现了一个可以交互的文本界面,如图 \@ref(fig:hstr) 所示。该界面大致可以分为 4 个部分,从上往下依次为: 786 | 787 | 1. 命令输入行:我们输入命令,或者查询关键字的地方。 788 | 2. 使用提示行:包含如何使用的说明以及按键的作用。 789 | 3. 当前状态行:显示当前视图的排列方式、关键字的匹配方法、以及是否区分大小写等信息。 790 | 4. 历史命令列表:根据视图的排列方式展示历史命令。 791 | 792 | ```{r hstr, fig.cap='HSTR 的界面'} 793 | knitr::include_graphics('images/hstr.png') 794 | ``` 795 | 796 | 让我们在命令输入行输入一些字符看看: 797 | 798 | ```bash 799 | xiaodong@codeland$ vag # vagrant 800 | ``` 801 | 802 | 我们每输入一个字符,HSTR 都会进行搜索,并将列表中匹配的命令用高亮颜色显示。如果输错了,则按**退格键**或**Ctrl + u**删除。然后,再重新输入。 803 | 804 | 现在,根据我们的需要,我们可以进行如下选择: 805 | 806 | + 直接按**回车键**,这将执行列表中的第一行命令。 807 | + 如果想要对命令进行编辑,那么我们可以按**Tab 键**。 808 | + 或者,按**Ctrl + g**取消本次操作。 809 | 810 | 在执行 `hh` 的时候,我们也可以带一个查询关键字参数。这样,HSTR 启动时就会直接将过滤结果展示给我们,如图 \@ref(fig:hstr-arg) 所示。 811 | 812 | ```bash 813 | xiaodong@codeland:~/cli$ hh nvim 814 | ``` 815 | 816 | ```{r hstr-arg, fig.cap='执行 hh nvim 的结果'} 817 | knitr::include_graphics('images/hstr-arg.png') 818 | ``` 819 | 820 | 除了执行命令外,HSTR 还支持通过快捷键启动。我们只需要按**Ctrl + r**即可。 821 | 822 | ##### 列表操作 823 | 824 | 前面我们只提到了如何使用列表中的第一条命令,如果我们想要使用其它命令,那么又该如何操作呢? 825 | 826 | 首先,在 HSTR 的交互文本界面中,我们可以通过下面的快捷键来上下移动: 827 | 828 | + **下方向键**或**Ctrl + n**:向下移动一行 829 | + **上方向键**或**Ctrl + p**:往上移动一行 830 | 831 | 一旦选定某行命令,除了执行我们先前介绍的操作之外,我们还能够将其加入收藏夹,或者删除不再需要的命令。 832 | 833 | + **Ctrl + f**:将命令添加到收藏夹 834 | + **Delete 键**:删除命令,根据提示按**y**将确认删除 835 | 836 | ##### 控制选项 837 | 838 | 在 HSTR 的交互文本界面中,我们可以通过更改它的控制选项,从而改变其行为。HSTR 的控制选项主要包括以下 3 种: 839 | 840 | 1. 视图排序方式:包含按 HSTR 的排名算法排序、历史顺序、以及收藏夹。按**Ctrl + /**可以在这 3 中视图方式中循环。 841 | 2. 匹配方法:包括关键字匹配、精确匹配、以及正则匹配。按**Ctrl + e**可以在 3 中匹配方法中切换。 842 | 3. 是否区分大小写:按**Ctrl + t**进行切换。 843 | 844 | 随着对 HSTR 的使用次数越多,我们越感觉 HSTR 确实是很棒的工具。对于想要追求操作效率的各位读者来说,一定不要错过。 845 | -------------------------------------------------------------------------------- /07-finish.Rmd: -------------------------------------------------------------------------------- 1 | # 结语 2 | 3 | 在本书所讲的内容中,其实自始至终是贯穿着一些基本原则的。正是因为有了这些原则的指引,我们才能万变不离其终。举一反三,方能灵活运用。我们认为最重要的原则包括: 4 | 5 | + **少打多做**:所谓“少打”就是说要尽量少打字。我们输入越少,越能省时。那么,我们出错的机会也将随之变少,自然效率便会升高。我们使用自动补全,我们重用历史命令,其背后都是这个道理。另一方面,不要害怕做试验,要多练,只有通过实践才能了解事物的原理。熟能生巧,练习最终会转化成生产力。 6 | 7 | + **不要重复自己**:这条原则又称为 DRY,也就是 Don't repeat yourself,我从很喜欢的一本书《程序员修炼之道:从小工到专家》上学到的。人是创造性的动物,对于重复性的任务往往会感到厌烦。我们最好把这类任务交给计算机去做。计算机不仅比我们做的效率更高,一般也更少出错。举个例子,我们用 `for` 循环重复执行命令便是应用的该原则。 8 | 9 | + **关心你的工具**:有句俗话叫“磨刀不误砍柴工”,如果工具不拿来时不时的打磨一番,那么再好的工具久而久之也会变得不那么好用了。我们不断打磨 bash 和 zsh,将它们调配到最适合我们操作的状态,用起来自然得心应手。 10 | 11 | 到此为止,我们的命令行旅程就算结束了。通过学习,我相信你已经具有了独自前行的能力和勇气。最后,我们向大家推荐一些深入的学习材料。虽然“RTFM”充满戏谑的成分,但是我们认为手册还是应当读的。 12 | 13 | + [Bash Reference Manual](https://www.gnu.org/software/bash/manual/html_node/index.html):这份《Bash 参考手册》是 bash 的权威指南,如果你立志将其作为自己的主力 Shell 使用,那么每年都应该温习一遍。 14 | 15 | + [A User's Guide to Zsh](http://zsh.sourceforge.net/Guide/zshguide.html):《Zsh 用户指南》,这份文档由 zsh 的作者所写,全面的介绍了 zsh 的用法。 16 | 17 | + 《Unix Power Tools》:我非常喜欢的一本书,Unix 工具和技巧的集大成者。虽然这本书是面向 Unix 进行的讲解,但是可以同样应用于 Linux。 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: pdf epub 3 | 4 | pdf: 5 | Rscript -e "bookdown::render_book('index.Rmd', 'bookdown::pdf_book')" 6 | 7 | epub: 8 | Rscript -e "bookdown::render_book('index.Rmd', 'bookdown::epub_book')" 9 | 10 | mobi: 11 | cd _book && ebook-convert usingcli.epub usingcli.mobi --input-profile default --output-profile kindle 12 | 13 | png: 14 | cd _book && pdftoppm -f 3 -l 5 -png usingcli.pdf usingcli-toc 15 | 16 | tar: 17 | cd _book && \ 18 | mv usingcli_full.pdf 像黑客一样使用命令行.pdf && \ 19 | mv usingcli.epub 像黑客一样使用命令行.epub && \ 20 | mv usingcli.mobi 像黑客一样使用命令行.mobi && \ 21 | tar zcvf usingcli.tar.gz 像黑客一样使用命令行.* 22 | 23 | .PHONY: clean 24 | clean: 25 | rm -v usingcli.* 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 像黑客一样使用命令行 2 | 3 | 像黑客一样使用命令行 4 | 5 | 精通命令行用法通常被认为是 Linux 黑客的秘密武器。对于普通用户而言,这种看似比较复杂、难以掌握的技能,其实只要打消恐惧心理,辅之以一定的练习,学会并不难。本书将从实际的例子出发,教你从无到有,一步一步的学习使用命令行。一旦夯实了基础,在学了高级命令行用法后,你也可以像 Linux 黑客一样感受到使用命令行是何等的高效和令人愉悦。 6 | 7 | 除了文字版本之外,本书亦有[配套视频](https://selfhostedserver.com/usingcli)。 8 | 9 | ## 目录 10 | 11 | + 第一章 入门指引 12 | 13 | - 控制台 14 | - 终端 15 | - 终端模拟器 16 | - Shell 17 | - 命令行界面 18 | - 如何进入命令行 19 | - 你好,命令行 20 | 21 | + 第二章 神奇补全 22 | 23 | - 何谓补全 24 | - 补全触发按键 25 | - 文件名、路径名补全 26 | - 程序名、命令名补全 27 | - 用户名、主机名及变量名补全 28 | - 可编程补全 29 | 30 | + 第三章 重温历史 31 | 32 | - 设置历史变量 33 | - 查看历史命令 34 | - 搜索历史命令 35 | - 前后移动历史命令 36 | - 快速修改并执行上一条命令 37 | - 快速执行历史命令 38 | - 快速引用上一条命令的参数 39 | - 快速引用参数的部分内容 40 | - 历史命令展开模式总结 41 | 42 | + 第四章 编辑大法 43 | 44 | - 设置编辑模式 45 | - Emacs 编辑模式实战 46 | - vi 编辑模式实战 47 | 48 | + 第五章 必备锦囊 49 | 50 | - 快速导航 51 | - 使用别名 52 | - 利用 `{}` 构造参数 53 | - 其它妙招 54 | 55 | + 第六章 周边好品 56 | 57 | - 配置框架 58 | - 增强工具 59 | 60 | + 第七章 结语 61 | 62 | ## 构建 63 | 64 | 本书源文件使用 [R Markdown](https://rmarkdown.rstudio.com/) 格式编写而成,通过 [bookdown](https://bookdown.org/) 工具生成 PDF 及 ePub 电子书。在准备好这些工具之后,执行以下命令即可: 65 | 66 | ``` 67 | $ make 68 | ``` 69 | 70 | 这将在 `_book` 目录生成 `usingcli.pdf` 和 `usingcli.epub` 文件。 71 | 72 | ## 许可 73 | 74 | 知识共享许可协议 75 | 76 | 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 77 | -------------------------------------------------------------------------------- /_bookdown.yml: -------------------------------------------------------------------------------- 1 | book_filename: usingcli 2 | language: 3 | label: 4 | fig: "图 " 5 | tab: "表 " 6 | ui: 7 | chapter_name: ["第 ", " 章"] 8 | -------------------------------------------------------------------------------- /_output.yml: -------------------------------------------------------------------------------- 1 | bookdown::gitbook: 2 | css: css/style.css 3 | split_by: section 4 | config: 5 | toc: 6 | collapse: subsection 7 | before: | 8 |
  • 像黑客一样使用命令行
  • 9 | download: no 10 | sharing: no 11 | bookdown::pdf_book: 12 | includes: 13 | in_header: latex/preamble.tex 14 | before_body: latex/before_body.tex 15 | after_body: latex/after_body.tex 16 | keep_tex: yes 17 | dev: "cairo_pdf" 18 | latex_engine: xelatex 19 | citation_package: natbib 20 | template: latex/template.tex 21 | pandoc_args: "--top-level-division=chapter" 22 | toc_depth: 3 23 | toc_unnumbered: no 24 | toc_appendix: yes 25 | quote_footer: ["\\begin{flushright}", "\\end{flushright}"] 26 | bookdown::epub_book: 27 | stylesheet: css/style.css 28 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | .rmdcaution, .rmdimportant, .rmdnote, .rmdtip, .rmdwarning { 2 | padding: 1em 1em 1em 4em; 3 | margin-bottom: 10px; 4 | background: #f5f5f5 5px center/3em no-repeat; 5 | } 6 | .rmdcaution { 7 | background-image: url("../images/caution.png"); 8 | } 9 | .rmdimportant { 10 | background-image: url("../images/important.png"); 11 | } 12 | .rmdnote { 13 | background-image: url("../images/note.png"); 14 | } 15 | .rmdtip { 16 | background-image: url("../images/tip.png"); 17 | } 18 | .rmdwarning { 19 | background-image: url("../images/warning.png"); 20 | } 21 | p.caption { 22 | color: #777; 23 | margin-top: 10px; 24 | } 25 | p code { 26 | white-space: inherit; 27 | } 28 | pre { 29 | word-break: normal; 30 | word-wrap: normal; 31 | } 32 | pre code { 33 | white-space: inherit; 34 | } 35 | p.flushright { 36 | text-align: right; 37 | } 38 | blockquote > p:last-child { 39 | text-align: right; 40 | } 41 | blockquote > p:first-child { 42 | text-align: inherit; 43 | } 44 | -------------------------------------------------------------------------------- /images/bash-comp-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bash-comp-conf.png -------------------------------------------------------------------------------- /images/bash-it-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bash-it-install.png -------------------------------------------------------------------------------- /images/bashit-alias-git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bashit-alias-git.png -------------------------------------------------------------------------------- /images/bashit-show-alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bashit-show-alias.png -------------------------------------------------------------------------------- /images/bashit-show-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bashit-show-comp.png -------------------------------------------------------------------------------- /images/bashit-show-plug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bashit-show-plug.png -------------------------------------------------------------------------------- /images/bashit-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/bashit-theme.png -------------------------------------------------------------------------------- /images/caution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/caution.png -------------------------------------------------------------------------------- /images/cli-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/cli-hello.png -------------------------------------------------------------------------------- /images/cmd-comp-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/cmd-comp-more.png -------------------------------------------------------------------------------- /images/completion-hostname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/completion-hostname.png -------------------------------------------------------------------------------- /images/completion-prog-bash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/completion-prog-bash.png -------------------------------------------------------------------------------- /images/completion-prog-zsh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/completion-prog-zsh.png -------------------------------------------------------------------------------- /images/cover-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/cover-thumb.png -------------------------------------------------------------------------------- /images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/cover.jpg -------------------------------------------------------------------------------- /images/cover.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/cover.pdf -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/cover.png -------------------------------------------------------------------------------- /images/dec_vt100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/dec_vt100.jpg -------------------------------------------------------------------------------- /images/editor-emacs-view-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/editor-emacs-view-body.png -------------------------------------------------------------------------------- /images/editor-vi-view-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/editor-vi-view-body.png -------------------------------------------------------------------------------- /images/gimp-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/gimp-comp.png -------------------------------------------------------------------------------- /images/history-five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/history-five.png -------------------------------------------------------------------------------- /images/history-mode-i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/history-mode-i.png -------------------------------------------------------------------------------- /images/history-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/history-search.png -------------------------------------------------------------------------------- /images/history-word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/history-word.png -------------------------------------------------------------------------------- /images/hstr-arg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/hstr-arg.png -------------------------------------------------------------------------------- /images/hstr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/hstr.png -------------------------------------------------------------------------------- /images/ibm_1620_model_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/ibm_1620_model_1.jpg -------------------------------------------------------------------------------- /images/ibm_1620_model_1_front_panel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/ibm_1620_model_1_front_panel.jpg -------------------------------------------------------------------------------- /images/important.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/important.png -------------------------------------------------------------------------------- /images/linux_booting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/linux_booting.png -------------------------------------------------------------------------------- /images/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/man.png -------------------------------------------------------------------------------- /images/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/note.png -------------------------------------------------------------------------------- /images/ohmyzsh-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/ohmyzsh-install.png -------------------------------------------------------------------------------- /images/ohmyzsh-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/ohmyzsh-theme.png -------------------------------------------------------------------------------- /images/ohmyzsh-view-plug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/ohmyzsh-view-plug.png -------------------------------------------------------------------------------- /images/shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/shell.png -------------------------------------------------------------------------------- /images/shell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 32 | 37 | 38 | 45 | 49 | 50 | 58 | 63 | 64 | 65 | 78 | 80 | 81 | 83 | image/svg+xml 84 | 86 | 87 | 88 | 89 | 93 | 97 | 109 | 121 | 129 | 134 | 用户 145 | 161 | Shell 172 | Kernel 183 | 184 | 185 | -------------------------------------------------------------------------------- /images/subcmd-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/subcmd-comp.png -------------------------------------------------------------------------------- /images/synhigh-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/synhigh-a.png -------------------------------------------------------------------------------- /images/synhigh-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/synhigh-b.png -------------------------------------------------------------------------------- /images/systemd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/systemd.png -------------------------------------------------------------------------------- /images/tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/tip.png -------------------------------------------------------------------------------- /images/upgrade-zsh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/upgrade-zsh.png -------------------------------------------------------------------------------- /images/user-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/user-comp.png -------------------------------------------------------------------------------- /images/var-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/var-comp.png -------------------------------------------------------------------------------- /images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/warning.png -------------------------------------------------------------------------------- /images/xterm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/xterm.png -------------------------------------------------------------------------------- /images/zsh-autosug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/zsh-autosug.png -------------------------------------------------------------------------------- /images/zsh-cmd-opt-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/zsh-cmd-opt-comp.png -------------------------------------------------------------------------------- /images/zsh-stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/zsh-stats.png -------------------------------------------------------------------------------- /images/zsh-user-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/zsh-user-comp.png -------------------------------------------------------------------------------- /images/zsh-var-comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/zsh-var-comp.png -------------------------------------------------------------------------------- /images/zsh_prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuxiaodong/usingcli-book/2262e48c86ce66899d476959ef03984c59895b83/images/zsh_prompt.png -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "像黑客一样使用命令行" 3 | author: "徐小东" 4 | description: 命令行高手修炼之道。 5 | documentclass: ctexbook 6 | geometry: 7 | - b5paper 8 | - tmargin=2.5cm 9 | - bmargin=2.5cm 10 | - lmargin=2.5cm 11 | - rmargin=2.5cm 12 | link-citations: yes 13 | colorlinks: yes 14 | lof: yes 15 | lot: yes 16 | site: bookdown::bookdown_site 17 | biblio-style: apalike 18 | cover-image: images/cover.png 19 | --- 20 | 21 | ```{r, setup, include=FALSE} 22 | knitr::opts_chunk$set( 23 | echo = FALSE, out.width = '100%' 24 | ) 25 | ``` 26 | 27 | # 致谢 {-} 28 | 29 | 感谢 Bash 及 Zsh 开源社区,你们永远是最棒的家伙! 30 | 31 | # 更新 {-} 32 | 33 | 你可以从 获取本书的更新版本。另外,本书也包括视频版本,请通过 了解详情。 34 | 35 | + Version 2019.3.17:初版 36 | + Version 2019.8.24:修订版,增加“高效查询 Shell 历史:HSTR”小节。 37 | -------------------------------------------------------------------------------- /latex/after_body.tex: -------------------------------------------------------------------------------- 1 | \backmatter 2 | \printindex 3 | -------------------------------------------------------------------------------- /latex/before_body.tex: -------------------------------------------------------------------------------- 1 | 2 | \thispagestyle{empty} 3 | 4 | \begin{center} 5 | 献给 6 | 7 | 海燕和铭基 8 | \end{center} 9 | 10 | \setlength{\abovedisplayskip}{-5pt} 11 | \setlength{\abovedisplayshortskip}{-5pt} 12 | -------------------------------------------------------------------------------- /latex/preamble.tex: -------------------------------------------------------------------------------- 1 | \usepackage{booktabs} 2 | \usepackage{longtable} 3 | 4 | \usepackage{framed,color} 5 | \definecolor{shadecolor}{RGB}{248,248,248} 6 | 7 | \renewcommand{\textfraction}{0.05} 8 | \renewcommand{\topfraction}{0.8} 9 | \renewcommand{\bottomfraction}{0.8} 10 | \renewcommand{\floatpagefraction}{0.75} 11 | 12 | \let\oldhref\href 13 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}} 14 | 15 | \makeatletter 16 | \newenvironment{kframe}{% 17 | \medskip{} 18 | \setlength{\fboxsep}{.8em} 19 | \def\at@end@of@kframe{}% 20 | \ifinner\ifhmode% 21 | \def\at@end@of@kframe{\end{minipage}}% 22 | \begin{minipage}{\columnwidth}% 23 | \fi\fi% 24 | \def\FrameCommand##1{\hskip\@totalleftmargin \hskip-\fboxsep 25 | \colorbox{shadecolor}{##1}\hskip-\fboxsep 26 | % There is no \\@totalrightmargin, so: 27 | \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% 28 | \MakeFramed {\advance\hsize-\width 29 | \@totalleftmargin\z@ \linewidth\hsize 30 | \@setminipage}}% 31 | {\par\unskip\endMakeFramed% 32 | \at@end@of@kframe} 33 | \makeatother 34 | 35 | \makeatletter 36 | \@ifundefined{Shaded}{ 37 | }{\renewenvironment{Shaded}{\begin{kframe}}{\end{kframe}}} 38 | \makeatother 39 | % https://github.com/CTeX-org/ctex-kit/issues/331 40 | % \RecustomVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\},formatcom=\xeCJKVerbAddon} 41 | 42 | \newenvironment{rmdblock}[1] 43 | { 44 | \begin{itemize} 45 | \renewcommand{\labelitemi}{ 46 | \raisebox{-.7\height}[0pt][0pt]{ 47 | {\setkeys{Gin}{width=3em,keepaspectratio}\includegraphics{images/#1}} 48 | } 49 | } 50 | \setlength{\fboxsep}{1em} 51 | \begin{kframe} 52 | \item 53 | } 54 | { 55 | \end{kframe} 56 | \end{itemize} 57 | } 58 | \newenvironment{rmdnote} 59 | {\begin{rmdblock}{note}} 60 | {\end{rmdblock}} 61 | \newenvironment{rmdcaution} 62 | {\begin{rmdblock}{caution}} 63 | {\end{rmdblock}} 64 | \newenvironment{rmdimportant} 65 | {\begin{rmdblock}{important}} 66 | {\end{rmdblock}} 67 | \newenvironment{rmdtip} 68 | {\begin{rmdblock}{tip}} 69 | {\end{rmdblock}} 70 | \newenvironment{rmdwarning} 71 | {\begin{rmdblock}{warning}} 72 | {\end{rmdblock}} 73 | 74 | \usepackage{makeidx} 75 | \makeindex 76 | 77 | \urlstyle{tt} 78 | 79 | \usepackage{amsthm} 80 | \makeatletter 81 | \def\thm@space@setup{% 82 | \thm@preskip=8pt plus 2pt minus 4pt 83 | \thm@postskip=\thm@preskip 84 | } 85 | \makeatother 86 | 87 | \frontmatter 88 | -------------------------------------------------------------------------------- /latex/template.tex: -------------------------------------------------------------------------------- 1 | \documentclass[$if(fontsize)$$fontsize$,$endif$$if(papersize)$$papersize$paper,$endif$$for(classoption)$$classoption$$sep$,$endfor$]{$documentclass$} 2 | $if(linestretch)$ 3 | % 设置行间距 4 | \usepackage{setspace} 5 | \setstretch{$linestretch$} 6 | $endif$ 7 | \usepackage{fontspec} 8 | \defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase} 9 | \usepackage{xunicode} 10 | $if(geometry)$ 11 | \usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} 12 | $endif$ 13 | \usepackage[unicode=true]{hyperref} 14 | $if(colorlinks)$ 15 | \PassOptionsToPackage{usenames,dvipsnames}{color} % color is loaded by hyperref 16 | $endif$ 17 | \hypersetup{ 18 | $if(title-meta)$ 19 | pdftitle={$title-meta$}, 20 | $endif$ 21 | $if(author-meta)$ 22 | pdfauthor={$author-meta$}, 23 | $endif$ 24 | $if(keywords)$ 25 | pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, 26 | $endif$ 27 | $if(colorlinks)$ 28 | colorlinks=true, 29 | linkcolor=$if(linkcolor)$$linkcolor$$else$Maroon$endif$, 30 | citecolor=$if(citecolor)$$citecolor$$else$Blue$endif$, 31 | urlcolor=$if(urlcolor)$$urlcolor$$else$Blue$endif$, 32 | $else$ 33 | pdfborder={0 0 0}, 34 | $endif$ 35 | breaklinks=true, 36 | bookmarksnumbered=true, 37 | pdfstartview=FitH} 38 | \urlstyle{same} % don't use monospace font for urls 39 | $if(natbib)$ 40 | \usepackage{natbib} 41 | \bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} 42 | $endif$ 43 | $if(biblatex)$ 44 | \usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} 45 | $for(bibliography)$ 46 | \addbibresource{$bibliography$} 47 | $endfor$ 48 | $endif$ 49 | $if(listings)$ 50 | \usepackage{listings} 51 | \lstset{breaklines=true} 52 | $endif$ 53 | $if(highlighting-macros)$ 54 | $highlighting-macros$ 55 | $endif$ 56 | $if(verbatim-in-note)$ 57 | \usepackage{fancyvrb} 58 | \VerbatimFootnotes % allows verbatim text in footnotes 59 | $endif$ 60 | $if(tables)$ 61 | \usepackage{longtable,booktabs} 62 | % Fix footnotes in tables (requires footnote package) 63 | \IfFileExists{footnote.sty}{\usepackage{footnote}\makesavenoteenv{long table}}{} 64 | $endif$ 65 | $if(graphics)$ 66 | \usepackage{graphicx,grffile} 67 | \makeatletter 68 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} 69 | \def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} 70 | \makeatother 71 | % Scale images if necessary, so that they will not overflow the page 72 | % margins by default, and it is still possible to overwrite the defaults 73 | % using explicit options in \includegraphics[width, height, ...]{} 74 | \setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} 75 | $endif$ 76 | $if(links-as-notes)$ 77 | % Make links footnotes instead of hotlinks: 78 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}} 79 | $endif$ 80 | $if(strikeout)$ 81 | \usepackage[normalem]{ulem} 82 | % avoid problems with \sout in headers with hyperref: 83 | \pdfstringdefDisableCommands{\renewcommand{\sout}{}} 84 | $endif$ 85 | $if(indent)$ 86 | $else$ 87 | \IfFileExists{parskip.sty}{% 88 | \usepackage{parskip} 89 | }{% else 90 | \setlength{\parindent}{0pt} 91 | \setlength{\parskip}{6pt plus 2pt minus 1pt} 92 | } 93 | $endif$ 94 | \setlength{\emergencystretch}{3em} % prevent overfull lines 95 | \providecommand{\tightlist}{% 96 | \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} 97 | $if(numbersections)$ 98 | \setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} 99 | $else$ 100 | \setcounter{secnumdepth}{0} 101 | $endif$ 102 | $if(subparagraph)$ 103 | $else$ 104 | % Redefines (sub)paragraphs to behave more like sections 105 | \ifx\paragraph\undefined\else 106 | \let\oldparagraph\paragraph 107 | \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} 108 | \fi 109 | \ifx\subparagraph\undefined\else 110 | \let\oldsubparagraph\subparagraph 111 | \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} 112 | \fi 113 | $endif$ 114 | 115 | % set default figure placement to htbp 116 | \makeatletter 117 | \def\fps@figure{htbp} 118 | \makeatother 119 | 120 | $for(header-includes)$ 121 | $header-includes$ 122 | $endfor$ 123 | 124 | $if(title)$ 125 | \title{$title$$if(thanks)$\thanks{$thanks$}$endif$} 126 | $endif$ 127 | $if(subtitle)$ 128 | \providecommand{\subtitle}[1]{} 129 | \subtitle{$subtitle$} 130 | $endif$ 131 | $if(author)$ 132 | \author{$for(author)$$author$$sep$ \and $endfor$} 133 | $endif$ 134 | $if(institute)$ 135 | \providecommand{\institute}[1]{} 136 | \institute{$for(institute)$$institute$$sep$ \and $endfor$} 137 | $endif$ 138 | \date{$date$} 139 | 140 | \begin{document} 141 | $if(title)$ 142 | \maketitle 143 | $endif$ 144 | $if(abstract)$ 145 | \begin{abstract} 146 | $abstract$ 147 | \end{abstract} 148 | $endif$ 149 | 150 | $for(include-before)$ 151 | $include-before$ 152 | 153 | $endfor$ 154 | $if(toc)$ 155 | { 156 | \setcounter{tocdepth}{$toc-depth$} 157 | \tableofcontents 158 | } 159 | $endif$ 160 | $if(lot)$ 161 | \listoftables 162 | $endif$ 163 | $if(lof)$ 164 | \listoffigures 165 | $endif$ 166 | $body$ 167 | 168 | $if(natbib)$ 169 | $if(bibliography)$ 170 | $if(biblio-title)$ 171 | $if(book-class)$ 172 | \renewcommand\bibname{$biblio-title$} 173 | $else$ 174 | \renewcommand\refname{$biblio-title$} 175 | $endif$ 176 | $endif$ 177 | \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} 178 | 179 | $endif$ 180 | $endif$ 181 | $if(biblatex)$ 182 | \printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ 183 | 184 | $endif$ 185 | $for(include-after)$ 186 | $include-after$ 187 | 188 | $endfor$ 189 | \end{document} 190 | --------------------------------------------------------------------------------