首页 Tech linux 正则表达式和 shell 脚本基础

linux 正则表达式和 shell 脚本基础

作者 Worthify 2965 字 · 14 分钟阅读# Tech
linux 正则表达式和 shell 脚本基础
伴乐 linux 正则表达式和 shell 脚本基础
0:00 / 0:00

正则表达式的应用

在 grep 中的使用正则表达式

grep 命令在 Linux 系统中是一个用于查找文本内容的命令,其中为了更好更加方便地定位文本内容,grep 命令中支持正则表达式查找文本。

在正式讲正则表达式之前,我们先讲一下 grep 的进阶用法。

例如,我们需要查找在 /etc/passwd 文件中 shibo 用户所在哪一行。我们可以使用 -n 选项。

SHELL
shibo@DESKTOP-U7FRP5M:~/spiit$ grep -n shibo /etc/passwd
27:shibo:x:1000:1000:,,,:/home/shibo:/bin/bash

当使用 -n 选项后,输出的内容前会出现该行所在文档中所处的行数,这样我们就可以更加方便的定位数据。

有时候我们需要查看开机自检信息的内容,使用 dmesg 命令可以查看开机自检的内容。由于直接使用 dmesg 命令输出的内容太多,我们需要使用 grep 命令搜索我们需要的内容。

CODE
shibo@DESKTOP-U7FRP5M:~/spiit$ dmesg
[    0.000000] Linux version 5.15.133.1-microsoft-standard-WSL2 (root@1c602f52c2e4) (gcc (GCC) 11.2.0, GNU ld (GNU Binutils) 2.37) #1 SMP Thu Oct 5 21:02:42 UTC 2023
[    0.000000] Command line: initrd=\initrd.img WSL_ROOT_INIT=1 panic=-1 nr_cpus=8 bonding.max_bonds=0 dummy.numdummies=0 fb_tunnels=none console=hvc0 debug pty.legacy_count=0
[    0.000000] KERNEL supported cpus:
[    0.000000]   Intel GenuineIntel
[    0.000000]   AMD AuthenticAMD
————————————以下内容省略————————————

现在我们需要输出我们系统中 eth0 网卡的相关内容。

CODE
shibo@DESKTOP-U7FRP5M:~/spiit$ dmesg | grep -n eth0
390:[    6.888925] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
443:[   79.586230] TCP: eth0: Driver has suspect GRO implementation, TCP performance may be compromised.

两条记录分别位于 390 行和 443 行中,为了更好的了解这两行的具体意思,我们需要输出包括这几行的前 4 行和后 4 行。

CODE
shibo@DESKTOP-U7FRP5M:~/spiit$ dmesg | grep -n eth0
390:[    6.888925] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
443:[   79.586230] TCP: eth0: Driver has suspect GRO implementation, TCP performance may be compromised.
shibo@DESKTOP-U7FRP5M:~/spiit$ dmesg | grep -n -A 3 -B 4 -i eth0
386-[    0.587575] sd 0:0:0:1: [sdb] Mode Sense: 0f 00 00 00
387-[    0.588423] sd 0:0:0:1: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
388-[    0.591034] sd 0:0:0:1: [sdb] Attached SCSI disk
389-[    0.627938] Adding 2097152k swap on /dev/sdb.  Priority:-2 extents:1 across:2097152k
390:[    6.888925] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
391-[    7.023048] hv_pci 3c9ddda8-257a-4f62-ba26-16ecd5ddbe09: PCI VMBus probing: Using version 0x10004
392-[    7.027081] hv_pci 3c9ddda8-257a-4f62-ba26-16ecd5ddbe09: PCI host bridge to bus 257a:00
393-[    7.027851] pci_bus 257a:00: root bus resource [mem 0xc00000000-0xe00001fff window]
--
439-[   71.788971] misc dxg: dxgk: dxgkio_query_adapter_info: Ioctl failed: -22
440-[   71.794064] misc dxg: dxgk: dxgkio_query_adapter_info: Ioctl failed: -2
441-[   72.622976] systemd-journald[45]: Received client request to flush runtime journal.
442-[   78.604096] WSL (2): Creating login session for shibo
443:[   79.586230] TCP: eth0: Driver has suspect GRO implementation, TCP performance may be compromised.
444-[ 4673.993794] hv_utils: TimeSync IC version 4.0
445-[ 5282.082469] scsi 0:0:0:3: Direct-Access     Msft     Virtual Disk     1.0  PQ: 0 ANSI: 5
446-[ 5282.090204] sd 0:0:0:3: Attached scsi generic sg3 type 0

