曾经总会好奇:怎么获取上一个命令呢?应该很简单才对吧?简单的搜索后,下面是我得到的结果,就记录一下吧
头图出自 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
,就不纠结这个问题了。也许以后我还会更新这篇文章呢?哈哈。
那么,感谢你看到这里,祝您身心愉悦,身体健康~