Bash history 和 fc 命令详解

Bash history 和 fc 命令详解

history 简单介绍

在 Bash 命令行上执行各种命令,特别在键入比较长的命令时,如果每次都要手工逐个字符重复输入,比较烦人。因此,尽可能利用好 Bash 的历史扩展功能非常必要,其目的是便于复用前面执行过的命令,以提高工作效率,尽可能避免错误。近日花费不少时间进行理解和整理,为了今后使用查找方便,总结如下:

Bash 默认开启交互式登录的 history 功能,在用户登录时读取默认文件 (~/.bash_history) 中的记录,并在当前会话使用过程中,不断将新执行的命令自动添加到历史记录列表中。如果用户正常退出登录,就会把内存中的命令历史记录自动保存到默认文件中。

在我们与历史命令打交道过程中,我们总体上有几种操作:

  • 一是管理历史记录集合(增删查改);
  • 二是如何复用历史记录中命令(这是重点);
  • 三是管理与历史记录及文件相关的几个环境变量,例如设置历史记录的数量,显示历史记录时是否带有执行命令时的时间等;
  • 四是如何更好地修复命令,使用 fc 修复命令。

一、管理历史记录集合

对集合的常规操作一般是增删查改四个动作,历史记录集合也不例外。不过特定于历史记录的具体情况,还要管理保存历史记录的文件。因为我们在 shell 上操作的永远是内存中的记录列表,因此还需要有读写其它文件中的记录,或者把新增的部分记录追加到相应文件中等操作。

另外,还可以只是在历史记录列表中记录命令历史而不实际执行,及实际执行了命令而不记录到历史中。

history :其作用是显示或者操纵命令历史列表。显示命令历史列表时行前带有行号(或编号),修改过的条目前面有 ‘*’。

history 命令的语法为:

history [n]              # 显示最后 n 条历史记录,如果省略 n ,显示所有
history -c               # 清除内存中的所有历史记录
history -d offset        # 删除指定编号的历史记录
history -anrw [filename] # 几个与文件相关的读写操作选项
history -s arg [arg ...] # 不执行命令,而记录到历史列表中
history -p arg [arg ...] # 执行命令,但不记录其执行命令

查询

在使用 history [n] 时,默认会在每条命令条目前面加上编号,并且按照执行命令的时间倒序排列。

# 查看内存中的所有历史记录列表:
]# history
    1  date
    2  date +"%F %T"
    3  date 121210302025.30
    4  date -s "2023-10-13 10:25:30"
    5  hwclock -s
 ......

# 查看最后 5 条历史记录
]# history 5
  144  cat /etc/issue
  145  cat /etc/os-release
  146  ll
  147  ls /
  148  history 5

删除

# 想要删除历史列表中的某条记录,使用 `history -d n`,
# 如果 n 是一个正整数,就会删除对应编号的记录条目;
# 如果 n 是一个负整数,则会删除反向倒数对应的条目。
]# history -d 145
]# history 5
  145  ll
  146  ls /
  147  history 5
  148  history -d 145
  149  history 5

# 想要清除所有内存中的历史记录
]# history -c

# 此时内存中的历史记录全部被清除,但是文件中的历史记录不会被删除,
# 这样就是丢失了当前会话中新执行的历史记录而已。
# 可以再次读入文件中的历史记录,或者重新登录即可自动读取文件中的记录。

增加

增加历史记录;这是在不断执行命令过程中自动进行的。当然也可以从默认或其他保存有历史记录的文件中读取。

  • 当然也可以不真正执行,只是在历史记录中新增一条记录而已。

    # 看看下面这条命令,是否很危险?!但是,放心,并不会真正执行
    ]# history -s rm -rf /
    ]# history 5
      147  history 5
      148  history -d 145
      149  history 5
      150  rm -rf /
      151  history 5
    
  • 执行命令,但不记录到历史列表中

    ]# history 5
      166  ls
      167  ll /etc/issue
      168  history 5
      169  cat bash_history.new
      170  history 5
    
    ]# history -p ll /etc/os-release
    ll
    /etc/os-release
    
    ]# history 5
      166  ls
      167  ll /etc/issue
      168  history 5
      169  cat bash_history.new
      170  history 5
    

    这时没有看到最后执行的命令记录。

修改

