曾经总会好奇:怎么获取上一个命令呢?应该很简单才对吧?简单的搜索后,下面是我得到的结果,就记录一下吧
头图出自 Orangestar 的专辑 SEASIDE SOLILOQUIES, 好看又好听。所以这里贴曲就贴这个专辑的主打歌好了:一首 Alice in 冷凍庫,希望你喜欢。
什么时候要用这个?
有时候我们写了一长串命令,比如有很麻烦的路径之类的,这时候我们可能会希望用某个符号来自动地填上命令里的某些参数。一个最常见的例子,当我要安装某些软件包的时候,偶尔会忘记加上 sudo 来以管理员权限运行。这时候把上面的命令复制一遍再补上 sudo 实在是太慢了,而按下上箭头后在把光标挪到第一行,最后补上 sudo 总是感觉很累,手的移动距离感觉好远。除此之外,有时输入的一长串命令/参数并运行之后,我需要接着上面的参数继续运行别的命令,此时要是用命令行历史的话,就又得用光标定位之后,再删掉没有用的东西,最后再填上要替换的内容。这实在是太慢了。
好在这时候,我们还可以使用 zsh 交互模式下的一个内置宏:使用 !,感叹号,以及其对应的一些变体,来获取上个命令中的参数/整个命令等。下面就来介绍怎么使用吧。
我需要取整个命令
上个命令是什么?
我们可以用 !!,或者 !-1, 来获取 “上一个执行了的命令”。比如如下操作:
1$ echo hello bash world!
2hello bash world!
3$ echo !! # !! 替换了上面整个执行了的命令,也就是替换了 "echo hello bash world"
4echo hello bash world!
5$ echo !-1 # 同上,也是替换上面执行的命令,所以替换了 "echo echo hello bash world"
6echo echo hello bash world!
我要调用历史命令
我们还可以用 !<num> 来选择某个历史命令。我们可以先用 head 来查看一下我们的命令历史里最早有一些什么:
1$ head ~/.zsh_history # 这里我的 zsh 命令历史存在这个文件里,可以用 head 查看前几个命令
2: 12345:0;clear
3: 12346:0;echo hello
4: 12347:0;ls
5## ... ...
随后我们可以使用 !1 来选择历史命令中的第一个命令,这里的第一个命令就是 clear:
1$ !1 # 执行第一个历史命令,也就是 clear,会直接清空屏幕;
2$ !2 # 执行第二个历史命令,会打印 hello;
3hello
4$ !3 # 执行第三个历史命令,会打印当前文件夹下的内容
5file1 file2 file3
小结
我们可以看到,后面跟着的数字实际上表示了“第几个命令”,而举一反三,!-1 则代表的是“最后一个命令”,即上一个命令,那么 !-2 就是倒数第二个命令。
有了这两个命令,我们可以很方便地在忘记使用
sudo权限时,使用sudo !!或者选择某个历史命令,来快速使用sudo权限执行命令。
我需要取几个参数
我需要某个参数
我们可以使用 :<num> 来选择第几个参数。它需要配合 ! 进行使用。参数从 1 开始,而 0 有特殊含义,代表命令。比如:
1$ echo one two three
2one two three
3$ echo !-1:2 # 相当于 echo two
4two
5$ echo !:0 # 上个命令使用了 echo,所以 0 代表 echo,这个命令相当于 echo echo
6echo
当使用 : 来进行参数选择时,如果是从上一个命令中选择则可以简写为 !:<num1>-<num2>。
我需要这几个参数
我们还能用 :<num1>-<num2> 来范围式地选择命令的参数。比如,使用 !!:1-2 就说明要取第一个和第二个参数。(注意这里是参数,不是空格分隔的字符串,也不包含第一个词(也就是命令))。比如:
1$ echo one two three four
2one two three four
3$ echo !!:1-2 # 相当于 echo one two
4one two
5$ echo one two three four # 这行用来重置最后一个命令
6one two three four
7$ echo !!:-3 # 没有 <num1> 则会自动替换为0,相当于 echo echo one two three
8echo one two three
9$ echo !-2:1-2 # 配合 !<num> 使用,相当于 echo one two
10one two
11$ echo !-3:1- # 没有 <num2> 则会匹配到除了最后一个参数外的参数,相当于 echo one two three
12one two three
13$ echo !-4:$ # 使用 $ 来获取最后一个参数,相当于 echo four
14four
15$ echo !-5:3-$ # 同样 $ 也支持范围选择,相当于 echo three four
16three four
17$ echo !-6:* # 使用 * 来表示所有的参数,相当于 echo !-6:1-$,也就是 echo one two three four
18one two three four
19$ echo !:* # !: 是在使用冒号时 !!: 或者 !-1: 的简写,相当于 echo one two three four
20one two three four
如果没有 <num1>,则默认从 0 开始,也就是会包含所有内容;如果没有 <num2>,则默认停在最后一个参数前。可以使用 * 来选择所有的参数,使用 $ 选择最后一个参数。
我要对字符串做处理
在冒号后使用一些字母来做相应的处理。假设有命令 ls /path/to/a/file.txt 并且我们使用 echo !:1 尝试调用这个 ls 的命令,则下面的参数选择器可以做到:
:p(print) 只打印,不运行,或者说提供一个预览。ZSH 用户也许不需要担心这一点。:q(quote) 对选中字段加上引号,结果为'/path/to/a/file':r(root)取文件的完整文件名,结果为/path/to/a/file:e(extension)取文件的后缀名,结果为txt:h(head)取文件路径的地址,结果为/path/to/a/:t(tail)取文件的名称,结果为file.txt:s/to/has(search)可以在参数中寻找第一个to并替换为has,结果为/path/has/a/file.txt:gs/to/has(global search)同上,但是全局查找替换。
TL;DR
下面是一个表格简单描述这些用法
命令选择(使用 !)
| 语法 | 含义 | 示例 |
|---|---|---|
!! | 上一条命令 | sudo !! |
!-n | 倒数第 n 条命令 | !-2 |
!n | 第 n 条历史命令 | !42 |
!字符串 | 最近以该字符串开头的命令 | !ls |
!?字符串? | 最近包含该字符串的命令 | !?foo? |
^旧^新 | 将上一条命令中第一个“旧”替换为“新” | ^cat^bat |
参数选择 (使用 :)
下面的示例命令使用 echo file.txt 来做演示。
| 语法 | 含义 | 示例 |
|---|---|---|
!!:0 | 上一条命令的命令名 | !!:0 → echo |
!!:1 | 第一个参数 | !!:1 → file.txt |
!!:2 | 第二个参数 | |
!!:$ | 最后一个参数 | !!:$ |
!!:* | 所有参数(等同于 !!:1-$) | !!:* |
!!:1-3 | 第 1 到第 3 个参数 | !!:1-3 |
!!:2-$ | 从第 2 个到最后一个参数 | !!:2-$ |
!$ | 上一条命令的最后一个参数 (可以省略冒号) | cat !$ |
!* | 上一条命令的所有参数(可以省略冒号) | rm !* |
参数修饰
下面的示例命令使用 echo /path/to/file.txt 来做演示。
| 修饰符 | 含义 | 示例 |
|---|---|---|
:p | 只打印命令,不执行 | sudo !!:p |
:q | 给参数加引号,避免空格或特殊字符问题 | echo !!:1:q |
:h | 获取路径头部(类似 dirname) | echo !!:1:h → /path/to |
:t | 获取路径尾部(类似 basename) | echo !!:1:t → file.txt |
:r | 去掉文件扩展名(保留主名) | echo !!:1:r → file |
:e | 获取文件扩展名 | echo !!:1:e → txt |
:s/旧/新/ | 替换第一个出现的子串 | !!:1:s/foo/bar/ |
:gs/旧/新/ | 替换所有出现的子串 | !!:1:gs/foo/bar/ |
总结
这里其实应该没有写完,不过就这些已经列出来的方法而言,我个人感觉是已经挺够用的了。毕竟,平时最常用的也就是 sudo !! 来给 pamcan -Syu 补上管理员权限而已,或者是在 ls -l /path/to/file 确定文件/文件夹存在后用 vim 或者 cd 打开它罢了。
还有一点要注意的是,bash 默认是不会像 zsh 一样先提供一个预览,让你看看会发生什么的,而是直接就运行命令了。所以也许在 bash 中使用这个功能时需要额外注意,特别是涉及一些比较危险的动作,比如 rm 这类命令。此时你可以尝试先用 :p 来打印出来要运行的命令,没啥问题就可以运行了。印象中应该还有一个办法,来让 bash 也先提供一个预览而非直接运行。不过,因为我用的是 zsh,就不纠结这个问题了。也许以后我还会更新这篇文章呢?哈哈。
那么,感谢你看到这里,祝您身心愉悦,身体健康~