练习:

  • 查看 grep 的 man 文档,在其中寻找 inver 字段,查看具体的选项和解释。

  • 输入 df -h 命令,将有关 none 的行去掉,用以查看我们需要的内容。

    CODE
    shibo@DESKTOP-U7FRP5M:~/spiit$ df -h
    Filesystem      Size  Used Avail Use% Mounted on
    none            3.9G  4.0K  3.9G   1% /mnt/wsl
    none            247G  207G   40G  84% /usr/lib/wsl/drivers
    /dev/sdc        251G  1.8G  237G   1% /
    none            3.9G   84K  3.9G   1% /mnt/wslg
    none            3.9G     0  3.9G   0% /usr/lib/wsl/lib
    rootfs          3.9G  2.1M  3.9G   1% /init
    none            3.9G  848K  3.9G   1% /run
    none            3.9G     0  3.9G   0% /run/lock
    none            3.9G  400M  3.5G  11% /run/shm
    tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
    none            3.9G   76K  3.9G   1% /mnt/wslg/versions.txt
    none            3.9G   76K  3.9G   1% /mnt/wslg/doc
    C:\             247G  207G   40G  84% /mnt/c
    D:\             557G  253G  304G  46% /mnt/d
    snapfuse        128K  128K     0 100% /snap/bare/5
    snapfuse         74M   74M     0 100% /snap/core22/864
    snapfuse         75M   75M     0 100% /snap/core22/1380
    snapfuse         41M   41M     0 100% /snap/snapd/20290
    snapfuse         92M   92M     0 100% /snap/gtk-common-themes/1535
    snapfuse         39M   39M     0 100% /snap/snapd/21759
    snapfuse        132M  132M     0 100% /snap/ubuntu-desktop-installer/1276
    snapfuse        132M  132M     0 100% /snap/ubuntu-desktop-installer/1286
    shibo@DESKTOP-U7FRP5M:~/spiit$ df -h | grep -v none
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/sdc        251G  1.8G  237G   1% /
    rootfs          3.9G  2.1M  3.9G   1% /init
    tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
    C:\             247G  207G   40G  84% /mnt/c
    D:\             557G  253G  304G  46% /mnt/d
    snapfuse        128K  128K     0 100% /snap/bare/5
    snapfuse         74M   74M     0 100% /snap/core22/864
    snapfuse         75M   75M     0 100% /snap/core22/1380
    snapfuse         41M   41M     0 100% /snap/snapd/20290
    snapfuse         92M   92M     0 100% /snap/gtk-common-themes/1535
    snapfuse         39M   39M     0 100% /snap/snapd/21759
    snapfuse        132M  132M     0 100% /snap/ubuntu-desktop-installer/1276
    snapfuse        132M  132M     0 100% /snap/ubuntu-desktop-installer/1286
  • 将该命令定义一个别名 df2,以后需要用到该功能时直接输入 df2 这个别名就行了。

    CODE
    shibo@DESKTOP-U7FRP5M:~/spiit$ alias df2='df -h | grep -v none'
    shibo@DESKTOP-U7FRP5M:~/spiit$ df2
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/sdc        251G  1.8G  237G   1% /
    rootfs          3.9G  2.1M  3.9G   1% /init
    tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
    C:\             247G  207G   40G  84% /mnt/c
    D:\             557G  253G  304G  46% /mnt/d
    snapfuse        128K  128K     0 100% /snap/bare/5
    snapfuse         74M   74M     0 100% /snap/core22/864
    snapfuse         75M   75M     0 100% /snap/core22/1380
    snapfuse         41M   41M     0 100% /snap/snapd/20290
    snapfuse         92M   92M     0 100% /snap/gtk-common-themes/1535
    snapfuse         39M   39M     0 100% /snap/snapd/21759
    snapfuse        132M  132M     0 100% /snap/ubuntu-desktop-installer/1276
    snapfuse        132M  132M     0 100% /snap/ubuntu-desktop-installer/1286

正则表达式的符号意义

正则表达式的符号众多,这边先列出常用的的几个正则符号

符号含义及示例
^word表示匹配字符串(word)在行首。示例 grep -n '^#' /etc/hosts
word$表示匹配字符串(word)在行尾。示例 grep -n 's$' /etc/hosts
.表示匹配任意一个字符。示例 grep -n 'ip.' /etc/hosts
\转意字符,将某些具有特定含义的符号转化为普通字符串,例如匹配字符 ’ grep -n \ /etc/hosts'
*匹配在该符号前零次到无穷多次字符,例如 grep -n 'ess*' /etc/hosts 匹配 es,ess,essss等字符
[list]表示匹配字符合集中的任意一个字符,例如 grep -n 'a[fsy]g' /etc/hosts 表示匹配 afg,asg,ayg 字符。
[n1-n2]表示匹配字符集合中n1到n2字符之间任意个字符(这个连续与 ascll 有关,因此如何在匹配时出现问题因该查看系统变量中的LANG变量),例如:grep -n 'a[b-g]' /etc/hosts 表示匹配 ab
[^list]表示不匹配字符集中的字符,例如:grep -n 'a[^bcd]g' /etc/hosts 表示匹配 afg,asg,aeg,aag字符。
{n1,n2}表示匹配前一个字符n1到n2个连续的字符,例如:grep -n 'go\{1,3\}' test.txt 表示匹配 gooo 字符。

除了上面提到的符号,还有一些特殊字符。

特殊符号代表意义
[:alnum:]代表英文大小写字元及数字,亦即0-9, AZ, az
[:alpha:]代表任何英文大小写字元,亦即AZ, az
[:blank:]代表空白键与[Tab] 按键两者
[:cntrl:]代表键盘上面的控制按键,亦即包括CR, LF, Tab, Del.. 等等
[:digit:]代表数字而已,亦即0-9
[:graph:]除了空白字元(空白键与[Tab] 按键) 外的其他所有按键
[:lower:]代表小写字元,亦即az
[:print:]代表任何可以被列印出来的字元
[:punct:]代表标点符号(punctuation symbol),亦即:" ’ ? ! ; : # $…
[:upper:]代表大写字元,亦即AZ
[:space:]任何会产生空白的字元,包括空白键, [Tab], CR 等等
[:xdigit:]代表16 进位的数字类型,因此包括: 0-9, AF, af 的数字与字元