修改历史记录中的命令是为了执行,没有纯粹的修改。具体实例放在复用历史记录中。

追加新的历史记录到文件中

# 查看当前 `~/.bash_history` 文件中的最后 5 条记录情况
]# tail -5 ~/.bash_history
rpm -qa bash*
help history
exit
yum provides nmcli
exit

# 把当前会话中新增的历史记录追加到文件中
]# history -a

# 再次查看历史记录文件的最后 5 条记录
]# tail -5 ~/.bash_history
history 5
tail ~/.bash_history
tail 5 ~/.bash_history
tail -5 ~/.bash_history
history -a

# 把所有内存中的历史记录都保存到其它指定的文件中
]# history -w bash_history2

]# wc -l bash_history2
158 bash_history2

从其它文件中读取历史记录到当前会话中

# 手动创建一个文件,并写入部分记录数据
]# cat bash_history.new
ls
ll /etc/issue

# 读取这个文件的记录追加到历史记录列表中
]# history -r bash_history.new

]# history 5
  164  vim bash_history.new
  165  history -r bash_history.new
  166  ls
  167  ll /etc/issue
  168  history 5

如果多次执行 history -r 操作,文件的记录会多次追加到会话中的历史记录中。

history -n:其作用是从文件中读取尚未读取过的行追加到当前会话历史记录中。

刚开始学习时难以理解这个命令使用场景,后来看到一篇博文才理解其用意:具体使用场景为:

我们先复制当前会话,这时这两个会话中的历史记录是一致的。然后在一个会话(简称:会话1)中执行了一些新的命令,而另一个会话(简称:会话2)也想同步这些新增的历史记录时,可以先把会话1中的新增的历史记录保存到文件中,
这时会话2就可以执行同步操作了。

 会话1中执行追加新增命令记录的操作
]# history -a

# 会话2中执行同步操作,把没有读取过的记录同步到历史记录中,
# 实现了两个会话的历史记录同步
]# history -n

另外已经读取过的历史记录不会再追加到内存中的历史记录中,对比两个命令,建议使用 history -n [filename]

具体更详细的介绍参考:Linux history -n 与 history -r 功能区别

二、复用历史记录

复用历史记录中的命令,减少击键数量,总体思路是:在历史记录中找到想要的命令后直接执行;或者适当修改一下历史记录中的某个命令后再执行;或者复用历史命令中的各个组成部分及对这些部分进行修饰等。

不管进行何种复用操作,首先要知道如何在历史记录列表中找到相应的记录,然后,要么直接执行,要么复用历史命令中的各个组成部分,以及对这些部分进行调整。

在 Bash 的历史命令的帮助中,把命令行上用于触发从历史记录列表中搜索并扩展某条记录的动作称为“事件”,把历史记录命令中的各个组成部分(使用空格隔开的)称为“单词”,并有一些“修饰符”用于操作单词部分,例如替换,提取文件名、文件扩展名、目录等。

事件指示符

输入少量字符来查找命令历史记录,其实就是历史扩展功能。引入历史扩展的是特定的字符,这个字符默认是感叹号 !

