Skip to content

04 VIM 编辑器与 Shell 命令脚本

  • 介绍如何使用 VIM 编辑器来编写和修改文档,然后通过逐步配置主机名称、系统网卡以及软件仓库等文件,帮助大家加深 VIM 编辑器中诸多命令、快捷键与模式的理解。
  • 在 Shell 脚本中以多种方式接收用户输入的信息,能够对输入值进行文件、数字、字符串的判断比较。在熟练使用 "与、或、非" 三种逻辑操作符的基础上,大家还要充分学习 ifforwhilecase 条件测试语句,并通过 10 多个实战脚本的实操练习,达到在工作中灵活运用的水准。
  • 通过实战的方式演示了使用 at 命令与 crond 计划任务服务来分别实现一次性的系统任务设置和长期性的系统任务设置,在分钟、小时、日期、月份、年份的基础上实现工作的自动化,从而让日常的工作更加高效。

VIM 文本编辑器

  • VIM 的发布:VIM 的发布最早可以追溯到 1991 年,英文全称为 Vi Improved。它也是 Vi 编辑器的提升版本,其中最大的改进当属添加了代码着色功能,在某些编程场景下还能自动修正错误代码。Vi 基于一个名为 Ex 的行编辑器。ViEx 都是由 Bill Joy 开发的。
  • 文本编辑器:在 Linux 系统中一切都是文件,而配置一个服务就是在修改其配置文件的参数。而且在日常工作中大家也肯定免不了要编写文档,这些工作都是通过文本编辑器来完成的。
  • 目的:让读者切实掌握 Linux 系统的运维方法,而不是仅仅停留在 "会用某个操作系统" 的层面上,我们这里选择使用 VIM 文本编辑器,它默认会安装在当前所有的 Linux 操作系统上,是一款超棒的文本编辑器。

VIM 编辑器的 3 种模式

  • 命令模式:控制光标移动,可对文本进行复制、粘贴、删除和查找等工作。普通模式Normal Mode)。这个命令在文档中是这样的::h Normal, :h mode-switching
  • 输入模式:正常的文本录入。插入模式Insert Mode)。这个命令在文档中是这样的::h Insert, :h i_<Esc>, :h o, :h O, :h a, :h A, :h I
  • 末行模式:保存或退出文档,以及设置编辑环境。 命令行模式Command-line Mode)。这个命令在文档中是这样的::h Command-line:h cmdline-lines

VIM 编辑器模式的切换方法

  • 在每次运行 VIM 编辑器时,默认进入命令模式,此时需要先切换到输入模式后再进行文档编写工作。
  • 每次在编写完文档后需要先返回命令模式,然后再进入末行模式,执行文档的保存或退出操作。
  • 在 VIM 中,无法直接从输入模式切换到末行模式。

命令模式中的命令

技巧

所有的命令都需要在 Normal 模式下使用,如果你不知道现在在什么样的模式,你就狂按几次 ESC 键。

命令模式中最常用的一些命令:

命令作用
i在光标所在位置插入文本,即输入模式。按 ESC 键回到 Normal 模式
x删除当前光标所在的一个字符。
dd删除(剪切)光标所在整行,并把删除的行存到剪贴板里
p将之前删除(dd)或复制(yy)过的数据粘贴到光标后面
5dd删除(剪切)从光标处开始的 5 行
yy复制光标所在整行
5yy复制从光标处开始的 5 行
n显示搜索命令定位到的下一个字符串
N显示搜索命令定位到的上一个字符串
u撤销上一步的操作
hjkl光标左下上右移动 (也可以使用光标键 ←↓↑→)
:help <command>查看某个命令的帮助文档,例如::help dd:help yy:help p(退出帮助需要输入 :q

各种插入模式

命令作用
i在光标所在位置插入文本,即输入模式。按 ESC 键回到 Normal 模式
o在当前行后插入一个新行
O在当前行前插入一个新行
a在光标后插入
cw替换从光标所在位置后到一个单词结尾的字符

移动光标

命令作用
hjkl光标左下上右移动 (也可以使用光标键 ←↓↑→)
0数字零,到行头
^到本行第一个不是 blank 字符的位置(所谓 blank 字符就是空格,tab,换行,回车等)
$到本行行尾
g_到本行最后一个不是 blank 字符的位置
/pattern搜索 pattern 的字符串(如果搜索出多个匹配,可按 n 键到下一个)
NG到第 N 行(例如 10G 到第 10 行)
gg到文档头部
G到文档尾部
w光标跳到下一个单词的开头 (词是由 blank 字符分隔符,那么你需要使用大写的 W)
e光标跳到下一个单词的结尾 (词是由 blank 字符分隔符,那么你需要使用大写的 E)
%匹配括号移动,包括 ({[ (你需要把光标先移到括号上)
*匹配光标当前所在的单词,并高亮显示其他相同单词,可按 n 键到下一个
#匹配光标当前所在的单词,并高亮显示其他相同单词,可按 N 键到上一个
fx查找光标后第一个为 x 的字符,并把光标移动到该位置
tx查找光标后第一个为 x 的字符,并把光标移动到该位置的前一个字符
Nfx查找光标后第 N 个为 x 的字符,并把光标移动到该位置
Fx查找光标前第一个为 x 的字符,并把光标移动到该位置
Tx查找光标前第一个为 x 的字符,并把光标移动到该位置的后一个字符

删除

命令作用
x删除当前光标所在的一个字符
dd删除(剪切)光标所在整行,并把删除的行存到剪贴板里
5dd删除(剪切)从光标处开始的 5 行
dw删除一个单词(不包括空格)
d$删除光标后至行尾的内容
d^删除光标前至行首的内容
dG删除光标所在行至文档末尾的内容
d1G删除光标所在行至文档开头的内容
d/pattern删除从光标所在行开始到匹配 pattern 为止的内容

拷贝粘贴

命令作用
p在当前位置之后粘贴
P在当前位置之前粘贴
yy拷贝当前行,相当于 ddP

撤销恢复

命令作用
u撤销上一步的操作,undo
Ctrl + r恢复上一步的操作,redo

末行模式中的命令

末行模式主要用于保存或退出文件,以及设置 VIM 编辑器的工作环境,还可以让用户执行外部的 Linux 命令或跳转到所编写文档的特定行数。要想切换到末行模式,在命令模式中输入一个冒号就可以了。

末行模式中最常用的一些命令:

命令作用
:e <文件名>打开一个文件
:w保存 (:w 后可以跟文件名)
:q退出
:q! (ZQ)强制退出(放弃对文档的修改内容)
:wq:x (ZZ)保存并退出
:wq!强制保存退出
:saveas <文件名>另存为
:set nu显示行号
:set nonu不显示行号
:命令执行该命令
:整数跳转到该行
:s/one/two将当前光标所在行的第一个 one 替换成 two
:s/one/two/g将当前光标所在行的所有 one 替换成 two
:%s/one/two/g将全文中的所有 one 替换成 two
?字符串在文本中从下至上搜索该字符串
/字符串在文本中从上至下搜索该字符串
:bn下一个文件 (同时打开很多文件)
:bp上一个文件 (同时打开很多文件)

注意

  • : 开始的命令你需要输入 <enter> 回车,例如 :q 也就是说你要输入 :q<enter> .

:x Vs :wq

  • :x 是另一个与 :wq 几乎相同的命令。根据 :h:x,它是 "类似于 :wq,但只在做出更改时写入" 。
  • 这里的区别在于,无论你是否对其进行了更改,:wq 将保存文件。如果你没有做任何更改,:x 将不会保存文件。这里重要的是文件的修改时间,因为 :wq 会更新修改时间,而 :x 不会。

ZZ & ZQ

  • Normal Mode 下,你也可以按 ZZZQ 来退出 VIM。其中中 ZZ:x 相同,ZQ:q! 相同。
  • ZZZQ 被认为是 Normal Mode 正常/命令模式 的命令,而 :x:q! 是 Ex 末行/命令行模式 的命令。

如果 :wq 失败了怎么办

这是可能的,因为当文件是只读的或者文件名丢失时,:wq 可能会失败。

请注意,当一个文件是只读的,VIM 不会阻止你打开和编辑该文件。你可能也会发现 :wq! 在那个时候也不能工作。你可能最终会放弃所有用 :q! 进行的修改,然后打开同一文件,以 sudo 为前缀,再做一次同样的修改。

  • 解决方案 1:

    • 把这个文件保存在另一个你有写权限的目录下,比如 ~ 或者甚至是 /tmp,然后把它移回你没有写入权限的目录。
    • 为了实现这一点,你可以运行 :w ~/my-backup-file,这将把该文件保存在 ~
    • 接下来,你可以使用 mv 命令移动这个文件。还要注意,当 VIM 抱怨文件名丢失时,你需要这样做。。
  • 解决方案 2:

    • 使用命令::w !sudo tee %。这意味着我们从 shell 环境中使用 sudo 命令,从 VIM 复制当前文件(注意:修改过的版本)的内容,并将修改过的内容重定向到文件(通过使用文件名引用)。。

      • !sudo 表示在 shell 环境中运行 sudo 命令。例如,:ls 将列出 VIM 中的所有缓冲区,但是是 :!ls 将从 shell 运行 ls 命令并显示结果。
      • tee 从标准输入(又名 stdin)复制到标准输出(又名 stdout)。
      • % 为当前文件名。
    • 上面命令会引发下图的警告。发生警告是因为在 shell 环境中更新了文件内容而没有引起 VIM 的注意。所以 VIM 认为这是一个外部变化,并让你知道发生了什么。此时只需按 Enter,因为你是有意进行外部更改的。

      img

    • 你可能还会注意到,修改后的文件内容也显示在警告消息的上方。这是意料之中的,因为它来自标准输出 stdout。如果你不想看到它,你可以执行 :w !sudo tee% > /dev/null,这将丢弃来自 teestdout

需要强制退出 VIM

  • 如果我们尝试按 Ctrl + c 退出,VIM 会显示下图信息。但你仍然可以通过按按 Ctrl + Alt + Delete 来解决这个问题。

    img

  • 下一次,当你试图再次打开同一个文件时,你应该看到这个(这里我用一个名为 foo.txt 的文件作为例子)。

    img

  • 此时 VIM 正试图帮助你恢复你可能丢失的宝贵修改。通过检查该目录,你会发现一个扩展名为 .swp 的文件。这是一个 swap 文件(文档::h swap-file)。

    img

  • 如果我们按 R 键恢复,我们将看到以下情况:

    img

  • Enter 键后,现在你会发现你之前所做的修改又回来了。在你完成恢复过程后,你可以简单地删除 .swp 文件,这样你就不会再看到上面的错误。

编写简单文档

img

步骤详细说明

  1. 打开 practice.txt 文档后,默认进入的是 Vim 编辑器的命令模式。此时只能执行该模式下的命令,而不能随意输入文本内容,我们需要切换到输入模式才可以编写文档。

  2. 可以分别使用 aio 三个键从命令模式切换到输入模式。

    • a 键是在光标后面一位切换到输入模式
    • i 键是在光标当前位置切换到输入模式
    • o 键则是在光标的下面再创建一个空行
  3. 进入输入模式后,可以随意输入文本内容,Vim 编辑器不会把您输入的文本内容当作命令而执行。

img

步骤详细说明

  1. 在编写完之后,要想保存并退出,必须先敲击键盘的 Esc 键从输入模式返回命令模式。
  2. 在末行模式中输入 :wq! 命令时,就意味着强制保存并退出文档。然后便可以用 cat 命令查看保存后的文档内容了。

img

img

步骤详细说明

  1. 在原有 practice.txt 文档的下面追加内容,所以在命令模式中敲击 o 键进入输入模式更会高效。
  2. 在编写完之后,想不保存直接退出,必须先敲击键盘的 Esc 键从输入模式返回命令模式。
  3. 在末行模式中输入 :q! 命令时,就意味着不保存直接退出文档。然后便可以用 cat 命令查看保存后的文档内容了。

img

bash
$ vim practice.txt
$ cat practice.txt
You can write in it.
$ vim practice.txt
$ cat -n practice.txt
     1  You can write in it.
$

配置主机名称

  • 第 1 步:使用 VIM 编辑器修改 /etc/hostname 主机名称文件。
  • 第 2 步:把原始主机名称删除后追加 linuxprobe.com。注意,使用 VIM 编辑器修改主机名称文件后,要在末行模式下执行 :wq! 命令才能保存并退出文档。
  • 第 3 步:保存并退出文档,然后使用 hostname 命令检查是否修改成功。

INFO

hostname 命令用于查看当前的主机名称,但有时主机名称的改变不会立即同步到系统中,所以如果发现修改完成后还显示原来的主机名称,可重启虚拟机后再行查看。

bash
[linuxprobe@linuxprobe ~]$ cat /etc/hostname
linuxprobe.com
[linuxprobe@linuxprobe ~]$ hostname
linuxprobe.com
[linuxprobe@linuxprobe ~]$

配置网卡信息

  • 第 1 步:首先切换到 /etc/sysconfig/network-scripts 目录中(存放着网卡的配置文件)。
  • 第 2 步:使用 VIM 编辑器修改网卡文件 ifcfg-ens160,逐项写入下面的配置参数并保存退出。由于每台设备的硬件及架构是不一样的,因此请读者使用 ifconfig 命令自行确认各自网卡的默认名称。
    • 设备类型TYPE=Ethernet
    • 地址分配模式BOOTPROTO=static
    • 网卡名称NAME=ens160
    • 是否启动ONBOOT=yes
    • IP 地址IPADDR=192.168.10.10
    • 子网掩码NETMASK=255.255.255.0
    • 网关地址GATEWAY=192.168.10.1
    • DNS 地址DNS1=192.168.10.1
  • 第 3 步:重启网络服务并测试网络是否连通。

INFO

由于在 Linux 系统中 ping 命令不会自动终止,因此需要手动按下 Ctrl + C 组合键来强行结束进程。

[!details]- 实验代码

bash
[linuxprobe@linuxprobe ~]$ su - root
Password:
[root@linuxprobe ~]# cd /etc/sysconfig/network-scripts/
[root@linuxprobe network-scripts]# vim ifcfg-ens160
[root@linuxprobe network-scripts]# cat -n ifcfg-ens160
     1  TYPE=Ethernet
     2  PROXY_METHOD=none
     3  BROWSER_ONLY=no
     4  BOOTPROTO=static
     5  DEFROUTE=yes
     6  IPV4_FAILURE_FATAL=no
     7  IPV6INIT=no
     8  IPV6_AUTOCONF=yes
     9  IPV6_DEFROUTE=yes
    10  IPV6_FAILURE_FATAL=no
    11  IPV6_ADDR_GEN_MODE=stable-privacy
    12  NAME=ens160
    13  UUID=2690b476-f0fe-4106-b694-1905067dc9f5
    14  DEVICE=ens160
    15  ONBOOT=yes
    16  HWADDR=00:0C:29:99:F4:B2
    17  IPADDR=192.168.0.131
    18  NERMASK=255.255.255.0
    19  GATEWAY=192.168.0.1
    20  DNS1=223.5.5.5
    21  DNS2=223.6.6.6
    22  PREFIX=24
    23  PEERROUTES=no

[root@linuxprobe network-scripts]# nmcli connection reload ens160 # 重启网卡设备
[root@linuxprobe network-scripts]# ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=2.19 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=2.10 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=1.49 ms
64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=5.13 ms
64 bytes from 192.168.0.1: icmp_seq=5 ttl=64 time=2.60 ms
^C
--- 192.168.0.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 11ms
rtt min/avg/max/mdev = 1.485/2.699/5.125/1.265 ms

配置软件仓库

  • 第 1 步:进入 /etc/yum.repos.d/ 目录中(因为该目录存放着软件仓库的配置文件)。
  • 第 2 步:使用 VIM 编辑器创建一个名为 rhel8.repo 的新配置文件(文件名称可随意,但后缀必须为 .repo),逐项写入下面的配置参数并保存退出。
    • 仓库名称:具有唯一性的标识名称,不应与其他软件仓库发生冲突。
    • 描述信息(name):可以是一些介绍性的词,易于识别软件仓库的用处。
    • 仓库位置(baseurl):软件包的获取方式,可以使用 FTP 或 HTTP 下载,也可以是本地的文件(需要在后面添加 file 参数)。
    • 是否启用(enabled):设置此源是否可用;1 为可用,0 为禁用。
    • 是否校验(gpgcheck):设置此源是否校验文件;1 为校验,0 为不校验。
    • 公钥位置(gpgkey):若上面的参数开启了校验功能,则此处为公钥文件位置。若没有开启,则省略不写。
  • 第 3 步:按配置参数中所填写的仓库位置挂载光盘,并把光盘挂载信息写入 /etc/fstab 文件中。
  • 第 4 步:使用 dnf install httpd -y 命令检查软件仓库是否已经可用。

[!details]- 实验代码

bash
[root@linuxprobe network-scripts]# cd ~
[root@linuxprobe ~]# cd /etc/yum.repos.d/
[root@linuxprobe yum.repos.d]# ls
redhat.repo
[root@linuxprobe yum.repos.d]# vim rhel8.repo
[root@linuxprobe yum.repos.d]# cat -n rhel8.repo
     1  [BaseOS]
     2  name=BaseOS
     3  baseurl=file:///media/cdrom/BaseOS
     4  enabled=1
     5  gpgcheck=0
     6  [AppStream]
     7  name=AppStream
     8  baseurl=file:///media/cdrom/AppStream
     9  enabled=1
    10  gpgcheck=0
[root@linuxprobe yum.repos.d]# mkdir -p /media/cdrom
[root@linuxprobe yum.repos.d]# mount /dev/cdrom /media/cdrom
mount: /media/cdrom: WARNING: device write-protected, mounted read-only.
[root@linuxprobe yum.repos.d]# vim /etc/fstab
[root@linuxprobe yum.repos.d]# cat -n /etc/fstab
     1
     2  #
     3  # /etc/fstab
     4  # Created by anaconda on Sun Jun  4 03:05:37 2023
     5  #
     6  # Accessible filesystems, by reference, are maintained under '/dev/disk/'.
     7  # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
     8  #
     9  # After editing this file, run 'systemctl daemon-reload' to update systemd
    10  # units generated from this file.
    11  #
    12  /dev/mapper/rhel-root   /                       xfs     defaults        0 0
    13  UUID=73159c7d-b179-4ef9-a83f-3f6f6ce390ed /boot                   xfs     defaults        0 0
    14  /dev/mapper/rhel-swap   swap                    swap    defaults        0 0
    15  /dev/cdrom /media/cdrom iso9660 defaults 0 0
[root@linuxprobe yum.repos.d]# dnf install httpd -y
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
Last metadata expiration check: 0:02:20 ago on Tue 06 Jun 2023 04:31:02 PM CST.
Dependencies resolved.
======================================================================================================
 Package                 Arch        Version                                     Repository      Size
======================================================================================================
Installing:
 httpd                   x86_64      2.4.37-21.module+el8.2.0+5008+cca404a3      AppStream      1.4 M
Installing dependencies:
 apr                     x86_64      1.6.3-9.el8                                 AppStream      125 k
 apr-util                x86_64      1.6.1-6.el8                                 AppStream      105 k
 httpd-filesystem        noarch      2.4.37-21.module+el8.2.0+5008+cca404a3      AppStream       36 k
 httpd-tools             x86_64      2.4.37-21.module+el8.2.0+5008+cca404a3      AppStream      103 k
 mod_http2               x86_64      1.11.3-3.module+el8.2.0+4377+dc421495       AppStream      158 k
 redhat-logos-httpd      noarch      81.1-1.el8                                  BaseOS          26 k
Installing weak dependencies:
 apr-util-bdb            x86_64      1.6.1-6.el8                                 AppStream       25 k
 apr-util-openssl        x86_64      1.6.1-6.el8                                 AppStream       27 k

Transaction Summary
======================================================================================================
Install  9 Packages

Total size: 2.0 M
Installed size: 5.5 M
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                              1/1
  Installing       : apr-1.6.3-9.el8.x86_64                                                       1/9
  Running scriptlet: apr-1.6.3-9.el8.x86_64                                                       1/9
  Installing       : apr-util-bdb-1.6.1-6.el8.x86_64                                              2/9
  Installing       : apr-util-openssl-1.6.1-6.el8.x86_64                                          3/9
  Installing       : apr-util-1.6.1-6.el8.x86_64                                                  4/9
  Running scriptlet: apr-util-1.6.1-6.el8.x86_64                                                  4/9
  Installing       : httpd-tools-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64                    5/9
  Running scriptlet: httpd-filesystem-2.4.37-21.module+el8.2.0+5008+cca404a3.noarch               6/9
  Installing       : httpd-filesystem-2.4.37-21.module+el8.2.0+5008+cca404a3.noarch               6/9
  Installing       : redhat-logos-httpd-81.1-1.el8.noarch                                         7/9
  Installing       : mod_http2-1.11.3-3.module+el8.2.0+4377+dc421495.x86_64                       8/9
  Installing       : httpd-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64                          9/9
  Running scriptlet: httpd-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64                          9/9
  Verifying        : redhat-logos-httpd-81.1-1.el8.noarch                                         1/9
  Verifying        : apr-1.6.3-9.el8.x86_64                                                       2/9
  Verifying        : apr-util-1.6.1-6.el8.x86_64                                                  3/9
  Verifying        : apr-util-bdb-1.6.1-6.el8.x86_64                                              4/9
  Verifying        : apr-util-openssl-1.6.1-6.el8.x86_64                                          5/9
  Verifying        : httpd-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64                          6/9
  Verifying        : httpd-filesystem-2.4.37-21.module+el8.2.0+5008+cca404a3.noarch               7/9
  Verifying        : httpd-tools-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64                    8/9
  Verifying        : mod_http2-1.11.3-3.module+el8.2.0+4377+dc421495.x86_64                       9/9
Installed products updated.

Installed:
  apr-1.6.3-9.el8.x86_64
  apr-util-1.6.1-6.el8.x86_64
  apr-util-bdb-1.6.1-6.el8.x86_64
  apr-util-openssl-1.6.1-6.el8.x86_64
  httpd-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64
  httpd-filesystem-2.4.37-21.module+el8.2.0+5008+cca404a3.noarch
  httpd-tools-2.4.37-21.module+el8.2.0+5008+cca404a3.x86_64
  mod_http2-1.11.3-3.module+el8.2.0+4377+dc421495.x86_64
  redhat-logos-httpd-81.1-1.el8.noarch

Complete!
[root@linuxprobe yum.repos.d]#

编写 Shell 脚本

可以将 Shell 终端解释器当作人与计算机硬件之间的 "翻译官",它作为用户与 Linux 系统内部的通信媒介,除了能够支持各种变量与参数外,还提供了诸如循环、分支等高级编程语言才有的控制结构特性。

Shell 脚本命令的工作方式有下面两种:

  • 交互式(Interactive):用户每输入一条命令就立即执行。
  • 批处理(Batch):由用户事先编写好一个完整的 Shell 脚本,Shell 会一次性执行脚本中诸多的命令。

编写简单的脚本

INFO

Shell 脚本文件的名称可以任意,但为了避免被误以为是普通文件,建议将.sh 后缀加上,以表示是一个脚本文件。

bash
$ vim example.sh
$ cat -n example.sh
   1  #!/bin/bash
   2  #For Example BY linuxprobe.com
   3  pwd
   4  ls -al

在上面的这个 example.sh 脚本中实际上出现了 3 种不同的元素:

  • 第一行的脚本声明(#!)用来告诉系统使用哪种 Shell 解释器来执行该脚本;
  • 第二行的注释信息(#)是对脚本功能和某些命令的介绍信息,使得自己或他人在日后看到这个脚本内容时,可以快速知道该脚本的作用或一些警告信息;
  • 第三、四行的可执行语句也就是我们平时执行的 Linux 命令了。

[!details]- 实验代码

bash
[root@linuxprobe ~]# logout
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2  #For Example BY linuxprobe.com
     3  pwd
     4  ls -al
[linuxprobe@linuxprobe ~]$ bash example.sh
/home/linuxprobe
total 44
drwx------. 15 linuxprobe linuxprobe 4096 Jun  6 16:36 .
drwxr-xr-x.  3 root       root         24 Jun  4 03:15 ..
-rw-------.  1 linuxprobe linuxprobe  389 Jun  5 04:40 .bash_history
-rw-r--r--.  1 linuxprobe linuxprobe   18 Aug 30  2019 .bash_logout
-rw-r--r--.  1 linuxprobe linuxprobe  141 Aug 30  2019 .bash_profile
-rw-r--r--.  1 linuxprobe linuxprobe  312 Aug 30  2019 .bashrc
drwx------. 10 linuxprobe linuxprobe  232 Jun  4 03:27 .cache
drwx------. 12 linuxprobe linuxprobe  229 Jun  4 14:29 .config
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Desktop
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Documents
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Downloads
-rw-------.  1 linuxprobe linuxprobe   16 Jun  4 03:26 .esd_auth
-rw-rw-r--.  1 linuxprobe linuxprobe   57 Jun  6 16:36 example.sh
-rw-------.  1 linuxprobe linuxprobe  930 Jun  4 04:26 .ICEauthority
drwx------.  3 linuxprobe linuxprobe   19 Jun  4 03:26 .local
drwxr-xr-x.  4 linuxprobe linuxprobe   39 Jun  4 03:06 .mozilla
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Music
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Pictures
drwxrw----.  3 linuxprobe linuxprobe   19 Jun  4 03:26 .pki
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Public
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Templates
drwxr-xr-x.  2 linuxprobe linuxprobe    6 Jun  4 03:26 Videos
-rw-------.  1 linuxprobe linuxprobe 9540 Jun  6 16:36 .viminfo

接收用户的参数

Shell 脚本程序中的参数位置变量

Linux 系统中的 Shell 脚本语言已经内设了用于接收参数的变量,变量之间使用空格间隔。

  • $0 对应的是当前 Shell 脚本程序的名称
  • $# 对应的是总共有几个参数
  • $* 对应的是所有位置的参数值
  • $? 对应的是显示上一次命令的执行返回值
  • $1$2$3……则分别对应着第 N 个位置的参数值

判断用户的参数

条件测试语句的执行格式

Shell 脚本中的条件测试语法可以判断表达式是否成立,若条件成立则返回数字 0,否则便返回非零值。

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2  echo "当前脚本名称为 $0"
     3  echo "总共有 $# 个参数,分别是 $*。"
     4  echo "第 1 个参数为 $1,第 5 个为 $5。"
[linuxprobe@linuxprobe ~]$ bash example.sh one two three four five six
当前脚本名称为 example.sh
总共有 6 个参数,分别是 one two three four five six。
 1 个参数为 one,第 5 个为 five。

切记

条件表达式两边均应有一个空格。

按照测试对象来划分,条件测试语句可以分为 4 种:

  • 文件测试语句;
  • 逻辑测试语句;
  • 整数值比较语句;
  • 字符串比较语句。

文件测试语句

文件测试所用的参数:

操作符作用
-d测试文件是否为目录类型
-e测试文件是否存在
-f判断是否为一般文件
-r测试当前用户是否有权限读取
-w测试当前用户是否有权限写入
-x测试当前用户是否有权限执行

[!details]- 实验代码

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/usr/bin/env bash
     2
     3  # 判断文件是否存在
     4  if [ -e "/etc/passwd" ]
     5  then
     6      echo "'/etc/passwd' file exists"
     7  else
     8      echo "'/etc/passwd' file not exists"
     9  fi
    10
    11  # 判断文件是否为目录
    12  if [ -d "/home/linuxprobe/Desktop" ]
    13  then
    14      echo "'/home/linuxprobe/Desktop' is a directory"
    15  else
    16      echo "'/home/linuxprobe/Desktop' is not a directory"
    17  fi
    18
    19  # 判断文件是否为空
    20  touch /tmp/test
    21  if [ -s "/tmp/test" ]
    22  then
    23      echo "'/tmp/test' is not empty"
    24  else
    25      echo "'/tmp/test' is empty"
    26  fi
    27
    28  # 判断文件是否可读
    29  if [ -r "/etc/passwd" ]
    30  then
    31      echo "'/etc/passwd' is readable"
    32  else
    33      echo "'/etc/passwd' is not readable"
    34  fi
    35
    36  # 判断文件是否为一般文件
    37  if [ -f "/dev/sda" ]
    38  then
    39      echo "'/dev/sda' is a normal file"
    40  else
    41      echo "'/dev/sda' is not a normal file"
    42  fi
    43
    44  # 判断文件是否可写
    45  if [ -w "/etc/passwd" ]
    46  then
    47      echo "'/etc/passwd' is writable"
    48  else
    49      echo "'/etc/passwd' is not writable"
    50  fi
    51
    52  # 判断文件是否可执行
    53  if [ -x "/etc/passwd" ]
    54  then
    55      echo "'/etc/passwd' is executable"
    56  else
    57      echo "'/etc/passwd' is not executable"
    58  fi
[linuxprobe@linuxprobe ~]$ bash example.sh
'/etc/passwd' file exists
'/home/linuxprobe/Desktop' is a directory
'/tmp/test' is empty
'/etc/passwd' is readable
'/dev/sda' is not a normal file
'/etc/passwd' is not writable
'/etc/passwd' is not executable
[linuxprobe@linuxprobe ~]$

逻辑测试语句

  • && 是逻辑 ,只有当前面的语句执行成功的时候才会执行后面的语句。
  • || 是逻辑 ,只有当前面的语句执行失败的时候才会执行后面的语句。
  • ! 是逻辑 ,代表对逻辑测试结果取反值;之前若为正确则变成错误,若为错误则变成正确。

[!details]- 实验代码

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2  # 判断文件是否存在,如果存在则输出文件名,否则输出不存在
     3  FILE="example.txt"
     4  touch $FILE
     5  if [ -e $FILE ] || [ -e "example2.txt" ]
     6  then
     7      echo "$FILE or example2.txt exists"
     8  else
     9      echo "$FILE and example2.txt does not exist"
    10  fi
    11
[linuxprobe@linuxprobe ~]$ bash example.sh
example.txt or example2.txt exists
[linuxprobe@linuxprobe ~]$

[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ ls
Desktop  Documents  Downloads  example.sh  example.txt  Music  Pictures  Public  Templates  Videos
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2  # 判断文件是否存在,如果存在则输出文件名,否则输出不存在
     3  FILE="example.txt"
     4  touch $FILE
     5  if [ -e $FILE ] && [ -e "example2.txt" ]; then
     6      echo "$FILE and example2.txt exist"
     7  else
     8      echo "$FILE or example2.txt does not exist"
     9  fi
    10
[linuxprobe@linuxprobe ~]$ bash example.sh
example.txt or example2.txt does not exist
[linuxprobe@linuxprobe ~]$ touch example2.txt
[linuxprobe@linuxprobe ~]$ bash example.sh
example.txt and example2.txt exist
[linuxprobe@linuxprobe ~]$

[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2  # 判断文件是否不存在,如果不存在则输出不存在,否则输出文件名
     3  FILE="example.txt"
     4  if ! [ -e $FILE ]; then
     5      echo "$FILE does not exist"
     6  else
     7      echo "$FILE exists"
     8  fi
     9
[linuxprobe@linuxprobe ~]$ rm example2.txt
[linuxprobe@linuxprobe ~]$ ls
Desktop  Documents  Downloads  example.sh  example.txt  Music  Pictures  Public  Templates  Videos
[linuxprobe@linuxprobe ~]$ bash example.sh
example.txt exists
[linuxprobe@linuxprobe ~]$ rm example.txt
[linuxprobe@linuxprobe ~]$ bash example.sh
example.txt does not exist
[linuxprobe@linuxprobe ~]$

整数值比较语句

可用的整数比较运算符:

操作符作用
-eq是否等于
-ne是否不等于
-gt是否大于
-lt是否小于
-le是否等于或小于
-ge是否大于或等于
  • -eq:等于(equal to)
  • -ne:不等于(not equal to)
  • -gt:大于(greater than)
  • -lt:小于(less than)
  • -le:小于等于(less than or equal to)
  • -ge:大于等于(greater than or equal to)
bash
[linuxprobe@linuxprobe ~]$ free -m
              total        used        free      shared  buff/cache   available
Mem:           1960        1436         135          20         388         344
Swap:          2047         146        1901
[linuxprobe@linuxprobe ~]$ FreeMem=`free -m | grep Mem: | awk '{print $4}'`
[linuxprobe@linuxprobe ~]$ [ $FreeMem -lt 1024 ] && echo "Insufficient Memory"
Insufficient Memory
[linuxprobe@linuxprobe ~]$

[!details]- 实验代码

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2
     3  # 定义两个整数变量
     4  a=10
     5  b=20
     6
     7  # 使用 -eq 比较语句判断 a 是否等于 b
     8  if [ $a -eq $b ]
     9  then
    10      echo "a 等于 b"
    11  else
    12      echo "a 不等于 b"
    13  fi
    14
    15  # 使用 -ne 比较语句判断 a 是否不等于 b
    16  if [ $a -ne $b ]
    17  then
    18      echo "a 不等于 b"
    19  else
    20      echo "a 等于 b"
    21  fi
    22
    23  # 使用 -gt 比较语句判断 a 是否大于 b
    24  if [ $a -gt $b ]
    25  then
    26      echo "a 大于 b"
    27  else
    28      echo "a 不大于 b"
    29  fi
    30
    31  # 使用 -lt 比较语句判断 a 是否小于 b
    32  if [ $a -lt $b ]
    33  then
    34      echo "a 小于 b"
    35  else
    36      echo "a 不小于 b"
    37  fi
    38
    39  # 使用 -le 比较语句判断 a 是否小于等于 b
    40  if [ $a -le $b ]
    41  then
    42      echo "a 小于等于 b"
    43  else
    44      echo "a 大于 b"
    45  fi
    46
    47  # 使用 -ge 比较语句判断 a 是否大于等于 b
    48  if [ $a -ge $b ]
    49  then
    50      echo "a 大于等于 b"
    51  else
    52      echo "a 小于 b"
    53  fi
    54
[linuxprobe@linuxprobe ~]$ bash example.sh
a 不等于 b
a 不等于 b
a 不大于 b
a 小于 b
a 小于等于 b
a 小于 b
[linuxprobe@linuxprobe ~]$

字符串比较语句

常见的字符串比较运算符

操作符作用
=比较字符串内容是否相同
!=比较字符串内容是否不同
-z判断字符串内容是否为空
bash
[linuxprobe@linuxprobe ~]$ echo $LANG
en_US.UTF-8
[linuxprobe@linuxprobe ~]$ [ ! $LANG = "en.US" ] && echo "Not en.US"
Not en.US
[linuxprobe@linuxprobe ~]$

[!details]- 实验代码

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2  str1="hello"
     3  str2="world"
     4  str=""
     5
     6  if [ $str1 = $str2 ]; then
     7      echo "$str1 = $str2"
     8  else
     9      echo "$str1 != $str2"
    10  fi
    11
    12  if [ $str1 != $str2 ]; then
    13      echo "$str1 != $str2"
    14  else
    15      echo "$str1 = $str2"
    16  fi
    17
    18  if [ -z "$str" ]; then
    19      echo "$str 字符串为空"
    20  else
    21      echo "$str 字符串不为空"
    22  fi
    23
[linuxprobe@linuxprobe ~]$ bash example.sh
hello != world
hello != world
 字符串为空
[linuxprobe@linuxprobe ~]$

流程控制语句

  • 尽管此时可以通过使用 Linux 命令、管道符、重定向以及条件测试语句来编写最基本的 Shell 脚本,但是这种脚本并不适用于生产环境。
  • 原因是它不能根据真实的工作需求来调整具体的执行命令,也不能根据某些条件实现自动循环执行。通俗来讲,就是不能根据实际情况做出调整。
  • 通常脚本都是从上到下一股脑儿地执行,效率是很高,但一旦某条命令执行失败了,则后面的功能全都会受到影响。

if 条件测试语句

if 条件测试语句可以让脚本根据实际情况自动执行相应的命令。从技术角度来讲,if 语句分为单分支结构、双分支结构、多分支结构;其复杂度随着灵活度一起逐级上升。

单分支的 if 条件语句

if 条件语句的单分支结构由 ifthenfi 关键词组成,而且只在条件成立后才执行预设的命令,相当于口语的 如果……那么……

单分支的 if 条件语句

bash
[linuxprobe@linuxprobe ~]$ vim mkshell.sh
[linuxprobe@linuxprobe ~]$ cat -n mkshell.sh
     1  #!/bin/bash
     2  DIR="/home/linuxprobe/shell"
     3  if [ ! -d $DIR ]; then
     4      mkdir -p $DIR
     5  fi
     6
     7  ls -ld $DIR
     8
[linuxprobe@linuxprobe ~]$ bash mkshell.sh
drwxrwxr-x. 2 linuxprobe linuxprobe 6 Jun 11 14:27 /home/linuxprobe/shell
[linuxprobe@linuxprobe ~]$

双分支的 if 条件语句

if 条件语句的双分支结构由 ifthenelsefi 关键词组成,它进行一次条件匹配判断,如果与条件匹配,则去执行相应的预设命令;反之则去执行不匹配时的预设命令,相当于口语的 如果……那么……或者……那么……

双分支的 if 条件语句

bash
[linuxprobe@linuxprobe ~]$ vim chkhost.sh
[linuxprobe@linuxprobe ~]$ cat -n chkhost.sh
     1  #!/bin/bash
     2  ping -c 3 -i 0.2 -W 3 $1 &>/dev/null
     3  if [ $? -eq 0 ]; then
     4      echo "Host $1 is On-line."
     5  else
     6      echo "Host $1 is Off-line."
     7  fi
     8
[linuxprobe@linuxprobe ~]$ bash chkhost.sh 192.168.0.1
Host 192.168.0.1 is On-line.
[linuxprobe@linuxprobe ~]$ bash chkhost.sh 192.168.0.100
Host 192.168.0.100 is Off-line.
[linuxprobe@linuxprobe ~]$

多分支的 if 条件语句

if 条件语句的多分支结构由 ifthenelseeliffi 关键词组成,它进行多次条件匹配判断,这多次判断中的任何一项在匹配成功后都会执行相应的预设命令,相当于口语的 如果……那么……如果……那么……

多分支的 if 条件语句

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2
     3  read -r -p "请输入一个数字: " num
     4
     5  if [ "$num" -gt 0 ]; then
     6      echo "输入的数字是正数"
     7  fi
     8
     9  num=10
    10
    11  if [ "$num" -gt 10 ]; then
    12      echo "$num 大于10"
    13  else
    14      echo "$num 小于或等于10"
    15  fi
    16
    17  num=1
    18
    19  if [ $num -gt 10 ]; then
    20      echo "$num 大于10"
    21  elif [ $num -eq 10 ]; then
    22      echo "$num 等于10"
    23  else
    24      echo "$num 小于10"
    25  fi
    26
[linuxprobe@linuxprobe ~]$ bash example.sh
请输入一个数字: 100
输入的数字是正数
10 小于或等于10
1 小于10
[linuxprobe@linuxprobe ~]$

for 条件循环语句

  • for 循环语句允许脚本一次性读取多个信息,然后逐一对信息进行操作处理。
  • 当要处理的数据有范围时,使用 for 循环语句就再适合不过了。

for 范围循环语句

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2
     3  # 定义一个数组
     4  arr=("apple" "banana" "orange" "grape")
     5
     6  # 使用 for 循环遍历数组中的元素
     7  for fruit in "${arr[@]}"; do
     8      echo "I love ${fruit}s"
     9  done
    10
    11  echo ""
    12
    13  for i in {1..5}; do
    14      echo "num is $i"
    15  done
    16
[linuxprobe@linuxprobe ~]$ bash example.sh
I love apples
I love bananas
I love oranges
I love grapes

num is 1
num is 2
num is 3
num is 4
num is 5
[linuxprobe@linuxprobe ~]$
bash
[linuxprobe@linuxprobe ~]$ su - root
Password:
[root@linuxprobe ~]# cd /home/linuxprobe/
[root@linuxprobe linuxprobe]# cat -n addusers.sh
     1  #!/bin/bash
     2  read -r -p "Enter The Users Password : " PASSWD
     3  arr=("andy" "barry" "carl" "duke" "eric" "linuxprobe")
     4  for UNAME in "${arr[@]}"; do
     5    # id "$UNAME" &> /dev/null
     6    # if [ $? -eq 0 ]
     7    if ! id "$UNAME" &>/dev/null; then
     8      echo "$UNAME , Already exists"
     9    else
    10      useradd "$UNAME"
    11      echo "$PASSWD" | passwd --stdin "$UNAME" &>/dev/null
    12      echo "$UNAME , Create success"
    13    fi
    14  done
    15
[root@linuxprobe linuxprobe]# bash addusers.sh
Enter The Users Password : linuxprobe
andy , Create success
barry , Create success
carl , Create success
duke , Create success
eric , Create success
useradd: user 'linuxprobe' already exists
[root@linuxprobe linuxprobe]# tail -6 /etc/passwd
linuxprobe:x:1000:1000:linuxprobe:/home/linuxprobe:/bin/bash
andy:x:1001:1001::/home/andy:/bin/bash
barry:x:1002:1002::/home/barry:/bin/bash
carl:x:1003:1003::/home/carl:/bin/bash
duke:x:1004:1004::/home/duke:/bin/bash
eric:x:1005:1005::/home/eric:/bin/bash

while 条件循环语句

  • while 条件循环语句是一种让脚本根据某些条件来重复执行命令的语句,它的循环结构往往在执行前并不确定最终执行的次数,完全不同于 for 循环语句中有目标、有范围的使用场景。
  • while 循环语句通过判断条件测试的真假来决定是否继续执行命令,若条件为真就继续执行,为假就结束循环。

while 条件循环语句

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2
     3  # 使用 while 循环输出 0 到 5
     4  count=0
     5  while [ $count -lt 5 ]; do
     6      echo "count is $count"
     7      count=$((count + 1))
     8  done
     9
    10  # 使用 while 循环计算 1 到 100 的和
    11  i=1
    12  sum=0
    13  while [ $i -le 100 ]; do
    14      sum=$((sum + i))
    15      i=$((i + 1))
    16  done
    17
    18  echo "The sum of 1 to 100 is: $sum"
    19
[linuxprobe@linuxprobe ~]$ bash example.sh
count is 0
count is 1
count is 2
count is 3
count is 4
The sum of 1 to 100 is: 5050
[linuxprobe@linuxprobe ~]$

case 条件测试语句

  • case 条件测试语句和 switch 语句的功能非常相似!
  • case 语句是在多个范围内匹配数据,若匹配成功则执行相关命令并结束整个条件测试。
  • 如果数据不在所列出的范围内,则会去执行星号(*)中所定义的默认命令。

case 条件测试语句

bash
[linuxprobe@linuxprobe ~]$ vim example.sh
[linuxprobe@linuxprobe ~]$ cat -n example.sh
     1  #!/bin/bash
     2
     3  echo "请输入1-3之间的数字:"
     4  read -r num
     5
     6  case $num in
     7  1)
     8      echo "你输入的是1"
     9      ;;
    10  2)
    11      echo "你输入的是2"
    12      ;;
    13  3)
    14      echo "你输入的是3"
    15      ;;
    16  *)
    17      echo "输入错误"
    18      ;;
    19  esac
    20
[linuxprobe@linuxprobe ~]$ bash example.sh
请输入1-3之间的数字:
3
你输入的是3
[linuxprobe@linuxprobe ~]$ bash example.sh
请输入1-3之间的数字:
6
输入错误
[linuxprobe@linuxprobe ~]$
bash
[linuxprobe@linuxprobe ~]$ vim Checkkeys.sh
[linuxprobe@linuxprobe ~]$ cat -n Checkkeys.sh
     1  #!/bin/bash
     2  read -r -p "请输入一个字符,并按Enter键确认:" KEY
     3  case "$KEY" in
     4    [a-z] | [A-Z])
     5      echo "您输入的是 字母。"
     6      ;;
     7    [0-9])
     8      echo "您输入的是 数字。"
     9      ;;
    10    *)
    11      echo "您输入的是 空格、功能键或其他控制字符。"
    12      ;;
    13  esac
    14
[linuxprobe@linuxprobe ~]$ bash Checkkeys.sh
请输入一个字符,并按Enter键确认:y
您输入的是 字母。
[linuxprobe@linuxprobe ~]$ bash Checkkeys.sh
请输入一个字符,并按Enter键确认:[
您输入的是 空格、功能键或其他控制字符。
[linuxprobe@linuxprobe ~]$ bash Checkkeys.sh
请输入一个字符,并按Enter键确认:
您输入的是 空格、功能键或其他控制字符。
[linuxprobe@linuxprobe ~]$ bash Checkkeys.sh
请输入一个字符,并按Enter键确认:9
您输入的是 数字。
[linuxprobe@linuxprobe ~]$

计划任务服务程序

计划任务分为一次性计划任务与长期性计划任务。

  • 一次性计划任务:今晚 23:30 重启网站服务。
  • 长期性计划任务:每周一的凌晨 3:25 把 /home/wwwroot 目录打包备份为 backup.tar.gz

一次性计划任务

  • 一次性计划任务只执行一次,一般用于临时的工作需求。可以用 at 命令实现这种功能,只需要写成 at 时间 的形式就行。
  • 如果想要查看已设置好但还未执行的一次性计划任务,可以使用 at -l 命令。
  • 要想将其删除,可以使用 atrm 任务序号
bash
[linuxprobe@linuxprobe ~]$ at 23:30
warning: commands will be executed using /bin/sh
at> systemctl restart httpd
at> <EOT> # Ctrl + d 键来结束编写计划任务
job 1 at Sun Jun 11 23:30:00 2023
[linuxprobe@linuxprobe ~]$ at -l
1       Sun Jun 11 23:30:00 2023 a linuxprobe
[linuxprobe@linuxprobe ~]$

[linuxprobe@linuxprobe ~]$ echo "systemctl restart httpd" | at 23:30
warning: commands will be executed using /bin/sh
job 2 at Sun Jun 11 23:30:00 2023
[linuxprobe@linuxprobe ~]$ at -l
1       Sun Jun 11 23:30:00 2023 a linuxprobe
2       Sun Jun 11 23:30:00 2023 a linuxprobe
[linuxprobe@linuxprobe ~]$ atrm 2 # 使用 atrm 命令轻松删除计划任务
[linuxprobe@linuxprobe ~]$ at -l
1       Sun Jun 11 23:30:00 2023 a linuxprobe
[linuxprobe@linuxprobe ~]$

[linuxprobe@linuxprobe ~]$ echo "systemctl restart httpd" | at now +2 MINUTE # 表示 2 分钟(MINUTE)后执行这个任务,也可以将其替代成小时(HOUR)、日(DAY)、月(MONTH)等
warning: commands will be executed using /bin/sh
job 3 at Sun Jun 11 14:49:00 2023
[linuxprobe@linuxprobe ~]$ at -l
1       Sun Jun 11 23:30:00 2023 a linuxprobe
3       Sun Jun 11 14:49:00 2023 a linuxprobe

at 命令的参数及其作用

参数作用
-f指定包含命令的任务文件
-q指定新任务名称
-l显示待执行任务列表
-d删除指定待执行任务
-m任务执行后给用户发邮件

长期性计划任务

还有些时候,我们希望 Linux 系统能够周期性地、有规律地执行某些具体的任务,那么 Linux 系统中默认启用的 crond 服务简直再适合不过了。

  • 创建、编辑计划任务的命令为 crontab -e
  • 查看当前计划任务的命令为 crontab -l
  • 删除某条计划任务的命令为 crontab -r
  • 如果您是以管理员的身份登录的系统,还可以在 crontab 命令中加上 -u 参数来编辑他人的计划任务。
bash
[linuxprobe@linuxprobe ~]$ crontab -e # 使用 rontab -e 命令来创建计划任务
no crontab for linuxprobe - using an empty one
crontab: installing new crontab
[linuxprobe@linuxprobe ~]$ crontab -l
25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot
[linuxprobe@linuxprobe ~]$
[linuxprobe@linuxprobe ~]$ crontab -e
crontab: installing new crontab
[linuxprobe@linuxprobe ~]$ crontab -l
25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot
0  1 * * 1-5   /usr/bin/rm -rf /tmp/*
[linuxprobe@linuxprobe ~]$ crontab -r # 使用 crontab -r 命令删除计划任务
[linuxprobe@linuxprobe ~]$ crontab -l
no crontab for linuxprobe
[linuxprobe@linuxprobe ~]$

crontab 命令的参数及其作用

参数作用
-e编辑计划任务
-u指定用户名称
-l列出任务列表
-r删除计划任务

使用 crond 设置任务的参数格式

使用 crond 设置任务的参数格式

使用 crond 设置任务的参数字段说明

字段说明
分钟取值为 0 ~ 59 的整数
小时取值为 0 ~ 23 的任意整数
日期取值为 1 ~ 31 的任意整数
月份取值为 1 ~ 12 的任意整数
星期取值为 0 ~ 7 的任意整数,其中 0 与 7 均为星期日
命令要执行的命令或程序脚本

使用计划服务的注意事项

  • crond 服务的配置参数中,一般会像 Shell 脚本那样以 # 号开头写上注释信息,这样在日后回顾这段命令代码时可以快速了解其功能、需求以及编写人员等重要信息。
  • 计划任务中的 字段必须有数值,绝对不能为空或是 * 号,而 星期 字段不能同时使用,否则就会发生冲突。

删除 crond 计划任务

  • 直接使用 crontab -e 命令进入编辑界面,删除里面的文本信息即可。
  • 使用 crontab -r 命令直接进行删除。

总结

[!question]- VIM 编辑器的 3 种模式分别是什么? 命令模式、末行模式与输入模式(也叫编辑模式或插入模式)。

[!question]- 怎么从输入模式切换到末行模式? 需要先敲击 Esc 键退回到命令模式,然后敲击冒号(:)键后进入末行模式。

[!question]- 一个完整的 Shell 脚本应该包含哪些内容? 应该包括脚本声明、注释信息和可执行语句(即命令)。

[!question]- 分别解释 Shell 脚本中 $0$3 变量的作用。 在 Shell 脚本中,$0 代表脚本文件的名称,$3 则代表该脚本在执行时接收的第 3 个参数。

[!question]- if 条件测试语句有几种结构,最灵活且最复杂的是哪种结构? if 条件测试语句包括单分支、双分支与多分支等 3 种结构,其中多分支结构是最灵活且最复杂的结构,其结构形式为 if…then…elif…then…else…fi

[!question]- for 条件循环语句的循环结构是什么样子的? for 条件循环语句的结构为 for 变量名 in 取值列表 do 命令序列 done

[!question]- 若在 while 条件循环语句中使用 true 作为循环条件,那么会发生什么事情? 由于条件测试值永久为 true,因此脚本中的循环部分会无限地重复执行下去,直到碰到 exit 命令才会结束。

[!question]- 如果需要依据用户的输入参数执行不同的操作,最方便的条件测试语句是什么? case 条件语句。

[!question]- Linux 系统的长期计划任务所使用的服务是什么,其参数格式是什么? 长期计划任务需要使用 crond 服务程序,参数格式是 分、时、日、月、星期 命令