练习:

  1. 找出/etc/services 内含http 关键字的那几行

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep -n 'http' /etc/services
    3:# Updated from https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml .
    39:http         80/tcp          www             # WorldWideWeb HTTP
    83:https                443/tcp                         # http protocol over TLS/SSL
    84:https                443/udp                         # HTTP/3
    263:http-alt    8080/tcp        webcache        # WWW caching service
  2. 承上,若仅须『开头含有http 』字样的那几行?

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep -n '^http[^s-]' /etc/services
    39:http         80/tcp          www             # WorldWideWeb HTTP
  3. 承上,若仅须『开头含有http 或https 』字样的那几行?

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep -n '^http[^-]' /etc/services
    39:http         80/tcp          www             # WorldWideWeb HTTP
    83:https                443/tcp                         # http protocol over TLS/SSL
    84:https                443/udp                         # HTTP/3
  4. 承上,若仅须『开头含有http 或https 』字样之外,且后面仅能接空白字元或[tab] 字元的那几行?

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep -n '^http[^-][[:blank:]]' /etc/services
    39:http         80/tcp          www             # WorldWideWeb HTTP
    83:https                443/tcp                         # http protocol over TLS/SSL
    84:https                443/udp                         # HTTP/3
  5. 承上,若仅须『开头含有http 且后续接有80 』字样的那几行

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep -n '^http[^-][[:blank:]]80' /etc/services
    39:http         80/tcp          www             # WorldWideWeb HTTP
  6. 找出/etc/services 内含有星号(*) 的那几行

    image-20240621154506429

  7. 找出/etc/services 内含有星号,且星号前为英文(不论大小写)的那几行

    image-20240621154549577

  8. 找出/etc/services 含有一个数字且后面紧邻一个大写字元的那几行

    image-20240621154644670

  9. 找出/etc/services 开头是一个数字紧邻一个大写字元的那几行

    image-20240621155251710

  10. 使用find /etc 找出档名,并找出结尾含有『 .conf 』的档名资料

    CODE
    shibo@DESKTOP-U7FRP5M:~$ find /etc -name '*.conf'
    /etc/rsyslog.conf
    /etc/systemd/timesyncd.conf
    /etc/systemd/resolved.conf
    /etc/systemd/journald.conf
    /etc/systemd/system/systemd-binfmt.service.d/00-wsl.conf
    /etc/systemd/system/systemd-sysusers.service.d/00-wsl.conf
    /etc/systemd/system.conf
    /etc/systemd/logind.conf
    /etc/systemd/pstore.conf
  11. 承上,且含有『大写字元或数字』档名在内的那几个

    CODE
    shibo@DESKTOP-U7FRP5M:~$ find /etc -name '*.conf' | grep '[[:digit:],[:upper:]]'
    find: ‘/etc/polkit-1/localauthority’: Permission denied
    /etc/systemd/system/systemd-binfmt.service.d/00-wsl.conf
    /etc/systemd/system/systemd-sysusers.service.d/00-wsl.conf
    /etc/dbus-1/system.d/com.ubuntu.SoftwareProperties.conf
    /etc/dbus-1/system.d/org.freedesktop.PackageKit.conf
    /etc/polkit-1/localauthority.conf.d/50-localauthority.conf
    /etc/polkit-1/localauthority.conf.d/51-ubuntu-admin.conf
    find: ‘/etc/ssl/private’: Permission denied
    /etc/apt/apt.conf.d/20apt-esm-hook.conf
    /etc/apt/apt.conf.d/20snapd.conf
    /etc/rsyslog.d/20-ufw.conf
    /etc/rsyslog.d/50-default.conf
    /etc/sysctl.d/10-ptrace.conf

使用 sed 工具

使用 sed 工具实验信息选取。

sed 是一个流编辑器,它时文本处理中非常重要的工具,并且也支持完整的正则表达式,熟练使用 sed 命令可以极大提升工作效率。这里介绍一下该命令的基本用法,详细使用方法 点这里

sed 命令的格式如下:

CODE
sed ’s/旧文本内容/新文本内容/g‘ 文档路径

旧文本内容必须指定,新本文内容如果忽略则表示替换为空。依据这个特性,我们用来代替 awk '{print $@}'命令。现在,我们使用这f命令来显示 eth0 网卡中的 IPv4 地址。

首先使用 ifconfig eth0 和 grep 命令提取出 IPv4 地址行。

SHELL
shibo@DESKTOP-U7FRP5M:~$ ifconfig eth0 | grep 'inet[[:space:]]'
        inet 172.27.254.212  netmask 255.255.240.0  broadcast 172.27.255.255

然后,我们使用 sed 命令将 IPv4 地址前的 inet 字段去除。

SHELL
shibo@DESKTOP-U7FRP5M:~$ ifconfig eth0 | grep 'inet[[:space:]]' | sed 's/^.*inet[[:space:]]//g'			# 这里 sed 命令的意识是,将 inet 及其前面的空额替换为空。
172.27.254.212  netmask 255.255.240.0  broadcast 172.27.255.255

现在,我们将 IPv4 地址后的 netmask 及其后面的字段去除。

CODE
shibo@DESKTOP-U7FRP5M:~$ ifconfig eth0 | grep 'inet[[:space:]]' | sed 's/^.*inet[[:space:]]//g' | sed 's/[[:space:]]netmask.*//g'		# 最后一个 sed 的意思是,将 netmask 及其后面的所有字段替换为空
172.27.254.212

现在提取出来的就是一个纯粹的 IPv4 地址。

使用 sed 命令替换文件中的内容。