想要转义这个字符使之成为字面值,可以使用斜杠(\)或者单引号。还有几个字符如果紧跟在 ! 后面也会阻止其历史扩展,具体为:
空格,tab键,换行,回车和 = ,如果启用了extglob shell选项,还有小括号 ( 。

事件指示符是对历史记录列表中命令条目的引用。以下是语法:

!     开始历史替换,可以理解为,只要在命令上输入了 !,并在紧跟以下字符,
      就是触发历史扩展功能。
!n    引用历史记录中第 n 条命令。
!-n   引用当前命令行倒数 n 条命令。
!!    引用前一个命令。这等价于 `!-1`!string
      引用历史记录中以 string 开头的上一条命令。
!?string[?]
      引用历史记录中包含 string 的上一条命令,
      如果 string 后面紧跟换行,则尾随的 ? 可以省略。
^string1^string2^
      用 string2 快速替换 string1 后重复执行前一个命令。
      这相当于后面要示例的 "!!:s/string1/string2/"!#    引用到目前为止输入的整个命令。没有找到使用场景,后面没有示例。

为了示例,先清理内存中的历史记录,执行 history -c ,然后执行一些操作以产生一些历史记录。

]# history
    1  tar cf etc.tar /etc/
    2  cp /etc/passwd passwd.backup
    3  ps aux | grep http
    4  systemctl restart sshd
    5  /usr/sbin/apachectl restart
    6  history
  • 使用编号 !n 搜索历史记录中的指定命令

    如果想要直接执行前面已经执行过的命令,不需要重新输入,只需通过使用历史记录中的命令行编号即可。

    ]# !4
    systemctl restart sshd
    

    想要执行倒数第 2 条命令:

    ]# !-2
    

    想要执行上一条命令,可以使用以下之一:

    ]# !!
    
    ]# !-1
    

    你也可以使用快捷键 ctrl+p (如果你在默认的 emacs 模式)来得到上一条命令。
    也可以使用向上的方向键。我个人认为如果不想直接执行,这两种方法更好,因为这种方法是先得到上一条命令,有进一步修改的机会,然后按回车会才执行。

    如果你已经使用了 set -o vi ,启用了 vi 式样的命令行编辑,则使用 Esc,k 来获得相同的结果。

  • 使用 !string!?string 来执行带关键字的命令

    你也可以使用关键字来执行历史记录中的命令。个人认为这是更好的选择,因为你不需要知道编号是多少。

    以下的示例会搜索以 ps 开头的最近使用过的命令,并执行。

    4]# !ps
    ps aux | grep http
    root       29325  0.0  0.5 282924 11916 ?        Ss   10:50   0:00 /usr/sbin/httpd -DFOREGROUND
    apache     29326  0.0  0.4 296808  8600 ?        S    10:50   0:00 /usr/sbin/httpd -DFOREGROUND
    apache     29327  0.3  1.0 2730920 20472 ?       Sl   10:50   0:02 /usr/sbin/httpd -DFOREGROUND
    apache     29328  0.2  0.9 2599784 18424 ?       Sl   10:50   0:01 /usr/sbin/httpd -DFOREGROUND
    apache     29329  0.2  0.9 2534248 18424 ?       Sl   10:50   0:01 /usr/sbin/httpd -DFOREGROUND
    root       29555  0.0  0.0  12136  1040 pts/9    S+   11:01   0:00 grep --color=auto http
    

    以下示例会搜索包含关键字 apache 的最近使用过的命令,并执行。

    ]# !?apache
    /usr/sbin/apachectl restart
    
    # 这里使用 ? 代表逆序向上搜索,这与vim中 ? 功能一样,便于记忆。
    

    你也可以使用快捷键 ctrl+r 逆向搜索,并且是可视的增量搜索,使用更友好:

    ]#
    (reverse-i-search)`apa': /usr/sbin/apachectl restart
    

    在搜索过程中,可以通过 ctrl+o 执行命令,并获取历史记录中当前行的下一行到命令行上等待编辑, ctrl+g 放弃执行搜索到的命令。

    这里还有一个小技巧:就是找到想要的命令后,点击左或右方向键就能显示整条命令等待修改,有时这样更好。

  • 使用 ^string1^string2 替换上一条命令

    在下面的示例中,首先执行 ls 命令来验证一个文件,然后意识到我们想要查看文件的内容。这时可以不需要再次键入完整路径及文件名,只需将前面命令中的 ls 替换成 cat ,如下所示:

    ]# ls /etc/cron.daily/logrotate
    /etc/cron.daily/logrotate
    
    ]# ^ls^cat^
    cat /etc/cron.daily/logrotate
    

    该语法是下面要介绍的单词修饰符 :s 简化版。

单词指示符

在您键入一个新的命令时,单词指示符可能非常有用,它可以使用前面执行过的命令的参数(即为单词)。使用冒号(:)与事件提示符隔开。下面提供一些示例。

  • 使用 ^ 获取命令的第一个参数

    在以下示例中,!cp:^ 当成一个参数传递给 ls -l 命令。!cp:^ 在历史记录中定位以 cp 开头的先前命令,且取得该命令的第一个参数。

    ]# cp /etc/passwd passwd.backup
    
    [root@CentOS8 ~]# ls -l !cp:^
    ls -l /etc/passwd
    

    以下示例获取上一个命令的第一个参数。

    ]# ls /etc/issue /etc/os-release
    /etc/issue  /etc/os-release
    
    ]# ls -l !!:^
    ls -l /etc/issue
    
    # 以下是 ls -l !!:^ 简化版
    ]# ls -l !^
    
  • 使用 $ 获取命令的最后参数

    在以下示例中,!cp:$ 作为一个参数传递给 ls -l 命令。!cp:$ 先定位以 cp 开头的前一个命令,且取得该命令的最后参数。

    ]# cp /etc/passwd passwd.backup
    
    ]# ls -l !cp:$
    ls -l passwd.backup
    

    以下示例取得上一个命令的最后参数。

    ]# ls -l !!:$
    
    # 更简化的格式
    ]# ls -l !$
    

    其实更好的方法是使用快捷键来取得上一个命令的最后参数。在命令行上先键入命令的前面部分,空格后采用以下两种之一的组合键,ESC, . 或者 Alt+.

    注意:后一种快捷键可能无效,原因是该快捷键被占用,或没有把 Meta 键设置成 Alt 键。

    在xshell中,如果需要进行设置,方法是:
    右键所有会话中的某个会话 --> 属性 --> 终端 --> 键盘 --> 选择元(Meta)键仿真下的多选按钮),顺便把 DELETE键序列选择两个 ASCII 127的单选按钮。

  • 使用 :n 获取命令的第 n 个参数

    在以下示例中,!tar:2 作为一个参数传递给 ls -l 命令。也是先在历史记录中定位以 tar 开头的上一个命令,并获得该命令的第 2 个参数。

    ]# tar cvfz home-dir-backup.tar.gz /home
    
    ]# ls -l !tar:2
    ls -l home-dir-backup.tar.gz
    
  • 使用 :* 获取命令的所有参数

    在以下示例中,!cp:* 作为一个参数传递给 ls -l 命令。也是先在历史记录中定位以 cp 开头的上一个命令,并获得该命令所有的参数。

     ]# !cp
     cp /etc/passwd passwd.backup
    
     ]# ls -l !cp:*
     ls -l /etc/passwd passwd.backup
    
  • 使用 !% 引用最近搜索到的单词

    正如上面解释的那样,!?apache 将搜索历史记录中包含关键字 apache 的命令,并执行它。

    ]# /usr/sbin/apachectl restart
    
    ]# !?apach
    /usr/sbin/apachectl restart
    

    !% 将引用被前个 ? 搜索匹配的整个单词。

    例如,如果前面已经进行了 ?apache 搜索,那么这个 !% 将会匹配整个单词 /usr/sbin/apachectl 。注意,在该环境中 / 作为一个单词的组成部分对待。其实是以空白分割的各个部分都是单词。

    因此,在这个示例中,通过执行以下命令,就可以停止 apache 服务了。

    ]# !% stop
    /usr/sbin/apachectl stop
    
  • 使用 x-y 获取命令中的指定范围的参数

    在以下示例中,!tar:3-5 作为一个参数传递给 ls -l 命令。!tar:3-5 先在历史记录中定位以 tar 开头的上一个命令,并取得第 3 个至第 5 个参数。

    ]# tar cvf home-dir.tar john jason ramesh rita
    
    ]# ls -l !tar:3-5
    ls -l john jason ramesh
    

    以下命令将得到从第 2 开始的所有参数。

    ]# ls -l !tar:2-$
    
    # 更简单的格式为
    ]# ls -l !tar:2*
    
    # 而以下的格式等价于 `x-$-1`,即从第x个开始至倒数第2个为止,不含最后一个参数
    ]# ls -l !tar:2-
    

特别说明:如果在没有指定事件提示符的情况下应用单词提示符,则上一个命令当成事件,即选中最近的上一个命令。

历史修饰符

修饰符用在单词指示符之后,请看下面的示例中的解释。

  • 使用 :h 以删除单词中尾随的路径名

    在以下示例中,!!:$:h 得到上一个命令中的最后参数,并删除尾随的路径名。在这个案例中,它删除文件名,最后得到完整的路径。

    ]# mkdir -p /very/long/path/name/
    
    ]# touch /very/long/path/name/file-name.txt
    
    ]# ls -l /very/long/path/name/file-name.txt
    
    ]# ls -l !!:$:h
    ls -l /very/long/path/name
    
  • 使用 :t 从单词中删除所有前导路径名

    这与前面的示例刚好相反。

    在以下示例中,!!:$:t 获取上一个命令的最后参数,并删除所有前导路径名。在这个案例中,它只得到文件名。

    ]# ls -l /very/long/path/name/file-name.txt
    
    ]# ls -l !!:$:t
    ls -l file-name.txt
    
  • 使用 :r 删除单词中的文件名扩展名

    在以下示例中,!!:$:r 获取上一个命令的最后参数,并仅删除“.后缀”(这里是文件名的扩展名)。在这个案例中,它删除 .txt

    ]# ls -l /very/long/path/name/file-name.txt
    
    [root@CentOS8 ~]# ls -l !!:$:r
    ls -l /very/long/path/name/file-name
    
  • 使用 :s/string1/string2 像 sed 一样在 bash 历史中进行替换

    我们不使用前面讨论过的 ^original^replacement^ 这种格式,也可以在 bash 历史中使用像 sed 那样的替换,示例如下。这可能容易记住:!! 用于调用上一个命令,:s/original-string/replacement-string/ 像 sed 那样语法替换字符串。

    ]# !!:s/ls -l/cat/
    

    您也可使用 g 标志(放在 s 前面)进行全局替换,如下所示。当您拼错多个单词并想修改所有这些词,然后再次执行该命令时比较有用。

    在以下示例中,我错误地键入两次"password"(应该是"passwd")。

    ]# cp /etc/password password.bak
    

    想要解决这个问题,只需像 sed 一样执行以下的全局历史命令替换操作。

    ]# !!:gs/password/passwd/
    cp /etc/passwd passwd.bak
    
  • 使用 :& 快速重复替换

    如果您像上面示例那样已经成功地执行了 bash 历史替换,那么,您能使用 :& 快速重复相同的替换。

    ]# tar cvf password.tar /etc/password
    

    现在,您可以不用重新键入该命令,或者执行gs/password/passwd ,仅仅使用 :& ,就能复用最后的替换操作。使用 :g& 是为了全局复用最后的替换操作。

    ]# !!:g&
    tar cvf passwd.tar /etc/passwd
    
  • 使用 :p 仅仅打印命令而不执行

    当您正在执行复杂的历史替换,且在执行它前,想查看最终的命令的样子时,这非常有用。

    因为使用了 :p,因此它仅仅执行替换和显示新的命令。一旦您已经验证该 bash 历史扩展,且其结果正是您想要执行的命令,只需删除 :p ,然后再次执行它。

    ]# tar cvf home-dir.tar john jason ramesh rita
    
    ]# tar cvfz new-file.tar !tar:3-:p
    tar cvfz new-file.tar john jason ramesh
    
    # 以下介绍一些使用语法
    !string:p # 仅打印逆序搜索到以string开头的命令,不实际执行命令
    !$:p      # 仅打印输出上一条命令的最后一个参数
    !*:p      # 仅打印输出上一条命令的所有参数
    

三、与 history 命令相关的环境变量

Bash 默认开启历史扩展功能,如果想关闭或者启用该功能,可采用以下设置:

# 关闭历史扩展功能
]# set +o histexpand

# 启用历史扩展功能
]# set -o histexpand

在非交互的shell中(例如,shell 脚本)没有开启历史扩展功能。

Bash 内置了 6 个环境变量,用于控制 history 的特性。如果想修改这些环境变量,可以在 ~/.bash_profile 文件进行添加或者修改,例如在该文件中添加:

HISTSIZE=2000
export HISTSIZE
  • HISTSIZE:指定可以添加到内存中历史记录的最大数量,默认是 1000 条。

  • HISTFILESIZE:指定可以保存到历史记录的文件中记录的最大数量,有默认值,各版本不尽相同。

    # Ubuntu 22.04
    ]$ echo $HISTSIZE; echo $HISTFILESIZE
    1000
    2000
    
    # CentOS 8.5
    ]# echo $HISTSIZE; echo $HISTFILESIZE
    1000
    1000
    
  • HISTFILE: 指定保存历史记录的文件,默认是:~/.bash_history

    你可以指定不同的文件来保存历史记录,例如:

    vim ~/.bash_profile
    HISTFILE=~/.bash_history2
    export HISTFILE
    
  • HISTTIMEFORMAT:在显示历史记录时,默认显示带编号的记录。如果想要显示带有时间日期的记录,可以设置该变量。

    ]# export HISTTIMEFORMAT='%F %T '
    ]# history
      1  2022-10-18 10:07:15 date
      2  2022-10-18 10:07:15 date +"%F %T"
      3  2022-10-18 10:07:15 date 121210302025.30
      4  2022-10-18 10:07:15 date -s "2023-10-13 10:25:30"
      5  2022-10-18 10:07:15 hwclock -s
      ......
    

    用于该变量设置的格式字符串与 date 命令的相同。

  • HISTIGNORE: 指定那些命令予以忽略,不会记录到历史列表中。
    HISTIGNORE=“cmd1:cmd2*”,表示忽略cmd1,及以cmd2开头的命令,不会记录到历史中。

    ]# export HISTIGNORE="pwd:ls:exit"
    ]# pwd
    /root
    # ls
    anaconda-ks.cfg
    ]# history 3
    270  2022-10-18 10:34:38 man date
    271  2022-10-18 10:36:39 export HISTIGNORE="pwd:ls"
    272  2022-10-18 10:37:03 history 3
    
    # pwd, ls 命令没有被记录
    
  • HISTCONTROL:控制历史命令的记录方式。可以取以下值:

    ignoredups:这是默认值,可以忽略连续重复的命令。
    
    ignorespace:忽略所有以空格开头的命令。
    
    ignoreboth:相当于赋值 ignoredups, ignorespace 的两个值。
    
    erasedups:删除历史记录中的重复命令,包括不连续的。
    

    如果我们把该变量设置成 ignoreboth ,那么连续相同的命令只记录一次;以空格开头的命令不会记录。这样,可以使用空格加命令,以确保带有敏感信息的内容不会被记录,如密码。

    export HISTCONTROL=ignoreboth
    

四、fc 修复命令

fc 是 fix command 的缩写。这是 Bash 的内置命令,用于显示或编辑,及重新执行历史记录列表中的命令。您可以在您喜欢的编辑器中编辑指定执行过的命令并执行,无需重新键入该完整的命令。此命令有助于纠正前面键入的命令中的拼写错误,并避免重复键入冗长复杂的命令。大多数 shell 都可以使用,包括 Bash、Zsh、Ksh等。

其语法为:

fc [-e ename] [-lnr] [first] [last]

  其中的选项说明:
    first 和 last 可以通过数字指定范围,或者 first, last 也可以是字符串,
    用以确定以该字符串开始的最近的命令。

    -e ename  选择用于编辑的编辑器。默认是FCEDIT,然后 EDITOR,最后是 vi。
    -l        列出部分历史记录,而不是编辑命令。
    -n        在列出历史记录时,省略行号。
    -r        翻转行的排列次序。


fc -s [pat=rep] [command]
   这种格式就是先进行替换,即每个 pat 都替换成 rep,然后重新执行。command 与上一种格式的 first 一样。
   一种有用的别名就是使用这种语法,`alias r="fc -s"` ,因此,键入 `r cc` 执行以 `cc` 开头的最后命令,
   键入 `r` 重新执行上一条命令。

CentOS 8 默认编辑器是 vi, 而 Ubuntu 22.04 默认是 nano,如果想要修改为vi,可以使用

$ sudo update-alternatives --config editor

然后选择

/usr/bin/vim.basic

即可。也可以使用后面介绍的方法,及设置环境变量。

使用 fc 命令列出最近执行过的命令

在执行不带参数的 fc -l 命令时,将显示最近执行过的 16 个命令历史记录。

$ fc -l
171	 history -w
172	 cat .bash_history
173	 man bash
174	 sudo update-alternatives --config editor
175	 man grep
176	 export | grep -i hist
177	 history 5
178	 fc -l
179	 export | grep -i hist
180	 ls hist
181	 export | grep -i hist
182	 xcli
183	 sudo apt install xclip
184	 man xclip
185	 echo hello | xclip
186	 man bash

使用 -r 选项,可以翻转历史记录的次序。

$ fc -lr

使用 -n 选项,可以压制行号出现。

$ fc -ln
	 man grep
	 export | grep -i hist
	 history 5
	 fc -l
	 export | grep -i hist
	 ls hist
	 export | grep -i hist
	 xcli
	 sudo apt install xclip
	 man xclip
	 echo hello | xclip
	 man bash
	 fc -l
	 help fc
	 fc -lr
	 fc -l

现在您看不到行号了。

想要列出从指定命令开始的结果,只需使用带行号的 -l 选项。例如,要显示从第 100 行开始后所有记录,示例如下:

$ fc -l 100
100	 vim .bash_history
101	 man history
102	 man 1 history
103	 man -k history
104	 history
105	 history -h
106	 help history
  ......

想要列出指定范围的记录,例如,从第 100 行至 105 行。

$ fc -l 100 105
100	 vim .bash_history
101	 man history
102	 man 1 history
103	 man -k history
104	 history
105	 history -h

我们也可以不使用行号,而是使用字符串。例如,想要列出从 export 命令开始至最近的记录,只需使用 export 命令的起始的几个字符,示例如下:

$ fc -l exp
181	 export | grep -i hist
182	 xcli
183	 sudo apt install xclip
184	 man xclip
185	 echo hello | xclip
186	 man bash
187	 fc -l
188	 help fc
189	 fc -lr
190	 fc -l
191	 fc -ln
192	 history
193	 fc -l 2
194	 fc -l 100
195	 fc -l 100 105
196	 history

想要查看在 export 和 man 之间的命令记录,您可以使用以下一种:

$ fc -l exp ma
181	 export | grep -i hist
182	 xcli
183	 sudo apt install xclip
184	 man xclip
185	 echo hello | xclip
186	 man bash

# 混合使用 行号和字符串
$ fc -l exp 186

# 都使用行号
$ fc -l 181 186

注意:使用字符串定位时,都是逆序搜索历史记录列表的,上面示例中,有两个 man 命令。

编辑并自动执行命令

在 shell 中执行命令,总是会遇到拼写错误情况,如果命令比较短,重新键入也可以,但是如果命令长而复杂,要在命令行上进行修改比较困难,这时应该是 fc 出场时候。

编辑并执行上一条命令

如果您刚刚错误拼写了一条命令。在这种情况下,您可以使用默认的编辑器轻松地修正该条命令的错误之处并执行,而无需重新输入。

想编辑上一条命令并再次执行它,只需:

fc

这时就会打开默认的编辑器并显示上一条命令。

在这里插入图片描述
正如您所见,最后一条命令 fc -l exp ma出现在里面。现在您可以在这条命令里进行任意的修改,且一旦您保存退出编辑器就会自动再次执行它。

编辑并执行指定命令

先从历史记录列表中找出想要编辑的命令,然后记住其编号。在以下示例中,使用编号 214。

$ fc -l 210
210	 fc -l
211	 sudo apt list rang*
212	 export FCEDIT=vim
213	 ranger
214	 echo hello fix command
215	 pwd
216	 cat /etc/issue
217	 cat /etc/os-release
218	 help fc
219	 fc -l
220	 cat /etc/os-release
221	 pwd
222	 echo hello fix command

此时只需使用以下命令就可以编辑第 214 条命令,该命令将在默认文件编辑器中打开等待编辑。

fc 214

修改完成后,保存并退出编辑器。修改后的命令自动执行。

也可以使用字符串指定最近执行过的命令,例如,要修改 export 那个命令,如下所示:

$ fc expo

后面的操作与前面一样。

编辑并执行多条命令

如果您认为 fc 一次只能编辑和执行一条前面的命令,那么您就错了。因为 fc 还允许编辑多个前面的命令!这有什么用呢?假设您要执行多条互相有关联的命令,结果在执行时次序不正确,这时使用该技巧比较适合。

  1. 首先您确定好要编辑的编号范围,例如 2 - 5。
$ fc -l 1 6
1	 hwclock
2	 sudo hwclock
3	 date; sudo hwclock
4	 man zdump
5	 zdump HongKong
6	 zdump china
  1. 执行以下命令
fc 2 5
  1. 这时会打开编辑器,您可以任意编辑这些命令,包括调整命令前后次序。

  2. 完成后,保存退出编辑器,就会自动执行编辑好的命令了。

重要提示:请注意,这也可能会有破坏性的。例如,如果上一条命令是类似于 rm -rf <some-path> 这种致命命令,退出编辑器后就自动执行,这样可能会丢失重要数据。因此,在使用 fc 命令是要格外小心。

指定用于编辑命令的默认编辑器

fc 的另一个重要选项是 e ,用于选择编辑命令的不同编辑器。例如,我们可以使用 “nano” 编辑器来编辑上一条命令,示例如下:

$ fc -e nano

该命令会打开 nano 编辑器(而不是默认编辑器)来编辑上一条命令。

您可能会发现每次使用时指定 -e 选项很费时间。要将您喜欢的编辑器设置成默认值,只需将环境变量 FCEDIT 设置为 fc 要使用的编辑器名称即可。

例如,想要设置 “vim” 成为新的默认编辑器,只需编辑您的 ~/.profile 或者环境文件。

$ vi ~/.profile

增加以下内容:

FCEDIT=vim
export FCEDIT

保存退出文件后,使用以下命令来更新这些修改:

$ source ~/.profile

现在,您可以仅键入 fc 使用 “vim” 编辑上一条命令。

不编辑直接重新执行前面的命令

我们已经知道如果执行不带参数的 fc ,它将打开编辑器来编辑最近的命令。有时,您可能不想编辑,只是简单地执行指定的命令。

直接执行上一条命令

第一种方法:在 fc -e 末尾使用连字符(-)符号,如下所示。

$ echo hello fix command
hello fix command

$ fc -e -
echo hello fix command
hello fix command

如您所见,即使使用了-e 选项,fc 也不会编辑上一条命令。

第二种方法:这种方法更简单,即使用 fc -s 直接执行上一条命令。

$ fc -s
直接执行指定的命令

这种方法的使用需要先确定好命令的编号,其余的操作非常简单,示例如下:

$ fc -s 5

这时就会打开编辑器,并把第 5 条命令显示在其中等待编辑。同样,也可以指定范围,一起执行。

对指定的命令替换后直接执行

有时,我们想要直接替换指定的命令后再次执行,例如,先验证一个文件,然后替换命令查看其内容。可以使用以下格式:

$ ll /etc/issue
-rw-r--r-- 1 root root 26 Aug 22 10:54 /etc/issue

$ fc -s ll=cat
cat /etc/issue

在该格式中的 “command” 部分,其功能与 前一个格式的 “first” 一样。

在 zsh 中显示命令执行时间

请注意,有些选项是特定于 shell 的。它们在其他 shell 中可能无效。例如,在 zsh shell 中可以使用以下选项。而在 Bash 或 Ksh shell 中出错。

想要查看历史记录中的命令何时执行,请使用 -d 选项,示例如下:

% fc -ld
    2  10:31  pwd
    3  10:31  ls
    4  10:32  echo hello fix command
    6  10:33  fc -lf
    7  10:34  fc -lE
    8  10:40  fc -d
    9  10:40  10:34  fc -lE

现在您看到最近执行的命令的执行时间。

我们也可以显示每个命令执行的完整时间戳,请使用 -f 选项。

% fc -fl
    2  10/18/2022 10:31  pwd
    3  10/18/2022 10:31  ls
    4  10/18/2022 10:32  echo hello fix command
    6  10/18/2022 10:33  fc -lf
    7  10/18/2022 10:34  fc -lE
    8  10/18/2022 10:40  fc -d
    9  10/18/2022 10:40  10:34  fc -lE
   10  10/18/2022 10:40  fc -ld

小结

从我理解来看,能够使用好这些历史记录确实可以在许多情况下提升工作效率。

如果只是想再次执行上一个命令,使用快捷键 ctrl+p 或 上方向键 比较好,原因是这个快捷键比较好记,p 是 previous ,同时上一个命令显示在命令行上,有修改的机会,还可以多按几次,则取得再前面的命令,当然 Ctrl+n 则作用相反。

如果前面执行的命令长而复杂,里面有拼写错误或需要修改,则在自己喜欢的文件编辑器中编辑肯定更好,那就使用 fc 命令。

如果想要执行前面执行过的命令,但是不知道编号,使用这个这种格式比较好。即 !str

如果想要执行历史记录中含有 string的,使用快捷键 ctrl+r 这种实时增量搜索比较直观。

如果想要复用前面命令的指定位置的参数,只能使用 history 中的相关格式。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值