使用 sed 命令,可以直接替换掉源文件中的文本内容。例如,现在将 test .txt.文档 “ess” 小写字母替换为大写字母。

CODE
shibo@DESKTOP-U7FRP5M:~$ cat test.txt
find: ‘/etc/polkit-1/localauthority’: Permission denied
/etc/shadow-
/etc/shadow
find: ‘/etc/ssl/private’: Permission denied
/etc/gshadow-
/etc/gshadow
gooooooooogle
gadbec def
essss
ess

shibo@DESKTOP-U7FRP5M:~$ sed 's/ess/ESS/g' test.txt
find: ‘/etc/polkit-1/localauthority’: Permission denied
/etc/shadow-
/etc/shadow
find: ‘/etc/ssl/private’: Permission denied
/etc/gshadow-
/etc/gshadow
gooooooooogle
gadbec def
ESSss
ESS

注意:在不加任何选择时,我们修改的内容只会存储到缓冲区,并不会直接写入文件。如果你需要将替换的内容写入到文件内,那么我们可以使用 -i 选项。

CODE
shibo@DESKTOP-U7FRP5M:~$ sed -i 's/ess/ESS/g' test.txt
shibo@DESKTOP-U7FRP5M:~$ cat test.txt
find: ‘/etc/polkit-1/localauthority’: Permission denied
/etc/shadow-
/etc/shadow
find: ‘/etc/ssl/private’: Permission denied
/etc/gshadow-
/etc/gshadow
gooooooooogle
gadbec def
ESSss
ESS

除了替换内容外,sed 命令还能指定输出行的范围。例如,现在要输出 /etc/passwd 文件中 10-15 行的内容。

CODE
shibo@DESKTOP-U7FRP5M:~$ cat -n /etc/passwd | sed -n '10,15p'
    10  news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
    11  uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
    12  proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
    13  www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
    14  backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
    15  list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin

练习;

  1. 应该要用grep 去找出/etc/passwd 里面,结尾是bash 的那几行

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep 'bash$' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    shibo:x:1000:1000:,,,:/home/shibo:/bin/bash
  2. 承上,透过sed 将/bin/bash 改成/sbin/nologin 显示到萤幕上

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep 'bash$' /etc/passwd | sed 's/\/bin\/bash/\/sbin\/nologin/g'
    root:x:0:0:root:/root:/sbin/nologin
    shibo:x:1000:1000:,,,:/home/shibo:/sbin/nologin
  3. 承上,透过tr 这个指令,将全部的英文都变成大写字元

    CODE
    shibo@DESKTOP-U7FRP5M:~$ grep 'bash$' /etc/passwd | sed 's/\/bin\/bash/\/sbin\/nologin/g' | tr [a-z] [A-Z]
    ROOT:X:0:0:ROOT:/ROOT:/SBIN/NOLOGIN
    SHIBO:X:1000:1000:,,,:/HOME/SHIBO:/SBIN/NOLOGIN

shell 脚本

shell 脚本相当于一组命令的集合,依据自己的需求将需要执行的命令和判断条件写进 shell 脚本中,可以实现一些复杂的功能。在实际项目中 shell 脚本还可以用来进行自动化。

shell 脚本编写基础

shell 脚本的特性与大部分编程语言类似,因此理解起来并不复杂。

  • Shell 脚本执行是由上到下,由左到右执行的
  • 脚本开头需要声明所使用的 shell 环境。如比常用的 #!/bin/bash。这表示使用 bash shell 环境。
  • 命令之前的多余的空格和空行会被忽略。
  • 当检测到 Enter (CR)换行符时,执行该行所出现的命令。
  • 内容过多需要换行继续写时,可以使用 \[Enter] 符号来表示延伸至下一行。
  • # 在 shell 脚本中为注释的意思,注释不会被执行,只有说明或解释的作用。

执行 shell 脚本有两种方式,分别为:

  • 直接命令下达,脚本必须具有 x 可执行权限。
    • 绝对路径执行: 通过输入脚本的绝对路径来执行。例如:/home/shibo/myid.sh
    • 相对路径执行: 当与脚本同目录时,可以通过 输入脚本的相对路径执行脚本。例如:./myid.sh
    • 直接输入脚本名执行: 当脚本位于全局变量 【PATH】 中所指向的目录时,可以直接输入脚本名称来执行脚本。例如:myid.sh
  • 通过 bash 程序执行:使用 bash <脚本名> 或 sh <脚本名> 等命令来执行脚本。

现在,我们使用普通用户,在 /bing 目录创建 shell 脚本来进行练习。脚本要求如下:

  • 输出用户的 id 信息
  • 输出用户的家目录。
  • 输出用户的历史命令记录条数。
  • 列出当前用户下所有的别名命令。

依据上面的要求,我们的脚本大体上因该如下:

CODE
#!/bin/bash
# This script will use id, echo to show account's messages
# write by VBird 2016/04/27
echo "This script will show your accout messages."
echo "The 'id' command output is: "
id		# 用以输出用户的 id 信息
echo "your user's home is: ${HOME}"		# 显示用户的家目录
echo "your history record: ${HISTSIZE}"		# 显示用户的历史命令记录条数
echo "your command aliases: "
alias		# 显示当前用户下的别名命令。
echo "your home dir's filenames: "
ll ~		# 列出当前用户家目录下的文件。

练习:

  1. 通过 bash 或 sh 命令来执行脚本。

    CODE
    shibo@DESKTOP-U7FRP5M:~$ sh myid.sh
    This script will show your accout messages.
    The 'id' command output is:
    uid=1000(shibo) gid=1000(shibo) groups=1000(shibo),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),116(netdev)
    your user's home is: /home/shibo
    your history record:
    your command aliases:
    your home dir's filenames:
    myid.sh: 12: ll: not found
  2. 承上,当需要在调试模式下执行脚本时,需要用哪一个选项。

    CODE
    shibo@DESKTOP-U7FRP5M:~$ man sh
    ————————————省略————————————
               -x xtrace        Write each command to standard error (preceded by a ‘+ ’) before it is executed.  Useful for debugging.
    ————————————省略————————————
    shibo@DESKTOP-U7FRP5M:~$ sh -x myid.sh
    + echo This script will show your accout messages.
    This script will show your accout messages.
    + echo The 'id' command output is:
    The 'id' command output is:
    + id
    uid=1000(shibo) gid=1000(shibo) groups=1000(shibo),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),116(netdev)
    + echo your user's home is: /home/shibo
    your user's home is: /home/shibo
    + echo your history record:
    your history record:
    + echo your command aliases:
    your command aliases:
    + alias
    + echo your home dir's filenames:
    your home dir's filenames:
    + ll /home/shibo
    myid.sh: 12: ll: not found
  3. 如何直接输入脚本名称执行脚本?

    直接输入脚本名称执行脚本需要对脚本进行设置,确保脚本在当前用户下有 x 权限,且脚本所在的目录也存在 PATH 变量中

  4. 如何使用绝对路径执行脚本?

    CODE
    shibo@DESKTOP-U7FRP5M:/$ /home/shibo/myid.sh
  5. 当你在当前目录看到一个脚本,但是不清楚该目录是否在 PATH 变量中,因该如何执行脚本。

    CODE
    shibo@DESKTOP-U7FRP5M:~$ ./myid.sh
  6. 为什么

  7. 上面执行脚本后 HISTSIZE,alias,ll 等命令在脚本中不输出,如何解决?

    当我们使用 bash 或 sh 来执行脚本时,系统会单独开启一个 “子bash” 来执行脚本,由于系统开启的 “子bash” 无法直接调用 “父bash” 中的变量和别名命令,所以在执行一些命令时会报错或不输出。这个时候,我们可以使用 source 命令来执行脚本

    CODE
    shibo@DESKTOP-U7FRP5M:~$ source myid.sh
    This script will show your accout messages.
    The 'id' command output is:
    uid=1000(shibo) gid=1000(shibo) groups=1000(shibo),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),116(netdev)
    your user's home is: /home/shibo
    your history record: 10000
    your command aliases:
    alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
    alias cp='cp -i'
    alias egrep='egrep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias grep='grep --color=auto'
    alias l='ls -CF'
    alias la='ls -A'
    alias ll='ls -alF'
    alias ls='ls --color=auto'
    alias mv='mv -i'
    alias rm='rm -i'
    your home dir's filenames:
    total 205020
    drwxr-x--- 7 shibo shibo      4096 Jun 21 20:59  ./
    drwxr-xr-x 3 root  root       4096 May 28 13:50  ../
    -rw------- 1 shibo shibo     29002 Jun 19 17:46  .bash_history
    ————————以下省略————————

shell 脚本执行环境。

程序之间时有相关性的,我们可以使用 pstree 来查看。那 shell 脚本与 shell 之间有无相关性?我们用下面例子来看看:

CODE
shibo@DESKTOP-U7FRP5M:/tmp$ vim /bin/gototmp.sh
#!/bin/bash
cd /tmp
pwd
shibo@DESKTOP-U7FRP5M:/tmp$ sudo chmod a+x /bin/gototmp.sh           

问:

  1. 在使用一般执行方式执行完脚本后,你的当前目录为:

    CODE
    shibo@DESKTOP-U7FRP5M:/$ gototmp.sh
    /tmp
    shibo@DESKTOP-U7FRP5M:/$ pwd
    /

    因为通过 bash 或 sh 执行程序系统会创建一个新的子 bash,在这个子 bash 中执行脚本。因此脚本并不是没有起作用,而是另一个子 bash 中执行了。

  2. 如果是用 source 去执行,那么结果如何?

    CODE
    shibo@DESKTOP-U7FRP5M:/bin$ source gototmp.sh
    /tmp
    shibo@DESKTOP-U7FRP5M:/tmp$ pwd
    /tmp

    使用 source 执行脚本后,系统并不会创建一个子 bash ,而是在当前 bash 中执行脚本。

使用 source 命令执行脚本。

依据上面所说的,执行脚本有两种形式。

  • 产生一个 Bash 子程序,在其 bash 子程序中进行执行,例如使用 bash gototmp.sh 或 ./gototmp.sh
  • 不产生新的 bash ,在当前 bash 中执行。例如使用 source gototmp.sh。

练习:

  1. 设计一个脚本,它具有以i下功能。

    • 创建一个 MYIP 的变量,这个变量内存放着当前连网网卡的 IP 地址
    • 创建一个 myWork 变量,该变量内存放着路径地址 /usr/local/lib 。
    • 创建一个别名命令 megacil ,该别名是 /usr/bin/cat 命令。
    • SHELL
      #!/bin/bash
      MYIP=$(ifconfig eth0 | grep 'inet[[:space:]]' | sed 's/^.*[[:space:]]inet[[:space:]]//g' | sed 's/netmask[[:space:]].*//g')
      myWork="/usr/local/lib"
      alias megacli='/usr/bin/cat'
  2. 现在需要让 myenv.sh 这个脚本在当前 bash 中运行,如何做?

    CODE
    shibo@DESKTOP-U7FRP5M:~$ source myenv.sh
    shibo@DESKTOP-U7FRP5M:~$ echo $MYIP
    172.27.254.212
    shibo@DESKTOP-U7FRP5M:~$ echo $myWork
    /usr/local/lib
    shibo@DESKTOP-U7FRP5M:~$ megacli
    fd
    fd
    te

以人机交互方式编写脚本

在编写脚本时,我们可以使用以下两种方式实现人机交互式脚本。

  • 使用 read 命令实现读取用户输入的数据实现人机交互脚本。
  • 使用 ${1} 等外部传值方式实现人机交互脚本。

使用 read 让用户输入数据

我们首先来了解一下 read 命令的用法,read 命令的用法和其他编程语言当中的标准输入命令差不多,它的基本格式如下:

SHELL
read [option] <提示信息> <存入的变量>
# 常见选项
-a <arr_var>:将输入的数据存入到素组变量中。
-p <"test">:指定读取时的提示符
-t <number>:指定等待输入时间。
-d <"txt">:指定结束输入字符。
-n <number>:读取指定 number 的字符,不等回车读取。

例如我们让用户输入一个数,然后让该数存入到变量 num 中。

CODE
read -p "place input a number:" num

现在我们用这个命令,来实现用户输入数字,脚本输出特定的 pi 值小数位数。

CODE
#!/bin/bash
# 这是一个用于计算 pi 值的脚本。
echo -e "The program will calculate pi value \n"	#这里的 -e 选项表示允许使用 \ 符号
echo -e "You should input a float number to calculate pi value \n"
read -p "place input a number:" num
echo -e "Starting calculate pi value Be patient"
time echo "scale=${num} ; 4*a(1)" | bc -lq			#这里的 scala 变量就是指定输出的小数位长度(精度)。
# time 命令用于显示程序或命令运行使用的的时间和用户/系统使用情况。

程序执行效果如下:

CODE
shibo@DESKTOP-U7FRP5M:~$ source myip.sh
The program will calculate pi value

-3 You should input a float number to calculate pi value \n
place input a number:10
Starting calculate pi value Be patient
3.1415926532

real    0m0.005s
user    0m0.003s
sys     0m0.002s
  • 练习:在 /usr/local/bin/listcmd.sh 编写脚本,该脚本能偶执行以下任务。

    1. 使用 # 注释表明该脚本的作用。
    2. 通过 read 命令读取用户输入的命令名称
    3. 通过 which 命令将用户输入的命令名称取缔其完整路径。
    4. 利用 ls -al 命令查看命令完整权限。
    5. 使用 getfacl 命令查看你命令的完整权限。
    6. 执行结束返回 0 值。
    SHELL
    #!/bin/bash
    # 这个程序用于显示你输入的命令的路径详细信息和权限。
    read -p "请输入命令:" cmd
    cmd_path=$(which $cmd)
    ls -al $cmd_path
    getfacl $cmd_path
  • 执行程序,它的执行效果如下:

    CODE
    shibo@DESKTOP-U7FRP5M:~$ listcmd.sh
    请输入命令:cat
    -rwxr-xr-x 1 root root 35280 Feb  8  2022 /usr/bin/cat
    getfacl: Removing leading '/' from absolute path names
    # file: usr/bin/cat
    # owner: root
    # group: root
    user::rwx
    group::r-x
    other::r-x

通过外带参数来执行程序

外带参数其实就是在执行命令的同时在命令后给出的参数,例如:

CODE
shibo@DESKTOP-U7FRP5M:~$ source mypi_case.sh 20 
									${0}   ${1} 					

上面的脚本 mypi_case.sh 后的 20 就是该脚本的外带参数,通过将这个参数传入到脚本内并进行处理。如上所示,外带参数都有一个 【位置】 。及参数出现在脚本/命令后的顺序。如上面的 ${0} 的位置就表示传入程序的名称,${1} 表示传入脚本/命令后的的一个出现的参数,这里是 20。之后的参数以此类推。外带参数的具体用法如下:

  • ${#}:这里的 “#” 表示参数出现的顺序/个数,上面演示中,“#” 为 1 表示传入 命令后出现的一个参数 20。

  • ${@}:这里的 “@” 表示接受脚本/命令后所有的参数,如果使用该变量 ${@}则输入的参数会被视为一个整体。如果在双引号中使用该变量,即"${@}" 则输入的参数会以空格分隔作为独立的参数。

    创建一个 example.sh 脚本,先使用一般方式使用该变量。

    SHELL
    f#!/bin/bash
    echo "all argument ${@}"

    现在我们执行脚本,并将在脚本后输入四个参数。

    CODE
    shibo@DESKTOP-U7FRP5M:~/shell_script$ sh -x example.sh  this is a argument
    + echo all argument this is a argument
    all argument this is a argument

    可以看到,这里把四个参数合并为了一个参数。现在我们改变一下脚本。

    SHELL
    #!/bin/bash
    for arg in "${@}"; do
            echo "all argument $arg"
    done

    同样的,我们再执行脚本后输入四个参数。

    CODE
    shibo@DESKTOP-U7FRP5M:~/shell_script$ sh  example.sh  this is a argument
    all argument this
    all argument is
    all argument a
    all argument argument

    被 "" 双引号括起来的 ${@} 会将参数以空格隔开,保证每个参数的独立性。

  • $*:这里的 * 表示接受所有参数,并将其输入的参数合并为一个处理。

    同样是上面的脚本,我们将其中的 ${@} 改为 $* 。

    SHELL
    #!/bin/bash
    for arg in "$*"; do
            echo "all argument $arg"
    done

    执行脚本并再其脚本后输入 4 个参数。

    CODE
    shibo@DESKTOP-U7FRP5M:~/shell_script$ sh  example.sh  arg1 arg2 arg3 arg4
    all argument arg1 arg2 arg3 arg4

    可以看到,其参数被合并为了一个来处理。

现在我们使用外带参数的方式来修改之前我们写的计算 pi 值的程序中。

SHELL
#!/bin/bash
# 这是一个用于计算 pi 值的脚本。
echo -e "The program will calculate pi value \n"
echo -e "You should input a float number to calculate pi value \n"
# read -p "place input a number:" num
num=${1}
time echo "scale=${num} ; 4*a(1)" | bc -lq

现在,我们可以直接脚本后输入我们需要计算的 pi 值的长度啦。

CODE
shibo@DESKTOP-U7FRP5M:~$ source myip.sh 20
The program will calculate pi value

You should input a float number to calculate pi value

Starting calculate pi value Be patient
3.14159265358979323844

real    0m0.003s
user    0m0.000s
sys     0m0.000s

练习:

  1. 修改之前编写的 listcmd.sh 脚本,将通过用户输入数据修改为通过外带参数输入数据。

    CODE
    #!/bin/bash
    # 这个程序用于显示你输入的命令的路径详细信息和权限。
    cmd=${1}
    # read -p "请输入命令:" cmd
    cmd_path=$(which $cmd)
    ls -al $cmd_path
    getfacl $cmd_path

    执行结果。

    CODE
    shibo@DESKTOP-U7FRP5M:/usr/local/bin$ listcmd2.sh pwd
    -rwxr-xr-x 1 root root 35328 Feb  8  2022 /usr/bin/pwd
    getfacl: Removing leading '/' from absolute path names
    # file: usr/bin/pwd
    # owner: root
    # group: root
    user::rwx
    group::r-x
    other::r-x

通过 if … then 条件判断

有时候需要通过判断参数或功能是否符合程序要求,这个时候我们就可以使用 if 语句。再 shell 脚本中 if 语句的格式如下:

SHELL
if [表达式]; then
	# 条件成立,位于这里的语句就会被执行。
fi

如果需要进行多个判断,格式如下:

SHELL
if [表达式]; then
	# 条件成立,执行这里的语句
else
	# 条件不成立,执行这里的语句。
fi

如果需要进行更加复杂的操作,那么可以使用下面的格式:

SHELL
if [表达式1]; then
	# 表达式1条件成立,执行这里的语句
elif [表达式2];then
	# 表达式2条件成立,执行这里的语句
else
	# 两个表达式条件都不成立,执行这里的语句。
fi 

若需要两个或以上的表达式混合执行,可以使用 -a(and) 和 -o(or) 选项。

SHELL
if [表达式1 -a 表达式2]; then
	# 当表达1 与表达式2 都成立时,这里的语句执行。
fi

if [表达式1 -o 表达式2]; then
	# 当表达式1或表达式2其中一条成立时,这里的语句执行。
fi

或者,你也可以使用下面的格式。

CODE
if [表达式1] && [表达式2]; then
	## 当表达式1与表达式2都成立,这里的语句执行
fi

if [表达式1] || [表达式2]; then
	# 当表达式1或表达式2的其中任意一条成立,这里的语句执行。
fi 

通过上面的方式,我们可以用以修改 mypi.sh 程序,实现防呆的效果。这里我们要求输入的数不能小于 10 ,因为精度太低没意义。同时输入的数也不能大于 100,精度太高会算的慢,会占用 cpu 资源。

SHELL
#!/bin/bash
# 这是一个用于计算 pi 值的脚本。
echo -e "The program will calculate pi value \n"
echo -e "You should input a float number to calculate pi value \n"
# read -p "place input a number:" num
num=${1}
checking=$( echo ${num} | grep '[0-9]')
if [ ”${num}“ == ”“ -o "${checking}" != "" ];then
        if [[ "${num}" -le 10 ]]; then
                echo -e "数字过小\n"
        elif [[ "${num}" -ge 100 ]]; then
                echo -e "数字过大\n"
        else
                echo -e "Starting calculate pi value Be patient"
                time echo "scale=${num} ; 4*a(1)" | bc -lq
        fi
else
        echo -e "非法字符,请输入数字【10~100】"
fi

执行这个脚本,看看结果如何。

CODE
shibo@DESKTOP-U7FRP5M:~$ source myip.sh 5
The program will calculate pi value

You should input a float number to calculate pi value

数字过小

shibo@DESKTOP-U7FRP5M:~$ source myip.sh 14
The program will calculate pi value

You should input a float number to calculate pi value

Starting calculate pi value Be patient
3.14159265358976

real    0m0.001s
user    0m0.000s
sys     0m0.000s
shibo@DESKTOP-U7FRP5M:~$ source myip.sh 130
The program will calculate pi value

You should input a float number to calculate pi value

数字过大

shibo@DESKTOP-U7FRP5M:~$ source myip.sh hg
The program will calculate pi value

You should input a float number to calculate pi value

非法字符,请输入数字【10~100】

以 case … esac 来判断。

这里的 case 与其他编程语言的类似语法一致,只是格式上有些差别。

SHELL
case $变量名 in
	"第一个变量内容")
		# 匹配后执行的语句
		;;
	”第二个变量内容“)
		# 匹配后执行的语句。
		;;
	"第三个变量内容")
		# 匹配后执行的语句
		;;
		....
	"*")
		# 匹配后执行的语句
		;;
esac

继续用 mypi.sh 这个脚本举例,我们将 mypi.sh 通过 case 改写,用户只能输入 20、50、80 这几个数,如果输入的数不是这个几个,则会提示”请输入指定的数 20|50|80!“

CODE
#!/bin/bash
# 这是一个用于计算 pi 值的脚本。
echo -e "The program will calculate pi value \n"
echo -e "You should input a float number to calculate pi value \n"
# read -p "place input a number:" num
num=${1}
checking=$( echo ${num} | grep '[0-9]')
case $num in
        "20")
                echo "scale=$num ; 4*a(1)" | bc -lq
                ;;
        "50")
                echo "scale=$num ; 4*a(1) " | bc -lq
                ;;
        "80")
                echo "scale=$num ; 4*a(1) " | bc -lq
                ;;
        *)
                echo "请输入指定的数 20|50|80!"
                ;;
esac

我们来看下执行效果。

CODE
The program will calculate pi value

You should input a float number to calculate pi value

请输入指定的数 20|50|80!
shibo@DESKTOP-U7FRP5M:~$ source mypi_case.sh 50
The program will calculate pi value

You should input a float number to calculate pi value

3.14159265358979323846264338327950288419716939937508

衍生正则表达式字符

衍生正则表达式可以在原正则表达式的基础上简化命令。普通的 grep 只支持正则表达,如果需要使用衍生正则表达式,则需要使用 egrep 命令。举一个例子:在检索文件内容时去除空行和注释行可以减少干扰项,使用常规正则表达式进行匹配时,需要执行两次。

CODE
grep -v '^$' | grep -v '^#'

而使用衍生正则表达式,只需要执行一次:

CODE
egrep -v '^$|^#'

衍生正则表达式的特殊符号为:

RE 字符意义与范例
+意义:重复『一个或一个以上』的前一个RE 字符 范例:搜寻(god) (good) (goood)… 等等的字串。 那个o+ 代表『一个以上的o 』所以,底下的执行成果会将第1, 9, 13 行列出来。egrep -n ‘go+d’ regular_express.txt
?意义:『零个或一个』的前一个RE 字符 范例:搜寻(gd) (god) 这两个字串。 那个o? 代表『空的或1 个o 』所以,上面的执行成果会将第13, 14 行列出来。 有没有发现到,这两个案例( ‘go+d’ 与’go?d’ )的结果集合与’go*d’ 相同? 想想看,这是为什么喔! ^_^egrep -n ‘go?d’ regular_express.txt
|意义:用或( or )的方式找出数个字串 范例:搜寻gd 或good 这两个字串,注意,是『或』! 所以,第1,9,14 这三行都可以被列印出来喔!那如果还想要找出dog 呢?egrep -n ‘gd|good’ regular_express.txt egrep -n ‘gd|good|dog’ regular_express.txt
()意义:找出『群组』字串 范例:搜寻(glad) 或(good) 这两个字串,因为g 与d 是重复的,所以, 我就可以将la 与oo 列于( ) 当中,并以| 来分隔开来,就可以啦!egrep -n ‘g(la|oo)d’ regular_express.txt
()+意义:多个重复群组的判别 范例:将『AxyzxyzxyzxyzC』用echo 叫出,然后再使用如下的方法搜寻一下!echo ‘AxyzxyzxyzxyzC’ | egrep ‘A(xyz)+C’上面的例子意思是说,我要找开头是A 结尾是C ,中间有一个以上的"xyz" 字串的意思~

衍生正则表达式的内容就那么多。我们平时最常用的可能就一个 “()” 群组和 ”|“ 符号。另外,在正则表达式中 “!” 不是特殊字符,如果需要查找文件内容包含 ”!“ 符号的行,可以使用下面的方法:

CODE
shibo@DESKTOP-U7FRP5M:~$ egrep -n '[!]' mypi_case.sh
1:#!/bin/bash
19:             echo "请输入指定的数 20|50|80!"

作业/练习

  • 正规表示法的应用-撷取重要的设定项目for /etc/man_db.conf

    1. 先确认/etc/man_db.conf 里面有几行?

      image-20240703204909601

    2. 再确认这个档案里面不是空白行也不是注解开头的,总共有几行?

      image-20240703205010153

    3. 最终只列出开头是『 MANPATH_MAP 』的那几行即可。

      image-20240703205033786

  • 正规表示法的应用-撷取重要的设定项目for /etc/rsyslog.conf

    1. 根据前一题的作法,请将/etc/rsyslog.conf 当中,非空白行、非注解行取出(注意喔, # 在最前面才是全注解!)

      image-20240703205448053

    2. 承上,输出的资料中,还是含有# ,请将每一行的# 到最后面的资料,全部删除,只保留需要的设定部份。

    3. 从/etc/rsyslog.conf 当中,找出含有tcp 或udp 那一行,包括行号列出

    4. 根据上面的输出结果,你觉得rsyslog 这个服务,可能会启动那一个网路埠口号码?

  • 撰写一只名为lm 的指令,这个指令会取得后面所有的参数( " {@} | more 』的样式, 进行一页一页翻动的功能。这个指令放在大家都能执行的/usr/local/bin 里面,请设定好权限之后, 以student 的身份执行看看喔!@ “),然后使用[ ls -al ]

    image-20240703210112924

    执行效果:

    image-20240703210210187