# Linux命令行与shell脚本编程大全

# 6 Linux环境变量

# 6.1 什么是环境变量

# 6.1.1 全局环境变量

# 6.1. 1 局部环境变量

# 6.2 设置用户自定义变量

# 6.2.1 设置局部用户自定义变量

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# my_variable=Hello#局部用户自定义变量直接赋值
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable 
Hello
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# my_variable=Hello World
-bash: World: command not found
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# my_variable="Hello World"#有空格需要用双引号包裹起来
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable 
Hello World
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# bash       #新开一个shell的时候,局部变量失效
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# exit
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable 
Hello World
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# my_variable = "Hello World"  #等号两边不可以有空格
-bash: my_variable: command not found
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


常量中间如果有空格  需要用双引号或者单引号包住
等号两边不能有空格
用户自定义变量只能在当前shell使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 6.2.2 设置全局环境变量

root@iZuf6fdhtuwmr11b7hkk8pZ ~]# export my_variable="Hello"#将变量设置为全局
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable 
Hello
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# bash  #新开一个shell也可以使用
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable
Hello
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# my_variable="World"  #重新赋值会变成新的值
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable
World
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# exit
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable 
Hello


使用export命令将用户自定义变量变成全局变量
另外,子shell中修改的该变量的值不会影响到父shell的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 6.3 删除环境变量

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# export my_variable="Hello"
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable 
Hello
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# bash
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable
Hello
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# unset my_variable #删除环境变量
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# exit
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $my_variable #子进程中删除不会对父进程产生影响
Hello

使用unset命令删除全局变量
在子进程中删除了该变量之后,不会对父进程产生影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 6.4 默认的shell环境变量

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $HISTFILESIZE
1000
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $HISTSIZE
1000



使用bash --version 查看bash shell的版本


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.5 设置PATH环境变量

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

PATH环境变量定义了用于查找命令和程序的目录。
如果命令和程序没有在上边的目录中,并且不使用绝对目录的情况下,shell无法找到。

如果想要在任意位置执行这个程序,需要将新的目录加入到PATH环境变量中,添加的方法是 PATH=$PATH:/home/christine/Sripts

但是这种方法只能持续到退出或者重启系统。如果想要持久化,那么需要在.bashrc或.bash_profile文件中添加
export PATH=$PATH:/your/new/path
然后使用
source ~/.bashrc  或者  source ~/.bash_profile 使其生效。

windows中同理,比如我们安装了java之后,需要添加环境变量,就是为了可以在任意目录下执行java的命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6.6 定位系统环境变量

启动bash shell有以下三种方式

1、登录时作为默认登shell

2、作为交互式shell,通过生成子shell启用 (使用bash命令就可以启动一个新的子进程shell)

3、作为运行脚本的非交互式shell

# 6.6.1 登录shell

/etc/profile文件是系统中默认的bash shell主启动文件,系统中的每个用户登录时都会执行这个启动文件,会执行这个文件中命令。

# 6.6.2 交互式shell进程

不会处理/etc/profile文件,只检查用户 $HOME目录中的 .bashrc文件

# 6.6.3 非交互式shell

脚本能以不同的方式执行。只有部分执行方式会启动子shell。

# 6.6.4 环境变量持久化

我们已经知道了各种shell进程以及其环境文件,并且将自己的环境变量放到这些文件中即可。

但是这样也不好,系统更新的时候,我们的环境变量就会丢失,(系统更新时,/etc/profile文件会更新,所以会丢失)所以最好的方法是在 $HOME/.bashrc目录下创建一个.sh的文件。

.bashrc目录是以.(点)开头的,所以说明这是隐藏文件。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $HOME
/root
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls
file1  file2  springboot_01_03-0.0.1-SNAPSHOT.jar

那么如何查看隐藏文件呢?
答:使用  ls -a命令
1
2
3
4
5
6
7

# 6.7 数组变量

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# myset=(zero one two three)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $myset  #直接输出 只会输出第一个,如果要引用单个,必须使用下标
zero
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# myset=(zero one two three)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $myset
zero
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[2]}
two
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[*]}
zero one two three
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# myset[0]=1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[0]}
1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# unset myset[0]
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[*]}  #虽然有输出,但是one的下标其实是1 不是 0
one two three
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[0]}  #打印下标为0的数据,输出为null

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[1]}
one
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# unset myset
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo ${myset[*]}

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


这是环境变量数组,当我们删除数组中的一个元素的时候,看似这个位置的元素已经被补上了,但是当单独输出这个位置的元素的时候,会发现他其实是不存在的。

和普通的环境变量一样,也可以用unset去删除一个数组环境变量,另外,也可以单独删除数组中的某一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 7.理解Linux文件权限

# 7.1Linux的安全性

Linux用户都会被分配一个唯一的用户账户,用来决定用户在访问系统时的权限。

# 7.1.1 /etc/passwd文件

Linux系统使用专门的文件来匹配登录名和对应的UID值。

一般UID大于500的是为普通用户使用的ID。

现在绝大多数的Linux系统将用户密码存放在/etc/shadow中,该文件只有root用户才可以访问。

# 7.1.2 /etc/shadow文件

该文件对Linux系统密码管理提供了更多的控制

# 7.1.3 添加新用户

Linux系统中,用useradd命令来添加新用户。 可以使用useradd -D来查看Linux的系统默认值

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# useradd -D
GROUP=100    //新用户会被添加到GID为100的公共组。
HOME=/home   //新用户的主目录会位于/home/loginname。
INACTIVE=-1  //新用户账户密码在过期后不会被禁用。
EXPIRE=      //新用户账户不设置过期日期。
SHELL=/bin/bash  //新用户账户将bash shell作为默认shell。
SKEL=/etc/skel //系统会将/etc/skel目录的内容复制到用户的$HOME目录。
CREATE_MAIL_SPOOL=yes //系统会为该用户账户在mail目录下创建一个用于接收邮件的文件。
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10

很多Linux的发行版本不会给用户创建$HOME目录,但是使用 -m 命令会使系统为新用户创建$HOME目录。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# useradd -m test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al /home/test
total 20
drwx------  2 test test 4096 Jan 24 23:09 .
drwxr-xr-x. 3 root root 4096 Jan 24 23:09 ..
-rw-r--r--  1 test test   18 Nov 25  2021 .bash_logout
-rw-r--r--  1 test test  193 Nov 25  2021 .bash_profile
-rw-r--r--  1 test test  231 Nov 25  2021 .bashrc
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


系统在创建用户的时候,将.bashrc、.bash_profile等文件复制过来,用于该用户登录时的标准启动文件。

1
2
3
4
5
6
7
8
9
10
11
12
13

另外,还可以使用其他选项例如

image-20240124231319170

另外还可以使用 -D选项来修改系统默认的新用户设置

image-20240124231419858

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# useradd -D -s /bin/tsch #修改默认登录的shell
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# useradd -D
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/tsch  #可以看到 默认的shell已经变了
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

这样就把默认shell的值修改为了tsch
1
2
3
4
5
6
7
8
9
10
11
12

# 7.1.4 删除用户

使用userdel命令来删除用户信息,但是一般情况下不会删除用户的$HOME目录以及邮件目录,可以带上-r参数

userdel -r test

1
2

# 7.1.5 修改用户

Linux系统提供了一些工具来修改已有用户的账户信息。

image-20240125125134738

1、usermod

大部分选项和useradd一致,例如

-c 用于修改备注字段

-e用于修改过期日期

-g用于修改默认的登录组

其他可能会排上用场的选项

-l 修改用户账户的登录名

-L 锁定账户,进制用户登录

-p 修改账户密码

-U 解除锁定,恢复用户登录

2、passwd & chpasswd

当使用passwd时, 用户只能修改自己的密码,但是root用户可以修改别人的密码。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]#passwd test
Changing password for user test.
New password: 
BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word
Retype new password: 
passwd: all authentication tokens updated successfully.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
1
2
3
4
5
6
7
passwd -e test
1

-e选项可以强制用户下次登录时修改密码

image-20240125174403496

如果想要修改大量用户的密码,可以使用 chpassed命令。

需要登录名和密码用冒号分割

使用格式如下

echo 'user1:pass1' 'user2:pass2' | sudo chpasswd
sudo chpasswd < passwd_file.txt

1
2
3

4、chage、chfn、chsh

chsh用户修改用户的默认的登录shell。但是必须使用shell的全路径名。

查看系统中安装了哪些SHeLL
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chsh -l
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


给用户修改shell
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chsh -s /bin/sh test
Changing shell for test.
Shell changed.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 7.2 使用Linux组

每个组都有唯一的UID,和UID类似,该值在系统中是唯一的。

# 7.2.1 /etc/group 文件

与用户账户类似,组信息也保存在一个文件中--/etc/group

组名|组密码|GID|属于该组的用户列表

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat /etc/group
root:x:0:
bin:x:1:
daemon:x:2:
sys:x:3:
adm:x:4:
tty:x:5:
disk:x:6:
lp:x:7:
mem:x:8:
kmem:x:9:
wheel:x:10:
cdrom:x:11:
mail:x:12:postfix
man:x:15:
dialout:x:18:
floppy:x:19:
games:x:20:
tape:x:33:
video:x:39:
ftp:x:50:
lock:x:54:
audio:x:63:
nobody:x:99:
users:x:100:
utmp:x:22:
utempter:x:35:
input:x:999:
systemd-journal:x:190:
systemd-network:x:192:
dbus:x:81:
polkitd:x:998:
ssh_keys:x:997:
sshd:x:74:
postdrop:x:90:
postfix:x:89:
chrony:x:996:
nscd:x:28:
tcpdump:x:72:
rpc:x:32:
rpcuser:x:29:
nfsnobody:x:65534:
cgred:x:995:
docker:x:994:
test:x:1000:
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

!!当一个用户在/etc/passwd文件中指定某个组为主要组时,该用户不会再出现在/etc/group 文件中。例如上边的GID为100的组中,就没有test这个用户。

root好像也是这个原因。

# 7.2.2 创建新组

groupadd 命令可以用来创建新组

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# groupadd shared
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail /etc/group | grep shared
shared:x:1001:
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

可以看到/etc/group文件中已经出现了shared这个组

在创建一个新的组的时候,默认不向组中添加任何用户,我们可以使用usermod命令向组中添加新的用户。


[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# useradd rich
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# usermod -G shared rich
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# usermod -G shared test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail /etc/group | grep shared
shared:x:1001:rich,test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
可以看到 shared 组中已经有两个用户了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7.2.3 修改组

修改组的GID groupmod -g 1001 1002

修改组的名称 groupmod -n sharing shared

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail /etc/group | grep shared
shared:x:1001:rich,test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# groupmod -n sharing shared
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# groupmod -n sharing shared  #已经将组的名称由shared修改成sharing
groupmod: group 'shared' does not exist
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail /etc/group | grep sharing
sharing:x:1001:rich,test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail /etc/group | grep sharing
sharing:x:1001:rich,test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# groupmod -g 1003 sharing
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail /etc/group | grep sharing #已经将组的id由1001修改为1003
sharing:x:1003:rich,test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7.3 理解文件权限

# 7.3.1 使用文件权限符号

  • -代表文件
  • d代表目录
  • l代表链接
  • c代表字符设备
  • b代表块设备
  • p代表具名管道
  • s代表网络套接字

之后是3组三字符的编码。每一组定义了3种访问权限。

  • ·r代表对象是可读的
  • ·w代表对象是可写的
  • ·x代表对象是可执行的

三组权限分别对应对象的3个安全级别

  • 对象的属主
  • 对象的属组
  • 系统其他用户

image-20240126093823369

最开头的-代表文件。

# 7.3.2 默认文件权限

使用umask命令来设置新文件和目录的默认权限。

创建一个文件 并且查看默认的权限。
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# touch 1.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 1.txt
-rw-r--r-- 1 root root 0 Jan 26 10:17 1.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# umask
0022
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

第一个数字(0)表示一项特别的安全特性
可以看到我们新创建的文件权限是644 但是umak得到的数字却是0022,是因为0022是一个掩码,文件默认权限设置666,文件夹默认权限777,与umask相减得到实际默认权限。(如果是文件用666-022,就得到644即:rw-r--r--如果是文件,就用777-022,最后得到的就是755 即rwxr-xr-x)

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mkdir xx
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l
drwxr-xr-x 2 root root     4096 May  9 17:56 xx  #(d代表目录,后边的权限是rwxr-xr-x)


umask通常会被设置在/etc/profile启动文件中。
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat /etc/profile | grep umask
# By default, we want umask to get set. This sets it for login shell
    umask 002
    umask 022
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


可以使用umask 026设置umask的默认值。
例如:
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# umask 026
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# touch 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-r----- 1 root root 0 Jan 26 10:25 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

同样也会作用于创建的文件夹上(mkdir创建  ls -l查看)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 7.4 更改安全设置

# 7.4.1 修改权限

chmod 命令可以修改文件和目录的安全设置。

#u代表用户  g代表组  o代表其他用户 a代表以上所有
#使用八进制模式 赋予文件权限
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod 760 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rwxrw---- 1 root root 0 Jan 26 10:25 2.txt
#使用符号模式赋予文件权限   o代表其他用户权限  r代表读
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod o+r 2.txt  #因为权限是属主 数组 其他用户,o代表其他用户所以是在最后边一组
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rwxrw-r-- 1 root root 0 Jan 26 10:25 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#给属主权限去掉可执行的权限
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod u-x 2.txt #代表给属主取消可执行权限
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-rw-r-- 1 root root 0 Jan 26 10:25 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

另外 chmod的option参数提供了其他的特性  -R 选项能够以递归的方式修改文件和目录的权限,另外还可以使用通配符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

另外附使用符号模式的符号以及含义

·u代表用户 ·g代表组 ·o代表其他用户 ·a代表上述所有

X:仅当对象是目录或者已有执行权限时才赋予执行权限。 ·s:在执行时设置SUID或SGID。 ·t:设置粘滞位(sticky bit)。 ·u:设置属主权限。 ·g:设置属组权限。 ·o:设置其他用户权限。

# 7.4.2 改变所属关系

chown 可以修改文件的属主。

chgrp 可以修改文件的默认数组。

#使用登录名或者UID来制定文件的新属主  可见root已经变成了test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-rw-r-- 1 root root 0 Jan 26 10:25 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chown test 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-rw-r-- 1 test root 0 Jan 26 10:25 2.txt

#同时修改文件的属主和数组  test是用户 sharing是组
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chown test.sharing 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-rw-r-- 1 test sharing 0 Jan 26 10:25 2.txt

#只修改文件的属组  注意数组需要用.开头
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chown .rich 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-rw-r-- 1 test rich 0 Jan 26 10:25 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

#如果用户名和组名相同,可以直接写 用户名.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al 2.txt
-rw-rw-r-- 1 root root 0 Jan 26 10:25 2.txt

#另外 该命令还有一些拓展
-R选项可以递归修改子目录和文件的所属关系。
需要注意的是 只有root用户能修改文件的属主,任何用户都可以修改文件的属组。
但是前提是 该用户必须属于该文件当前所在的组 以及 即将要修改的组

#chgrp可以修改文件或者文件的属组
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chgrp sharing 2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l 2.txt
-rw-rw-r-- 1 root sharing 0 Jan 26 10:25 2.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 7.5 共享文件

Linux为每个文件和目录存储了3个额外的信息位。

SUID:当用户执行该文件时,程序会以文件属主的权限运行。启用了之后,可以强制在共享目录中创建的文件都属于该目录的属组,这个组也就成了每个用户的属组。

SGID:对文件而言,程序会以文件属组的权限运行。对目录而言,该目录中创建的新文件会以目录的数组作为默认属组。

粘滞位:应用于目录时,只有稳健属主可以删除或者重命名该目录中的文件。

image-20240127231128427

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mkdir testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l
total 22624
-rw-r--r-- 1 root root           0 Jan 26 10:17 1.txt
-rw-rw-r-- 1 root sharing        0 Jan 26 10:25 2.txt
-rw-r--r-- 1 root root          28 Dec 28 09:03 file1
-rw-r--r-- 1 root root         191 Dec 28 09:04 file2
-rw-r--r-- 1 root root    23153747 Nov  5 14:35 springboot_01_03-0.0.1-SNAPSHOT.jar
drwxr-xr-x 2 root root        4096 Jan 27 23:12 testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chgrp sharing testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l
total 22624
-rw-r--r-- 1 root root           0 Jan 26 10:17 1.txt
-rw-rw-r-- 1 root sharing        0 Jan 26 10:25 2.txt
-rw-r--r-- 1 root root          28 Dec 28 09:03 file1
-rw-r--r-- 1 root root         191 Dec 28 09:04 file2
-rw-r--r-- 1 root root    23153747 Nov  5 14:35 springboot_01_03-0.0.1-SNAPSHOT.jar
drwxr-xr-x 2 root sharing     4096 Jan 27 23:12 testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod g+s testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l
total 22624
-rw-r--r-- 1 root root           0 Jan 26 10:17 1.txt
-rw-rw-r-- 1 root sharing        0 Jan 26 10:25 2.txt
-rw-r--r-- 1 root root          28 Dec 28 09:03 file1
-rw-r--r-- 1 root root         191 Dec 28 09:04 file2
-rw-r--r-- 1 root root    23153747 Nov  5 14:35 springboot_01_03-0.0.1-SNAPSHOT.jar
drwxr-sr-x 2 root sharing     4096 Jan 27 23:12 testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# umask 002
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cd testdir
[root@iZuf6fdhtuwmr11b7hkk8pZ testdir]# touch testfile
[root@iZuf6fdhtuwmr11b7hkk8pZ testdir]# ls -l
total 0
-rw-r--r-- 1 root sharing 0 Jan 27 23:15 testfile
[root@iZuf6fdhtuwmr11b7hkk8pZ testdir]# 

首先,使用mkdir命令创建希望共享的目录。然后,通过chgrp命令修改目录的默认属组,使其包含所有需要共享文件的用户。最后,设置目录的SGID位,保证目录中的新建文件都以shared作为默认属组。
为了让这个环境正常工作,所有组成员都要设置他们的umask值,使文件对属组成员可写。在先前的例子中,umask被改成了002,所以文件对属组是可写的。
完成这些步骤之后,组成员就能在共享目录下创建新文件了。跟期望的一样,新文件会沿用目录的默认属组,而不是用户账户的默认属组。现在shared组的所有用户都能访问这个文件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 7.6 访问控制列表

Linux系统的基本权限方法有一个缺点就是局限性,因为对于不同的文件和目录,不同的组要有不同的权限。

所以,开发设计者设计了一种更先进的文件和目录安全的方法:访问控制列表 ACL,它允许指定包含多个用户或者组的列表为其分配权限。

使用的基本命令是setfacl 和 getfacl

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# touch test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l
total 22624
-rw-r--r-- 1 root root           0 Jan 28 15:51 test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# getfacl test
# file: test
# owner: root
# group: root
user::rw-
group::r--
other::r--

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# setfacl -m g:rich:rw test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# getfacl test
# file: test
# owner: root
# group: root
user::rw-
group::r--
group:rich:rw-
mask::rw-
other::r--

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# setfacl -x g:rich test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# getfacl test
# file: test
# owner: root
# group: root
user::rw-
group::r--
mask::r--
other::r--
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

另外,Linux也允许对目录设置默认ACL,在该目录中创建的文件会自动集成,这个特性称为ACL继承。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mkdir saless
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# sudo setfacl -m d:g:rich:rw saless
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# getfacl saless
# file: saless
# owner: root
# group: root
user::rwx
group::r-x
other::r-x
default:user::rwx
default:group::r-x
default:group:rich:rw-
default:mask::rwx
default:other::r-x
#然后进到这个文件夹去创建文件 可以看到创建的文件也是具有rich这个组的权限
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cd saless
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# touch test
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# getfacl test
# file: test
# owner: root
# group: root
user::rw-
group::r-x			#effective:r--
group:rich:rw-
mask::rw-
other::r--

[root@iZuf6fdhtuwmr11b7hkk8pZ saless]#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 7.7 小结

首先讨论了Linux的安全性需要知道的命令,Linux将用户信息存储在/etc/passwd文件中,将组信息存储在/etc/group文件中,每个用户和组都有唯一的UID和GID

然后学习了管理用户组和用户需要用到的命令。

useradd 可以创建新的用户账户,groupadd可以创建新的组。

usermod可以修改已有的用户账户,groupmod可以修改组信息。

只有文件的属主才能修改文件或目录的权限。但root用户可以修改系统中任意文件或目录的安全设置。chown命令和chgrp命令可以改变文件的默认属主和属组。

接着讨论了SGID会强制某个目录下的新建文件和文件夹都沿用父目录的属组。

最后就是关于ACL的讨论,涉及到的命令有getfacl和setfacl。

# 8.管理文件系统

# 8.1 探索Linux文件系统

linux支持多种文件系统,每种文件系统都在存储设备上实现了虚拟目录结构。

# 8.1.1 Linux文件系统的演进

介绍了几种文件系统,分别是:

ext文件系统。

ext2文件系统。

# 8.1.2 日志文件系统

日志文件系统为Linux系统增加了一层安全性。它放弃了之前先将数据直接写入存储设备再更新i节点表的做法,而是先将文件变更写入临时文件(称作日志)。在数据被成功写到存储设备和i节点表之后,再删除对应的日志条目。

三种广泛使用的日志方法:

image-20240128162122109

之后,Linux系统又出现了一些不同的日志文件系统,接下来将介绍几种不同的日志文件系统。

1、ext3文件系统

2、ext4文件系统

3、JFS文件系统

4、ReiserFS文件系统

5、XFS文件系统

# 8.1.3 卷管理文件系统

采用了日志技术,就必须在安全性和性能之间做出选择。

所以日志技术出现了一种替代选择:写时复制(copy-on-write)的技术,这种技术兼顾了安全性和性能。

提供了COW、快照和卷管理特性的文件系统日渐流行,接下来将介绍几种文件系统。

1、ZFS文件系统

2、Btrfs文件系统

3、Stratis文件系统

# 8.2 使用文件系统

Linux提供了一些实用工具可以帮助我们轻松使用命令进行文件系统操作。

# 8.2.1 创建分区

1、fdisk

[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# whoami
root
1
2

以下是常用的fdisk命令。

image-20240128163539014

2、gdisk

image-20240128163746650

3、GUN parted

8.2.2 创建文件系统

image-20240128174329177

如果你想知道某个工具是否可以使用,可以使用type命令:

[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# type mkfs.ext4
mkfs.ext4 is /usr/sbin/mkfs.ext4
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# type mkfs.btrfs
mkfs.btrfs is /usr/sbin/mkfs.btrfs
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# 
1
2
3
4
5

8.2.3 文件系统的检查与修复

使用fsck命令,没有敲命令的场景,可以用到的时候去查询。

8.3 逻辑卷管理

# 9 .安装软件

  1. 安装软件
  2. 使用Debian软件包
  3. 使用Red Hat软件包
  4. 应用程序容器
  5. 再谈tarball

# 9.1 软件包管理基础

基于Debian的发行版(比如Ubuntu和Linux Mint)使用的是dpkg命令,该命令是其软件包管理系统的底层基础。

基于Red Hat的发行版(比如Fedora、CentOS和openSUSE)使用的是rpm命令,该命令是其软件包管理系统的底层基础。与dpkg命令类似,rpm命令也可以安装、管理和删除软件包。

# 9.2 基于Debian的系统

dpkg命令假定我们已经将DEB宝文件下载到了本地或者以URL的形式提供,但是并不是这样,所以我们需要使用到 apt-cache、apt-get、apt命令。

# 9.2.1 使用apt管理软件包

apt list命令会显示仓库中所有可用的软件包。

apt list --install 显示已经安装在系统中的包。

如果已经知道系统中的某个软件包,可以使用apt show package_name来查看详细信息。

但是apt show命令不会指明软件是否已经安装,只能根据仓库显示软件包的详细信息。

另外,使用apt show命令,不会让与这个软件包相关的所有文件显示,所以我们需要使用dpkg -L package_name 命令。

dpkg -L acl
也可以使用相反的操作,即找出特定的文件属于哪个软件包。但是文件需要使用绝对路径。
dpkg --search absolute_file_name
dpkg --search  /bin/getfacl
1
2
3
4

# 9.2.2 使用apt安装软件包

#查询特定的软件包
apt search package_name

#如果指向搜索软件包的名称,可以加入--names-only
apt --names-only search zsh

#找到之后就可以安装了
apt install package_name

#安装完成之后就可以使用list命令的installed 选项检查是否正确了
1
2
3
4
5
6
7
8
9
10

# 9.2.3 使用apt升级软件

#会将系统中的所有软件包升级到最新版本
apt upgrade

#upgrade命令在升级的过程中不会删除任何软件包,如果必须删除某个软件包才能完成升级,可以使用
apt full-upgrade
1
2
3
4
5

# 9.2.4 使用apt卸载软件包

apt的remove可以删除软件包,但是会保留数据和配置文件,如果想要全部删除,那么可以使用purge选项。

apt purge xxx

#另外,在purge的输出中,apt警告我们软件包可能存在依赖,不能自动删除,以免其他的软件包还有需要,如果确定不想要的话,可以使用autoremove命令。a
apt autoremove

autoremove命令会检查所有被标记为存在依赖关系并且不再被需要的软件包。
1
2
3
4
5
6

# 9.2.5 apt仓库

apt默认的软件存储库位置是在安装Linux发行版时设置的。仓库位置保存在文件/etc/apt/sources.list中。

# 9.3 基于Red Hat的系统

yum:用于Red Hat CentoS 和 Fedora。

zypper:用于openSUSE.

dnf:yum的升级版,有一些新增的特性。

# 9.3.1 列出已经安装的软件包

dnf list installed
yum list installed

#dnf list installed bash
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# yum list installed bash
Loaded plugins: fastestmirror
Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast
Loading mirror speeds from cached hostfile
Installed Packages
bash.x86_64                                                  4.2.46-35.el7_9                                                  @updates

#dnf provides /usr/bin/gzip
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# yum provides /usr/bin/gzip
Loaded plugins: fastestmirror
Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast
Loading mirror speeds from cached hostfile
gzip-1.5-10.el7.x86_64 : The GNU data compression program
Repo        : base
Matched from:
Filename    : /usr/bin/gzip



gzip-1.5-11.el7_9.x86_64 : The GNU data compression program
Repo        : updates
Matched from:
Filename    : /usr/bin/gzip



gzip-1.5-11.el7_9.x86_64 : The GNU data compression program
Repo        : @updates
Matched from:
Filename    : /usr/bin/gzip



[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# 
#dnf在查找的时候,分别检查了两个仓库,本地系统和默认的fedora仓库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 9.3.2 使用dnf安装软件

#dnf install package_name
#yum install package_name

[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# yum install zsh
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
base                                                                                                           | 3.6 kB  00:00:00     
docker-ce-stable                                                                                               | 3.5 kB  00:00:00     
epel                                                                                                           | 4.7 kB  00:00:00     
extras                                                                                                         | 2.9 kB  00:00:00     
updates                                                                                                        | 2.9 kB  00:00:00     
(1/5): epel/x86_64/group_gz                                                                                    | 100 kB  00:00:00     
(2/5): epel/x86_64/updateinfo                                                                                  | 1.0 MB  00:00:00     
(3/5): epel/x86_64/primary_db                                                                                  | 7.0 MB  00:00:00     
(4/5): docker-ce-stable/7/x86_64/primary_db                                                                    | 123 kB  00:00:00     
(5/5): updates/7/x86_64/primary_db                                                                             |  25 MB  00:00:00     
Resolving Dependencies
--> Running transaction check
---> Package zsh.x86_64 0:5.0.2-34.el7_8.2 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

======================================================================================================================================
 Package                    Arch                          Version                                   Repository                   Size
======================================================================================================================================
Installing:
 zsh                        x86_64                        5.0.2-34.el7_8.2                          base                        2.4 M

Transaction Summary
======================================================================================================================================
Install  1 Package

Total download size: 2.4 M
Installed size: 5.6 M
Is this ok [y/d/N]: y
Downloading packages:
zsh-5.0.2-34.el7_8.2.x86_64.rpm                                                                                | 2.4 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : zsh-5.0.2-34.el7_8.2.x86_64                                                                                        1/1 
  Verifying  : zsh-5.0.2-34.el7_8.2.x86_64                                                                                        1/1 

Installed:
  zsh.x86_64 0:5.0.2-34.el7_8.2                                                                                                     
Complete!
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 9.3.3 使用dnf升级软件

查看已安装的软件包所有可用的更新:

dnf list upgrades
yum list updates
[root@iZuf6fdhtuwmr11b7hkk8pZ saless]# yum list updates
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
Updated Packages
docker-ce.x86_64                                                3:25.0.1-1.el7                                        docker-ce-stable
docker-ce-cli.x86_64                                            1:25.0.1-1.el7                                        docker-ce-stable
docker-ce-rootless-extras.x86_64                                25.0.1-1.el7                                          docker-ce-stable
docker-compose-plugin.x86_64                                    2.24.2-1.el7                                          docker-ce-stable


#如果想要升级一个软件包的话
dnf upgrade package_name
对应的yum命令是:
yum upgrade package_name

如果是想升级列表中所有的软件包:
dnf upgrade
yum upgrade
由于不能轻易升级,在这里就不演示命令了。
另外,dnf还有一个很不错的特性,就是upgrade-minimal命令,他会将软件包升级至最新的bug修复版或者安全补丁版,而不是最新的最高版本。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 9.3.4 使用dnf卸载软件

dnf remove package_name
但是不会保留配置我呢间 或  数据文件
1
2

# 9.3.5 处理损坏的依赖关系

有时候在安装多个软件包的时候,一个软件包的依赖关系可能会被另一个软件包搞乱,这被称为依赖关系损坏。

当出现这种情况时,可以使用
dnf clean all 

如果还是不行 可以使用
dnf repoquery --deplist package_name
对应的yum的命令是:yum deplist xterm
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# yum deplist xterm
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
package: xterm.x86_64 295-3.el7_9.1
  dependency: /bin/sh
   provider: bash.x86_64 4.2.46-35.el7_9
  dependency: libICE.so.6()(64bit)
   provider: libICE.x86_64 1.0.9-9.el7
  dependency: libX11.so.6()(64bit)
   provider: libX11.x86_64 1.6.7-4.el7_9
  dependency: libXaw.so.7()(64bit)
   provider: libXaw.x86_64 1.0.13-4.el7
  dependency: libXft.so.2()(64bit)
   provider: libXft.x86_64 2.3.2-2.el7
  dependency: libXmu.so.6()(64bit)
   provider: libXmu.x86_64 1.1.2-2.el7
  dependency: libXpm.so.4()(64bit)
   provider: libXpm.x86_64 3.5.12-2.el7_9
  dependency: libXt.so.6()(64bit)
   provider: libXt.x86_64 1.1.5-3.el7
  dependency: libc.so.6(GLIBC_2.15)(64bit)
   provider: glibc.x86_64 2.17-326.el7_9
  dependency: libfontconfig.so.1()(64bit)
   provider: fontconfig.x86_64 2.13.0-4.3.el7
  dependency: libtinfo.so.5()(64bit)
   provider: ncurses-libs.x86_64 5.9-14.20130511.el7_4
  dependency: libutempter.so.0()(64bit)
   provider: libutempter.x86_64 1.1.6-4.el7
  dependency: rtld(GNU_HASH)
   provider: glibc.x86_64 2.17-326.el7_9
   provider: glibc.i686 2.17-326.el7_9
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

yum工具的upgrade命令支持--skip-broken选项,可以跳过依赖关系损坏的软件包,同时仍尝试继续升级其他包。dnf工具则自动执行该操作。

# 9.3.6 RPM仓库

#查看当前拉取软件的仓库
dnf repolist

如果没有配置的软件,可以配置仓库,编写文件:
/etc/dnf/dnf.conf
/etc/yum.repos.d目录中的单独文件
cat CentOS-Base.repo 
1
2
3
4
5
6
7

# 9.4 使用容器管理软件

计算带来了应用程序打包方式的一种新范式:应用程序容器(application container)。应用程序容器创建了一个环境,其中包含了应用程序运行所需的全部文件,包括运行时库文件。开发人员随后可以将应用程序容器作为单个软件包分发,保证能够在任何Linux系统中正常运行。

# 9.4.1 使用snap容器

#检查snap是否正在运行
snap version

#如果正在运行 可以使用snap list命令查看当前已安装的snap应用程序列表
snap list

#snap find命令可以在snap仓库中搜索指定程序。
snap find solitaire

#snap info命令可以查看snap应用程序的详细信息
snap info solitaire

#snap install命令可以安装新的snap
snap install

#删除某个snap
snap remove solitaire

#禁用某个snap
snap disable solitaire

#启用某个snap
snap enable solitaire
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 9.4.2 使用flatpak容器

#如果正在运行 可以使用flatpak list命令查看当前已安装的flatpak应用程序列表 还可以检查安装是否正确
flatpak list

#flatpak search命令可以在flatpak仓库中搜索指定程序。
flatpak search solitaire

#flatpak install命令可以安装新的flatpak
flatpak install

#删除某个flatpak
flatpak uninstall solitaire

1
2
3
4
5
6
7
8
9
10
11
12

# 9.5 从源代码安装

1、使用tar命令解压
2、运行configure工具,检查Linux是否拥有合适的能够编译源代码的编译器。

注意 大多数Linux程序是用C或C++编程语言编写的。要在系统中编译这些源代码,需要安装gcc软件包和make软件包。大多数Linux桌面发行版默认没有安装。如果configure显示的错误是由于缺少这部分引起的,请查阅你的Linux发行版文档,了解还需要安装哪些软件包。

3、执行make命令
4、执行make install命令
1
2
3
4
5
6
7

# 10 文本编辑器

10.1 vim编辑器

10.2 nano编辑器

10.3 Emacs编辑器

10.4 KWrite编辑器

10.5 Kate 编辑器

10.6 GNOME编辑器

# 11 构建基础脚本

# 11.1 使用多个命令

#命令行最大的字符255  一个简单的脚本
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# date ; who
Tue Jan 30 22:50:28 CST 2024
root     pts/0        2024-01-30 22:50 (36.249.178.76)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6

# 11.2 创建shell脚本文件

在创建shell脚本文件时,必须在文件第一行指定要使用的shell:

#!/bin/bash

#运行脚本有两种方法 
1、将放置shell脚本文件的目录添加到PATH环境变量中
2、在命令行中使用绝对路径或者相对路径你来引用shell脚本文件
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# test1
-bash: test1: command not found
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
#没有权限
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test1
-bash: ./test1: Permission denied
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l test1
-rw-r--r-- 1 root root 37 Jan 30 22:56 test1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod u+x test1

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test1
#这种方式就是以路径来引用脚本文件
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test1
Tue Jan 30 22:58:55 CST 2024
root     pts/0        2024-01-30 22:50 (36.249.178.76)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 11.3 显示消息

#echo命令可用单引号或双引号来划定字符串

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo "Rich says 'xx' "
Rich says 'xx' 
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo 'Rich says "xx" '
Rich says "xx" 
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

#如果想要把字符串和命令输出在同一行,可以使用echo的-n选项
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test1
The time and date are:Tue Jan 30 23:06:00 CST 2024
root     pts/0        2024-01-30 22:50 (36.249.178.76)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test1
#!/bin/bash
echo -n "The time and date are:"
date
who
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 11.4 使用变量

变量允许在shell脚本中临时存储信息,以便同脚本中的其他命令一起使用。

# 11.4.1 环境变量

#使用set命令可以列出来所有的环境变量
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# set
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_COMPLETION_COMPAT_DIR=/etc/bash_completion.d
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.2.46(2)-release'
COLUMNS=134
DIRSTACK=()
EUID=0
GROUPS=()
HISTCONTROL=ignoredups
HISTFILE=/root/.bash_history
HISTFILESIZE=1000
HISTSIZE=1000
HOME=/root
HOSTNAME=iZuf6fdhtuwmr11b7hkk8pZ
HOSTTYPE=x86_64
ID=0
IFS=$' \t\n'
LANG=en_US.UTF-8
LESSOPEN='||/usr/bin/lesspipe.sh %s'
LINES=36

#echo命令中的环境变量会在脚本运行中被替换成当前值。
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test2
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l test2
-rw-r--r-- 1 root root 76 Jan 30 23:37 test2
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod u+x test2
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test2
User info for userid:root
UID:0
HOME:/root
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test2
#!/bin/bash
echo "User info for userid:$USER"
echo UID:$UID
echo HOME:$HOME

#反斜线允许shell脚本按照字面意思解释$ 另外还可以使用${variable}
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test2
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test2
User info for userid:root
UID:0
HOME:/root
The cost of the item is 5
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test2
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test2
User info for userid:root
UID:0
HOME:/root
The cost of the item is /5
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test2
User info for userid:root
UID:0
HOME:/root
The cost of the item is $15
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test2
#!/bin/bash
echo "User info for userid:$USER"
echo UID:$UID
echo HOME:$HOME

echo "The cost of the item is \$15"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 11.4.2 用户自定义变量

除了环境变量,shell脚本还允许用户在脚本中定义和使用自己的变量。

用户自定义变量的名称可以是任何由字母、数字或下划线组成的字符串,长度不能超过20个字符。并且区分大小写,使用等号赋值,并且等号两边不能出现空格。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test3
guest is shiyan,in 10 days ago
guest is lulu,in 10 days ago
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test3
#!/bin/bash
days=10
guest="shiyan"
echo "guest is $guest,in $days days ago"
day=5
guest="lulu"
echo "guest is $guest,in $days days ago"
1
2
3
4
5
6
7
8
9
10
11

每次引用变量 都会输出该变量的当前值,引用的时候需要加$符号,赋值的时候不用加$。

# 11.4.3 命令替换

有两种方法可以将命令输出赋给变量

反引号``

$()格式

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# testing=`date`
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $testing
Wed Jan 31 11:48:21 CST 2024
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# testing=$(date)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $testing
Wed Jan 31 11:49:02 CST 2024

#获得当前日期来生成唯一文件名
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test5
#!/bin/bash
today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test5
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls
1.txt  file1  log.240131  saless                               test   test2  test5
2.txt  file2  sales       springboot_01_03-0.0.1-SNAPSHOT.jar  test1  test3  testdir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意:today变量保存着格式化的date命令。日志文件的内容是将

ls /usr/bin -al > log.$today 这个命令的内容输出到log.这个文件中。

警告 命令替换会创建出子shell来运行指定命令,这是由运行脚本的shell所生成的一个独立的shell,因此,在子shell中运行的命令无法使用脚本中的变量。

如果在命令行中./路径执行命令,就会创建子shell,如果不加路径,则不会创建子shell。不过内建的shell命令也不会创建子shell。在命令行中运行脚本时要当心。

# 11.5 重定向输入和输出

有时候,我们想要保存命令的输出到不仅仅是显示屏,所以提供了几种方法来实现。

# 11.5.1 输出重定向

#使用大于号实现输出发送到文件
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l > test6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -l test6
-rw-r--r-- 1 root root 874 Jan 31 12:45 test6
#有时候,你想在文件后追加数据,而不是覆盖,可以使用>>来实现,因为单个>会覆盖原来的文件
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# who >> test6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test6
total 22704
-rw-r--r--  1 root root           0 Jan 26 10:17 1.txt
-rw-rw-r--  1 root sharing        0 Jan 26 10:25 2.txt
-rw-r--r--  1 root root          28 Dec 28 09:03 file1
-rw-r--r--  1 root root         191 Dec 28 09:04 file2
-rw-r--r--  1 root root       57223 Jan 31 12:39 log.240131
drwxr-xr-x  2 root root        4096 Jan 28 16:00 sales
drwxr-xr-x+ 2 root root        4096 Jan 28 16:08 saless
-rw-r--r--  1 root root    23153747 Nov  5 14:35 springboot_01_03-0.0.1-SNAPSHOT.jar
-rw-r--r--+ 1 root root           0 Jan 28 15:51 test
-rwxr--r--  1 root root          54 Jan 30 23:05 test1
-rwxr--r--  1 root root         113 Jan 30 23:43 test2
-rwxr--r--  1 root root         136 Jan 30 23:54 test3
-rwxr--r--  1 root root          63 Jan 31 12:36 test5
-rw-r--r--  1 root root           0 Jan 31 12:45 test6
drwxr-sr-x  2 root sharing     4096 Jan 27 23:15 testdir
root     pts/0        2024-01-31 11:48 (117.28.134.235)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 11.5.2 输入重定向

输入重定向运算符是小于号(<)

#wc命令可以统计数据中的文本,在默认情况下,他会输出三个值
1、文本的行数
2、文本的单词数
3、文本的字节数
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# wc < test6
 17 142 930

#另外还有一种输入重定向的方法,就是内联输入重定向,使用符号双小于号
除了这个符号,必须指定一个文本标记来划分输入数据的起止。
command << marker
data
markfer

例如:
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# wc << marker
> asdad
> dasd
> asda
> marker
 3  3 16
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#但是我们一般使用EOF来标记
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# wc << EOF
> 1
> 1
> 2
> 2
> 
> 33
> 3
> 3
> 3
> 3
> EOF
10  9 20
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 11.6 管道

有时候,我们需要将一个命令的输出作为另一个命令的输入,这时候就可以通过重定向实现,

#rpm -qa用来查看一些已经安装的文件
rpm -qa >rpm.list
sort < rpm.list

rpm -qa | sort | more
rpm -qa | sort | less

1
2
3
4
5
6
7

# 11.7 执行数学运算

# 11.7.1 expr命令

shell脚本中,执行数学运算有两种方式。

#+两边必须有空格
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# expr 1+5
1+5
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# expr 1 + 5
6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7

image-20240201151708655

#需要在容易被误解的符号前边加上\反斜线进行转义
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# expr 5 * 2
expr: syntax error
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# expr 5 \* 2
10
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#在脚本中使用expr命令计算
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test7
The result is 1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test7
#!/bin/bash
var1=10
var2=10
var3=$(expr $var2 / $var1)
echo The result is $var3
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 11.7.2 使用方括号

在bash中,要将数学运算结果赋给变量,可以使用$和方括号$[]

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# var1=$[1 + 5]
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $var1
6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# var2=$[$var1 * 2]
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $var2
12
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

#这种方式也适用于脚本  这时候*号不用加\转义了,因为方括号清楚里边的符号不是通配符
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test8
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test8
500
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test8
#!/bin/bash
var1=100
var2=50
var3=45
var4=$[$var1 * ($var2 - $var3)]
echo $var4
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


shell脚本中的数学运算符只支持证书运算。例如 100 / 45 得到的结果是2
z shell提供了完整的浮点数操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 11.7.3 浮点数解决方案

有几种方法能够解决bash只支持整数运算的限制。

1、bc

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
12 * 5.4
64.8
1 * 3.2
3.2
4 / 3
1
scale=5   #设置几位小数,如果不设置,不会保留小数
4 /3
1.33333
quit
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

bc -q可以设置进入bc的时候不显示欢迎信息。另外在bc中符号两边不用带空格也可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2、在脚本中使用bc

用命令替换来运行bc,然后将输出赋给变量。

variable=$(echo "options; expression" | bc)

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# var1=$(echo "scale=4; 3.44/5" | bc)
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $var1
.6880
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

另外还可以使用内联输入重定向
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim test9
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod u+x test9
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test9
Thefinal answer for this mess is 2813.9882
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test9
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71

var5=$(bc << EOF
scale = 4
a1 = ($var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)

echo Thefinal answer for this mess is $var5
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 11.8 退出脚本

退出状态码是一个0~255的整数值,在命令结束时由其传给shell,我们可以获取这个值并在脚本中使用。

# 11.8.1 查看退出状态码

Linux提供了专门的变量$?来保存最后一个已执行命令的推出状态码。

#可以看到成功的话状态码是0,
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# data
-bash: data: command not found
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $?
127
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# date
Thu Feb  1 16:45:26 CST 2024
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $?
0
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]
1
2
3
4
5
6
7
8
9
10

image-20240201164751573

# 11.8.2 exit命令

默认状态下,shell脚本会以最后一个命令的推出状态码退出,但是我们可以改变这种情况,exit命令允许我们在脚本结束时指定一个退出状态码:

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test10
var3 is 20
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test10
#!/bin/bash
var1=10
var2=10
var3=$[$var1 + $var2]
echo var3 is $var3
exit 6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test10
var3 is 20
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $?
6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

#也可以使用变量作为exit的参数
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# chmod u+x test11
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test11
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo $?
10
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test10
#!/bin/bash
var1=10
var2=10
var3=$[$var1 + $var2]
echo var3 is $var3
exit 6
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


#但是退出码最大的值是255,如果超过255 会对255取余。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 11.9 实战演练

写一个脚本,计算出来两个时间差的天数。

#date命令允许使用-d选项指定特地给日期(以任意格式),所以我们可以定义其他方式输出日期,然后利用纪元时间。
#纪元时间指的是将时间指定为1970年1月1日午夜后的整数秒。
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test12
diff is 1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test12
#!/bin/bash
time1=$(date -d "2024-1-1" +%s)
time2=$(date -d "2024-1-2" +%s)
diff=$[($time2-$time1) / (24 * 60 * 60)]
echo diff is $diff
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12

# 12 结构化命令

本章将介绍if-then语句和case语句

# 12.1 使用if-then语句

#if-then的基本格式如下
if command then
	commands fi
	
#因为pwd命令的退出码是0,所以执行了echo这行命令,如果不是0,那么不会执行	

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test1.sh
/root
it work
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test1.sh
#!/bin/bash
if pwd
then
 echo "it work"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

第15章将会降到不想看到错误信息的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 12.2 if-then-else语句

格式如下
if commend then
   commendselse
   commandsfi
   

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test2.sh
#!/bin/bash
if test 1==1; then  #then如果想要和if一行中,必须加一个分号,否则会报错
   echo "true"
else echo "false"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test2.sh
true
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 12.3 嵌套if语句

ls -d :只显示目录,但不显示目录内容。
ls -sh :以人类易读的格式显示文件大小
ls -g : 在文件长列表中不显示文件属主
ls -o :在文件长列表中不显示文件属组


if command1then
   command set 1elif command2then
   command set 2elif command3then
   command set 3elif command4then
   command set 4elif command5then
   command set 5fi
1
2
3
4
5
6
7
8
9
10
11
12

# 12.4 test命令

test命令可以在if-then语句中测试出不同的条件,如果test命令中列出的条件成立,那么test命令就会推出并返回状态码0,另外,判断数值是否相等不是简单地使用 ==

test不仅仅可以判断条件是否成立,还可以判断变量是否为空,如果是空,那么返回的状态码也不是0;

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test3.sh
不为空
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test3.sh
#!/bin/bash
variable="FULL"
if test $variable
then
   echo "不为空"
else
   echo "空"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
当variable=“”时,输出就是空。
1
2
3
4
5
6
7
8
9
10
11
12
13

另外,bash shell提供了另一种条件测试方式,不用再写test命令了。

格式如下: 第一个括号之后和第二个括号之前必须留有空格,否则会报错。

if [ command ]
then
   command
1
2
3

-eq 等于

-ge 大于等于

-gt 大于

-le 小于等于

-lt 小于

-ne 不等于

# 12.4.1 数值比较

image-20240202104147115

上图列出了测试两个值时可用的条件参数

另外,对于条件测试,bash shell只能用于处理整数。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test4.sh
eq
ge
gt
le
lt
ne
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test4.sh
#!/bin/bash
var1=1
var2=1
var3=3
var4=5
var5=6
if [ $var1 -eq $var2 ];then
   echo "eq"
else
   echo "not eq"
fi
if [ $var1 -ge $var2 ];then
   echo "ge"
else
   echo "not ge"
fi

if [ $var3 -gt $var1 ];then
   echo "gt"
else
   echo "not gt"
fi

if [ $var3 -le $var4 ];then
   echo "le"
else
   echo "not le"
fi

if [ $var4 -lt $var5 ];then
   echo "lt"
else
   echo "not lt"
fi
if [ $var4 -ne $var5 ];then
   echo "ne"
else
   echo "eq"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 12.4.2 字符串比较

image-20240202110259326

1、字符串相等性

在比较字符串的相等性时,比较测试会将所有的标点和大小写情况都考虑在内。

2、字符串顺序

小于号和大于号必须转义,否则shell会将其看作重定向符,将字符串值当作文件名。使用(\)转义

大于和小于与sort命令所采用的不同。

#只会比较第一个字母
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test5.sh
大于
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test5.sh
#!/bin/bash
string1=zoccer
string2=aorbfootball

if [ $string1 \> $string2 ]
then 
   echo "大于"
else
   echo "小于"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

比较中,用来比较的是用的Unicode码值,而sort命令使用的是系统的语言环境谁知中定义的排序顺序。所以这两种方式比较大小是不一样的。

3、字符串大小

-n 和 -z可以很方便的用于检查一个变量是否为空


[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test6.sh

1不为空
2为空
2为空
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test6.sh
#!/bin/bash
string1="1"
string2=''

echo $string2
if [ -z "$string1" ]
then
   echo "1为空"
else
   echo "1不为空"
fi

if [ -z "$string2" ]
then
   echo "2为空"
else
   echo "2不空"
fi

if [ -n "$string2" ]
then
   echo "2不为空"
else
   echo "2为空"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 12.4.3 文件比较

文件比价常用的命令

image-20240202141504180

# 1、检查目录

-d测试回检查指定的目录是否存在与系统中。如果打算将文件写入目录,或者是准备切换到某个目录,那么先测试一下总是好事。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./jump_point.sh 
目录不存在
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat jump_point.sh 
#!/bin/bash
jump_directory=/home/Torfa

if [ -d $jump_directory ]
then
   echo "目录存在"
else
   echo "目录不存在"
fi

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#下边是目录存在的情况
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./jump_point.sh 
目录存在
test1.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat jump_point.sh 
#!/bin/bash
jump_directory=/home/Torfa

if [ -d $jump_directory ]
then
   echo "目录存在"
   cd $jump_directory
   ls
else
   echo "目录不存在"
fi

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 2、检查对象是否存在

-e测试允许在使用文件或者目录前先检查其是否存在。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./update_file.sh 
/root 这个目录存在
现在开始检查文件sentinel 是否存在
sentinel 不存在
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat update_file.sh 
#!/bin/bash
location=$HOME
file_name="sentinel"

if [ -d $location ]
then
   echo "$location 这个目录存在"
   echo "现在开始检查文件$file_name 是否存在"
   if [ -e $location/$file_name ]
   then
      echo "$file_name 存在于 $location 中"
      date >> $location/$file_name
   else
      echo "$file_name 不存在"
   fi
else
   echo "$location 不存在"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#创建一个名为sentinel的文件
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# vim sentinel
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./update_file.sh 
/root 这个目录存在
现在开始检查文件sentinel 是否存在
sentinel 存在于 /root 中
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat sentinel 
"测试文件"
Fri Feb  2 14:39:24 CST 2024
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#发现可以成功更新文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 3、检查文件

如果要确定指定对象为文件,那就必须使用-f 测试。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./dir-or-file.sh 
正在准备检查:/root
对象/root 存在
/root 是一个文件夹
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat dir-or-file.sh 
#!/bin/bash

object_name=$HOME
echo "正在准备检查:$object_name"
if [ -e $object_name ]
then
   echo "对象$object_name 存在"
   if [ -f $object_name ]
   then
      echo "$object_name 是一个文件"
	else
	   echo "$object_name 是一个文件夹"
	fi
else
   echo "$object_name 不存在"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 4、检查是否可读

在尝试从文件中读取数据之前,最好先使用-r测试检查一下文件是否可读

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./can-read-it.sh 
 检查是否可读 :/etc/shadow
准备展示文件:
sshd:!!:19619::::::
postfix:!!:19619::::::
chrony:!!:19619::::::
nscd:!!:19619::::::
tcpdump:!!:19619::::::
rpc:!!:19619:0:99999:7:::
rpcuser:!!:19619::::::
nfsnobody:!!:19619::::::
test:$6$WGRnUcwL$ZPo1kS8JudeSrXsXUwwCaOSTuMKeL1BfpA61CnzrxQ93YgwL7ejz7Su0gvOceNKlEcAf.4opQm5g2bHemsyEh1:0:0:99999:7:::
rich:!!:19747:0:99999:7:::
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat can-read-it.sh 
#!/bin/bash
pwfile=/etc/shadow

echo " 检查是否可读 :$pwfile"

if [ -f $pwfile ]
then
   if [ -r $pwfile ]
   then
      echo "准备展示文件"tail $pwfile
	else
	   echo "抱歉,您没有权限"
	fi
else
   echo "$pwfile不存在"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 5、检查空文件

我们应该使用-s测试检查文件是否为空,油漆是当我们不想删除非空文件时。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./is-it-empty.sh 

Checking if /root/sentinel file is empty...

The /root/sentinel 文件存在并且不为空
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat is-it-empty.sh 
#!/bin/bash
# Check if a file is empty
#
file_name=$HOME/sentinel
echo
echo "Checking if $file_name file is empty..."
echo

if [ -f $file_name ]
then
     if [ -s $file_name ]
     then
          echo "The $file_name 文件存在并且不为空"
     else
          echo "The $file_name 文件存在并且是空"
          echo "删除文件..."
          rm $file_name
     fi
#
else
     echo "The $file_name 文件不存在"
fi[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 6、检查是否可写

-w测试可以检查是否对文件拥有可写权限。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./can-write-to-it.sh 
 检查是否可写 :/root/sentinel
准备写入文件:
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# tail sentinel
"测试文件"
Fri Feb  2 14:39:24 CST 2024
Fri Feb  2 14:49:13 CST 2024
1536
1536
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat can-write-to-it.sh 
#!/bin/bash
item=$HOME/sentinel

echo " 检查是否可写 :$item"

if [ -f $item ]
then
   if [ -w $item ]
   then
      echo "准备写入文件"date +%H%M >> $item
	else
	   echo "抱歉,您没有权限"
	fi
else
   echo "$item不存在或者不是一个文件"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 7、检查文件是否可以执行

-x测试检查文件是否有执行权限

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./can-run-it.sh 

Checking if you can run /root/can-write-to-it.sh...
你可以执行 /root/can-write-to-it.sh.
Running /root/can-write-to-it.sh...
 检查是否可写 :/root/sentinel
准备写入文件:
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat can-run-it.sh 
#!/bin/bash
# Check if you can run a file
#
item_name=$HOME/can-write-to-it.sh
echo
echo "Checking if you can run $item_name..."
if [ -x $item_name ]
then
     echo "你可以执行 $item_name."
     echo "Running $item_name..."
     $item_name
else
     echo "抱歉,你不能执行 $item_name."
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 8、检查所有权

-O测试可以检查你是否是文件的属主。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./do-i-own-it.sh 
你是这个文件的属主
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# whoami
root
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat do-i-own-it.sh 
#!/bin/bash
if [ -O /etc/passwd ]
then
   echo "你是这个文件的属主"
else
   echo "你不是这个文件的属主"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 9、检查默认属组关系

-G 测试可以检查文件的属组。-G只会检查默认组而非用户所属的所有组。

# 10、检查文件日期

-nt会判定一个文件是否比另一个文件更新。

-ot会判定一个文件是否比另一个文件更旧。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./check_file_dates.sh 
test2比test1更新
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat check_file_dates.sh 
#!/bin/bash
if [ $HOME/test1 -nt $HOME/test2 ]
then
     echo "test1比test2更新"
else
     echo "test2比test1更新"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12

需要注意的是,这两种测试都不会检查文件是否存在,所以在使用-nt和-ot测试之前,请保证文件必须存在,使用-e测试。

# 12.5 符合条件测试

#if的测试中可以使用布尔逻辑将测试调价组合起来
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./andboolean.sh 
You cannot write to the file.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# touch newfile
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./andboolean.sh 
The file exists and you can write to it.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat andboolean.sh 
#!/bin/bash
if [ -d $HOME ] && [ -w $HOME/newfile ]
then
     echo "The file exists and you can write to it."
else
     echo "You cannot write to the file."
fi

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 12.6 if-then的高级特性

bash shell还提供了三个可以在if-then语句中使用的高级特性

  • 在子shell中执行命令的单括号
  • 用于数学表达式的双括号
  • 用于高级字符串处理功能的双方括号

# 12.6.1 使用单括号

单括号允许在if语句中使用子shell(子shell的用法参见第5章)。

在bash shell执行command之前,会先创建一个子shell,然后在其中执行命令。如果命令成功结束,则退出状态码(参见第11章)会被设为0,then部分的命令就会被执行。如果命令的退出状态码不为0,则不执行then部分的命令。


#!/bin/bash
# Testing a single parentheses condition
#
echo $BASH_SUBSHELL
#
if (echo $BASH_SUBSHELL)
then
     echo "The subshell command operated successfully."
#
else
     echo "The subshell command was NOT successful."
#
fi
$
$ ./SingleParentheses.sh
01
The subshell command operated successfully.
$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 12.6.2 使用双括号

双括号命令允许在比较过程中使用高级数学表达式。test命令在进行比较的时候只能使用简单的算术操作。双括号命令提供了更多的数学符号,这些符号对有过其他编程语言经验的程序员而言并不陌生。

image-20240202164035140

#双括号中赋值变量的时候等号两边可以有空格
#双括号中表达式的大于号不用转义
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./doubleparenteses.sh 
var1:10,var2:100
大于90
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat doubleparenteses.sh 
#!/bin/bash
var1=10
if (( $var1 ** 2 > 10 ))
then
  ((var2 = $var1 ** 2))
  echo "var1:$var1,var2:$var2"
  echo "大于90"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 12.6.3 使用双方括号

双方括号命令提供了针对字符串比较的高级特性

双方括号中可以使用test命令中的标准字符串比较,另外它还提供了test命令不具备的另一个特性 -- 模式匹配。需要注意的是并不是所有的shell都支持双括号。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./duoblebracket.sh 
你使用的bash shell 版本不是5
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat duoblebracket.sh 
#!/bin/bash
if [[ $BASH_VERSION == 5.* ]]
then
    echo "你使用的bash shell 版本是5"
else
    echo "你使用的bash shell 版本不是5"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12

# 12.7 case命令

#case的格式如下:
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac

*会匹配所有不符合的模式

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./ShortCase.sh 
Sorry, you are not allowed here.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat ShortCase.sh
#!/bin/bash
sy="xx"
case $sy in
rich | christine)
     echo "Welcome $sy"
     echo "Please enjoy your visit.";;
barbara | tim)
     echo "Hi there, $sy"
     echo "We're glad you could join us.";;
testing)
     echo "Please log out when done with test.";;
*)
     echo "Sorry, you are not allowed here."
esac
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

#将sy改成testing之后
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./ShortCase.sh 
Please log out when done with test.
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat ShortCase.sh
#!/bin/bash
sy="testing"
case $sy in
rich | christine)
     echo "Welcome $sy"
     echo "Please enjoy your visit.";;
barbara | tim)
     echo "Hi there, $sy"
     echo "We're glad you could join us.";;
testing)
     echo "Please log out when done with test.";;
*)
     echo "Sorry, you are not allowed here."
esac
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 12.8 实战演练

#!/bin/bash
# Checks system for popular package managers
#
#################### User Introduction ######################
echo "########################################################"
echo
echo "     This script checks your Linux system for popular"
echo "package managers and application containers, lists"
echo "what's available, and makes an educated guess on your"
echo "distribution's base distro (Red Hat or Debian)."
echo
echo "#######################################################"
#
##################### Red Hat Checks #######################
#
echo
echo "Checking for Red Hat-based package managers &"
echo "application containers..."
#####
if (which rpm &> /dev/null)
then
     item_rpm=1
     echo "You have the basic rpm utility."
#
else
     item_rpm=0
#
fi
####
if (which dnf &> /dev/null)
then
     item_dnfyum=1
     echo "You have the dnf package manager."
#
elif (which yum &> /dev/null)
then
     item_dnfyum=1
     echo "You have the yum package manager."
else
     item_dnfyum=0
#
fi
####
if (which flatpak &> /dev/null)
then
     item_flatpak=1
     echo "You have the flatpak application container."
#
else
     item_flatpak=0
#
fi
####
redhatscore=$[$item_rpm + $item_dnfyum + $item_flatpak]
#
##################### Debian Checks #######################
#
echo
echo "Checking for Debian-based package managers &"
echo "application containers..."
#####
if (which dpkg &> /dev/null)
then
     item_dpkg=1
     echo "You have the basic dpkg utility."
#
else
     item_dpkg=0
#
fi
####
if (which apt &> /dev/null)
then
     item_aptaptget=1
     echo "You have the apt package manager."
#
elif (which apt-get &> /dev/null)
then
     item_aptaptget=1
     echo "You have the apt-get/apt-cache package manager."
#
else
     item_aptaptget=0
fi
####
if (which snap &> /dev/null)
then
     item_snap=1
     echo "You have the snap application container."
#
else
    item_snap=0
#
fi
####
#
debianscore=$[$item_dpkg + $item_aptaptget + $item_snap]
#
#
##################### Determine Distro #######################
#
echo
if [ $debianscore -gt $redhatscore ]
then
   echo "Most likely your Linux distribution is Debian-based."
   #
elif [ $redhatscore -gt $debianscore ]
then
   echo "Most likely your Linux distribution is Red Hat-based."
else
   echo "Unable to determine Linux distribution base."
fi
#
echo
#
#############################################################
#
exit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

# 13 更多的结构化命令

# 13.1 for命令

for命令的基本格式如下:

for var in listdo
    commandsdone
1
2

# 13.1.1 读取列表中的值

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./bin/test1
-bash: ./bin/test1: No such file or directory
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test1
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test1
#!/bin/bash
# basic for command

for test in Alabama Alaska Arizona Arkansas California Colorado
do
   echo The next state is $test
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 13.1.2 读取列表中复杂值

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./badtest1 
The next state is I
The next state is dont know if thisll
The next state is work
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./badtest1 
The next state is I
The next state is don't
The next state is know
The next state is if
The next state is this'll
The next state is work
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat badtest1 
#!/bin/bash
# basic for command

for test in I don\'t know if "this'll" work
do
   echo The next state is $test
done[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

for循环假定每个字符之间是空格分隔的

当出现一个单词本来就是多个单词组成的时候,需要用双引号将其包裹住。shell并不会将双引号当成值的一部分。

# 13.1.3 从变量中读取值列表

但是有时候,我们会将一系列单词放到一个字符串中,我们又想分割出来一个字符串中的单词,

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test2.sh 
Have you ever visited Alabama?
Have you ever visited Alaska?
Have you ever visited Arizona?
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Connecticut?
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test2.sh
#!/bin/bash
# using a variable to hold the list

list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"  #这是一种向已有的字符串尾部添加文本的一种方法

for state in $list
do
   echo "Have you ever visited $state?"
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

脚本中,$list变量包含了用于迭代的值列表,

# 13.1.4 从命令中读取值列表

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test3.sh 
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test3.sh 
#!/bin/bash

file="status.txt"

for set in $(cat $file)
do echo $set
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注意,在上边的脚本中,我们的status.txt并没有给出绝对路径,是因为这个txt文件和我们的脚本在同一个目录中,如果不是这样的话,我们需要给出绝对路径。

# 13.1.5 更改字段分隔符

上一小节中,我们的单词之间都是用换行符分割的,这样子也可以成功执行,这是为什么呢?

因为bash shell默认情况下会将 空格 制表符 和 换行符视为字段分隔符。

如果想在脚本中指定某一种分隔符,那么可以更换IFS环境变量的值。

#只能识别换行符可以这么写
IFS=$'\n'
1
2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test3.sh
#!/bin/bash

file="status.txt"
IFS=$'\n'
for set in $(cat $file)
do echo $set
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test3.sh 
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在处理代码量较大的脚本的时候,我们可以先修改一下IFS的值,等这段代码执行完了再变回来:

IFS.OLD=$IFS
IFS=$'\n'
<在代码中使用新的IFS>
IFS=$IFS.OLD

#指定分隔符为冒号
IFS=:

#指定分隔符为多个
IFS=$'\n:;"'
1
2
3
4
5
6
7
8
9
10

# 13.1.6 使用通配符读取目录

我们还可以使用for命令来遍历目录中的文件,为此,我们必须使用通配符

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test4.sh 
/root/1.txt is a file
/root/2.txt is a file
/root/andboolean.sh is a file
/root/bash1 is a directory
/root/can-read-it.sh is a file
/root/can-run-it.sh is a file
/root/can-write-to-it.sh is a file
/root/check_default_group.sh is a file
/root/check_file_dates.sh is a file
/root/dir-or-file.sh is a file
/root/do-i-own-it.sh is a file
/root/doubleparenteses.sh is a file
/root/duoblebracket.sh is a file
/root/file1 is a file
/root/file2 is a file
/root/is-it-empty.sh is a file
/root/jump_point.sh is a file
/root/log.240131 is a file
/root/newfile is a file
/root/PackageMgrCheck.sh is a file
/root/rpm.ist is a file
/root/rpm.list is a file
/root/sales is a directory
/root/saless is a directory
/root/sentinel is a file
/root/ShortCase.sh is a file
/root/springboot_01_03-0.0.1-SNAPSHOT.jar is a file
/root/testdir is a directory
/root/update_file.sh is a file
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test4.sh
#!/bin/bash
# iterate through all the files in a directory

for file in /root/*
do

   if [ -d "$file" ]
   then
      echo "$file is a directory"
   elif [ -f "$file" ]
   then
      echo "$file is a file"
   fi
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 
#脚本中的$file两边是加了双引号的,是因为Linux系统允许空格作为文件名的一部分,如果文件名或者文件夹名中含有空格,那么会被当做两个参数,引发报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

警告 注意,可以在值列表中放入任何东西。即使文件或目录不存在,for语句也会尝试把列表处理完。如果是和文件或目录打交道,那就要出问题了。你无法知道正在遍历的目录是否存在:最好在处理之前先测试一下文件或目录

# 13.2 C语言风格的for命令

# 13.2.1 C语言中的for命令

基本格式如下:

for (( variable assignment ; condition ; iteration process ))
1

需要注意的是,仿C的for循环有些地方和标准的shell循环并不一致

  • 变量赋值可以有空格。
  • 迭代条件中的变量不以美元符号开头。
  • 迭代过程的算式不使用expr命令格式。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test5.sh 
1
2
3
4
5
6
7
8
9
10
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test5.sh 
#!/bin/bash

for ((i = 1;i<=10;i++))
do
   echo $i
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 13.2.2 使用多个变量

仿C命令的for命令也会允许为迭代使用多个变量。循环会单独处理每个变量,我们可以为每个变量定义不同的迭代过程。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test6.sh
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10 - 1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test6.sh
#!/bin/bash
for (( a=1,b=10;a<=10;a++,b--))
do
  echo "$a - $b"
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 13.3 while 命令

# 13.3.1 while的基本格式

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test7.sh
5
4
3
2
1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test7.sh
#!/bin/bash

var1=5
while [ $var1 -gt 0 ]
do
	echo $var1
	var1=$[ $var1 - 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 13.3.2 使用多个测试命令

while命令允许在while语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用于决定是否退出循环。

另外,当存在多个测试命令的时候,每个命令占一行。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test8.sh
5
this is inside the loop 
4
this is inside the loop 
3
this is inside the loop 
2
this is inside the loop 
1
this is inside the loop 
0
this is inside the loop 
-1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test8.sh
#!/bin/bash
var1=5
while echo $var1
	[ $var1 -ge 0 ]
do
	echo "this is inside the loop "
	var1=$[ $var1 - 1 ]
done[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 13.4 until命令

与while命令类型,until的命令语句中也可以放入多个test command命令,可以看到,Linux系统也是当满足最后一个条件的时候才会停止。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test9.sh
5
4
3
2
1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test9.sh
#!/bin/bash

var1=5
until [ $var1 -eq 0 ]
do
	echo $var1
	var1=$[ $var1 - 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test10.sh
#!/bin/bash
var1=5
until echo $var1
	[ $var1 -eq 0 ]
do
	echo "this is inside the loop "
	var1=$[ $var1 - 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test10.sh
5
this is inside the loop 
4
this is inside the loop 
3
this is inside the loop 
2
this is inside the loop 
1
this is inside the loop 
0
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 13.5 嵌套循环

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test11.sh
b: 1
b: 2
b: 3
a:1
b: 1
b: 2
b: 3
a:2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test11.sh
#!/bin/bash
for (( a=1;a<=2;a++ ))
do

  for (( b=1; b<=3;b++ ))
  do
	echo "b: $b"
  done
  
  echo "a:$a"
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test12.sh
a:1
a:2
a:3
var1 : 1
a:1
a:2
a:3
var1 : 2
a:1
a:2
a:3
var1 : 3
a:1
a:2
a:3
var1 : 4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test12.sh
#!/bin/bash
var1=1
while [ $var1 -lt 5 ]
do
    for (( a=1; a<=3; a++ ))
	do
	  echo "a:$a"
	done
	
	echo "var1 : $var1"
	var1=$[$var1 + 1]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test13.sh
大于零
大于零
大于零
var1不等于0
大于零
大于零
大于零
var1不等于0
大于零
大于零
大于零
var1不等于0
大于零
大于零
大于零
var1不等于0
大于零
大于零
大于零
var1不等于0
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test13.sh
#!/bin/bash
var1=5
until [ $var1 -eq 0 ]
do
    var2=3
	while [ $var2 -gt 0 ]
	do
	  echo "大于零"
	  var2=$[$var2 - 1]
	done
	
	echo "var1不等于0"
	var1=$[$var1 - 1]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

# 13.6 循环处理文件数据

当我们需要遍历文件中保存的数据的时候,需要做两件事,1是使用嵌套循环,2是修改IFS环境变量

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test14.sh
这一行的数据是 rich:x:1001:1002::/home/rich:/bin/bash
某个单词是 rich
某个单词是 x
某个单词是 1001
某个单词是 1002
某个单词是 
某个单词是 /home/rich
某个单词是 /bin/bash
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test14.sh
#!/bin/bash
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
  echo "这一行的数据是 $entry"
  IFS=:
  for value in $entry
  do
    echo "某个单词是 $value"
  done
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 13.7 循环控制

# 13.7.1 break命令

  • break命令可以跳出单个循环
  • 同样适用于while和until循环
  • 在处理多个循环时,break命令会自动结束所在的最内层循环
  • 跳出外层循环
#!/bin/bash
# breaking out of an outer loop
for (( a = 1; a < 4; a++ ))
do
   echo "Outer loop: $a"
   for (( b = 1; b < 100; b++ ))
   do
      if [ $b -gt 4 ]
      then
         break 2
      fi
      echo "   Inner loop: $b"
   done
done

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test19.sh
Outer loop: 1
   Inner loop: 1
   Inner loop: 2
   Inner loop: 3
   Inner loop: 4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 13.7.2 continue命令

continue命令可以提前终止某次循环,但不会结束整个循环

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test17.sh
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
Iteration number: 5
Iteration number: 10
Iteration number: 11
Iteration number: 12
Iteration number: 13
Iteration number: 14
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test17.sh 
#!/bin/bash
# using the continue command

for (( var1 = 1; var1 < 15; var1++ ))
do
   if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
   then
      continue
   fi
   echo "Iteration number: $var1"
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

但是需要注意的是,continue会跳过剩余的命令,如果我们将测试条件的增值操作放在了continue之后,那么可能就会出现问题。另外,和break命令一样,continue命令也支持通过命令行参数指定哪一级循环。

# 13.8 处理循环的输出

在shell脚本中,可以对循环的输出使用管道或者进行重定向,可以通过在done命令之后添加一个处理命令来实现:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test20.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test20.txt
1
2
3
4
5
6
7
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test20.sh
#!/bin/bash
for var in 1 2 3 4 5 6 7
do
 echo $var
done > test20.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

#同理,不仅可以将循环的输出定向到文件,还可以将循环的输出定向到另一个命令。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test20.sh
a
b
c
d
f
g
h
i
t
u
y
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test20.sh
#!/bin/bash

for var in a f g h t y u i b d c
do
 echo $var
done | sort
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 13.9 实战演练

# 13.9.1 查找可执行文件

[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test21.sh
#!/bin/bash
# finding files in the PATH

IFS=:
for folder in $PATH
do
   echo "$folder:"
   for file in $folder/*
   do
      if [ -x $file ]
      then
         echo "   $file"
      fi
   done
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]#./test21.sh
/usr/local/sbin:
/usr/local/bin:
   /usr/local/bin/cloud-id
   /usr/local/bin/cloud-init
   /usr/local/bin/cloud-init-per
   /usr/local/bin/jsondiff
   /usr/local/bin/jsonpatch
   /usr/local/bin/jsonpointer
   /usr/local/bin/jsonschema
   /usr/local/bin/normalizer
/usr/sbin:
   /usr/sbin/accessdb
   /usr/sbin/acs-plugin-manager
   /usr/sbin/addgnupghome
   /usr/sbin/addpart
   /usr/sbin/adduser
   /usr/sbin/agetty
   /usr/sbin/aliyun_installer
   /usr/sbin/aliyun-service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 13.9.2 创建多个用户账户

Barabra,BaraBara[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat user.csv
Tim,TimXx
Barabra,BaraBara
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# ./test22.sh
adding Tim
useradd: user 'Tim' already exists
adding Barabra
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# cat test22.sh
#!/bin/bash
# process new user accounts

input="user.csv"
while IFS=',' read -r loginname name
do
  echo "adding $loginname"
  useradd -c "$name" -m $loginname
done < "$input"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash1]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 14 处理用户输入

# 14.1 传递参数

有时候我们在执行脚本的时候,脚本中需要用到的变量不一定都是提前定义好的,需要我们在执行脚本的时候传进去。那么这节,就会学到传进去参数的写法。

# 14.1.1 读取参数

#修改文件夹的名称
sudo mv old_folder new_folder

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test1.sh
#!/bin/bash
for (( a=1;a<$1;a++))
do 
 echo $a
done


[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test1.sh 5
1
2
3
4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test1.sh
#!/bin/bash
:'
for (( a=1;a<$1;a++))
do 
 echo $a
done
'
echo "第一个值是 $1"
echo "第二个值是 $2"
echo "他们的乘积是 $[$1 * $2] "
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

#当参数是字符串时,如果有空格的话,那么需要将字符串用单引号或者双引号包裹起来
#如果变量的个数超过了9个,那么后边的参数在脚本中使用的时候必须要用花括号包裹起来 ${10}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 14.1.2 读取脚本名

使用$0可以获取脚本名称

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test1.sh 2 5
第一个值是 2
第二个值是 5
他们的乘积是 10 
脚本名称是 ./test1.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test1.sh
#!/bin/bash
: <<COMMENT
for (( a=1;a<$1;a++))
do 
 echo $a
done
COMMENT
echo "第一个值是 $1"
echo "第二个值是 $2"
echo "他们的乘积是 $[$1 * $2] "

echo "脚本名称是 $0"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

如果我们使用的绝对路径来执行脚本的话,那么最后的输出中会带上./  如果要去掉的话 可以使用$(basename $0)

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test1.sh
#!/bin/bash
: <<COMMENT
for (( a=1;a<$1;a++))
do 
 echo $a
done
COMMENT
echo "第一个值是 $1"
echo "第二个值是 $2"
echo "他们的乘积是 $[$1 * $2] "

name=$(basename $0)
echo "脚本名称是 $name"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test1.sh 2 5
第一个值是 2
第二个值是 5
他们的乘积是 10 
脚本名称是 test1.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 14.1.3 参数测试

我们在使用参数的时候,如果没有校验参数是否为空的话,那么可能就会报错,所以需要校验。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test1.sh 2 
参数2 值为空 ,不可操作
脚本名称是 test1.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test1.sh
#!/bin/bash
: <<COMMENT
for (( a=1;a<$1;a++))
do 
 echo $a
done
COMMENT
if [ -n "$2" ]
then
 echo "第一个值是 $1"
 echo "第二个值是 $2"
 echo "他们的乘积是 $[$1 * $2] "
else
 echo "参数2 值为空 ,不可操作"
fi
name=$(basename $0)
echo "脚本名称是 $name"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 14.2 特殊参数变量

# 14.2.1 参数统计

上一节中,我们看到了,如果想要在脚本中使用参数,如果参数较多的情况校验比较麻烦。其实无需逐一测试,我们可以统计一下有多少个命令行参数,bash为此提供了一个特殊变量。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test2.sh 1 2
参数的数量不是1
两个参数相加的和是 3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test2.sh
#!/bin/bash
if [ $# -eq 1 ]
then
   echo "参数的数量是1"
else
   echo "参数的数量不是1"
fi

if [ $# -ne 2 ]
then 
   echo "参数的数量应该是2"
else
   echo "两个参数相加的和是 $(($1 + $2))"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

#获取最后一个参数的方法
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test2.sh 1 2 3 4 5
最后一个参数是 5
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test2.sh
#!/bin/bash
echo "最后一个参数是 ${!#}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 14.2.2 获取所有的数据

有时候你想要抓取命令行中的所有参数。这时无须先用$#变量判断有多少个命令行参数,然后再进行遍历,用两个特殊变量即可解决这个问题。

#使用$@ 和 $*可以得到所有的参数变量,但是$*会将所有的参数视为一个整体,但是$@是将所有的参数分割开了,以方便我们可以直接获得某一个参数
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test3.sh
#!/bin/bash
# Exploring different methods for grabbing all the parameters
#
echo
echo "Using the \$* method: $*"
count=1
for param in "$*"
do
     echo "\$* Parameter #$count = $param"
     count=$[ $count + 1 ]
done
#
echo
echo "Using the \$@ method: $@"
count=1
for param in "$@"
do
     echo "\$@ Parameter #$count = $param"
     count=$[ $count + 1 ]
done
echo
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test3.sh 1 2 3 4 5

Using the $* method: 1 2 3 4 5
$* Parameter #1 = 1 2 3 4 5

Using the $@ method: 1 2 3 4 5
$@ Parameter #1 = 1
$@ Parameter #2 = 2
$@ Parameter #3 = 3
$@ Parameter #4 = 4
$@ Parameter #5 = 5

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 14.3 移动参数

shift命令可以移动参数,具体操作是将脚本后边的参数向左移动一位,但是$0 的值永远不会改变,只是$1的值被删除了,所以,一旦使用了这个命令之后,被删除的参数就找不到了。shift命令默认移动的位置是1,如果我们想移动多个位置的话,可以在shift后边带上数量的参数,例如shift 2

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test4.sh 1 2 3 4
参数1:1
参数2:2
参数3:3
参数4:4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test4.sh
#!/bin/bash
count=1
if [ $# -eq 0 ]
then
   echo "参数为空"
else 
   while [ $# -ne 0 ]
   do
     echo "参数$count:$1"
	 count=$[$count + 1]
	 shift
   done
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

#将shift换成shift 2之后输出就会变成
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test4.sh 1 2 3 4 5 6 7 8 9 0
参数1:1
参数2:3
参数3:5
参数4:7
参数5:9
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 14.4 处理选项

# 14.4.1 查找选项

# 1、处理简单选项

我们可以使用shift命令来一次处理脚本的命令行参数,这样子,假如我们的脚本有参数可以设置的话,那么就可以根据不同的脚本来实现不同的功能。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test5.sh 1 2 3 4
1 不是一个选项
2 不是一个选项
3 不是一个选项
4 不是一个选项
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test5.sh
#!/bin/bash
while [ -n "$1" ]
do
  case "$1" in 
  -a) echo "找到-a选项" ;;
  -b) echo "找到-b选项" ;;
  -c) echo "找到-c选项" ;;
  *) echo "$1 不是一个选项" ;;
  esac
  shift
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2、分离参数和选项

但是,有些命令不只是仅仅有选项,还有参数的情况,处理这个问题常用的做法是使用特殊字符(--)将两者分开,--表明选项部门结束,在双连字符后,脚本将之后的部分作为参数处理。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test6.sh
#!/bin/bash
while [ -n "$1" ]
do
  case "$1" in 
  -a) echo "找到-a选项" ;;
  -b) echo "找到-b选项" ;;
  -c) echo "找到-c选项" ;;
  --) shift
      break;;
  *) echo "$1 不是一个选项" ;;
  esac
  shift
done

count=1
for param in $@
do
  echo "$param"
  count=$(($count + 1 ))
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test6.sh -a -v -b -- as adasd 1 3  4
找到-a选项
-v 不是一个选项
找到-b选项
as
adasd
1
3
4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 3、处理含值的选项

有些时候,我们需要处理一个额外的参数值。例如下边这样

$ ./testing.sh -a test1 -b -c -d test2
1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test7.sh -a -b bvalue -d -- a b c e d r f f 

找到 -a 选项
找到 -b 选项 with parameter value bvalue
-d is not an 选项

参数 #1: a
参数 #2: b
参数 #3: c
参数 #4: e
参数 #5: d
参数 #6: r
参数 #7: f
参数 #8: f
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test7.sh
#!/bin/bash
# Extract command-line 选项s and values
#
echo
while [ -n "$1" ]
do
     case "$1" in
          -a) echo "找到 -a 选项" ;;
          -b) param=$2
              echo "找到 -b 选项 with parameter value $param"
              shift;;
          -c) echo "找到 -c 选项" ;;
          --) shift
              break;;
          *) echo "$1 is not an 选项" ;;
     esac
     shift
done
#
echo
count=1
for param in $@
do
     echo "参数 #$count: $param"
     count=$[ $count + 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test7.sh -ac

-ac is not an 选项

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
 
 #但是当ac两个选项合并到一起的时候,我们的脚本就不管用了,所以,这时候需要使用getopt命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 14.4.2 使用getopt命令

1、命令格式

getopt命令能够是被命令行参数,简化解析过程。

getopt的命令格式如下:

getopt optstring parameters

#这个命令格式的是先定义好都有哪些参数  ab:cd 指的是一共有这四个选项,b后边的冒号指的是b这个选项有一个参数,最后的test1和test2不在里边 所以被当做是其他的参数,就没有被当做选项了。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# getopt ab:cd -a -b bvalue -cd test1 test2
 -a -b bvalue -c -d -- test1 test2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

#但是当e选项不在里边的时候,就会报错,这时候可以使用-q选项
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# getopt ab:cd -a -b bvalue -cde test1 test2
getopt: invalid option -- 'e'
 -a -b bvalue -c -d -- test1 test2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 


[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# getopt -q  ab:cd -a -b bvalue -cde test1 test2
 -a -b 'bvalue' -c -d -- 'test1' 'test2'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2、在脚本中使用getopt

难点在于要使用getopt命令生成的格式化版本替换已有的命令行选项和参数。这得求助于set命令。(第六章介绍过set)

set命令有一个选项是双连字符(--),可以将位置变量的值替换成set命令所指定的值

具体做法是将脚本的命令行参数传给getopt命令,然后再将getopt命令的输出传给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数,如下所示:

#$(getopt -q ab:cd "$@")会将解析后的参数作为单个字符串展开,并通过set --将其设置给位置参数($1, $2, 等等)。
set -- $(getopt -q ab:cd "$@")
1
2
#这时候 我们可以看到 -ac就可以正确解析出来了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test8.sh
#!/bin/bash
# Extract command-line 选项s and values
#

set -- $(getopt -q ab:cd "$@")

echo
while [ -n "$1" ]
do
     case "$1" in
          -a) echo "找到 -a 选项" ;;
          -b) param=$2
              echo "找到 -b 选项 with parameter value $param"
              shift;;
          -c) echo "找到 -c 选项" ;;
          --) shift
              break;;
          *) echo "$1 is not an 选项" ;;
     esac
     shift
done
#
echo
count=1
for param in $@
do
     echo "参数 #$count: $param"
     count=$[ $count + 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
#这样子看起来还不错,但是当参数中有引号的时候就有异常了,还好我们还有另外的解决方法
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test8.sh -a -b bvalue -c -d -- "test1 test2" test3

-a
找到 -a 选项
-b
找到 -b 选项 with parameter value 'bvalue'
-c
找到 -c 选项
-d
-d is not an 选项
--

参数 #1: 'test1
参数 #2: test2'
参数 #3: 'test3'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 14.4.3 使用getopts命令

getopts的命令格式如下

getopts optstring variable
1
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ./test9.sh -abBvaluec

找到 -a 选项
找到 -b 选项 with 参数 Bvaluec
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test9.sh
#!/bin/bash
# Extract command-line 选项s and values with getopts
#
echo
while getopts :ab:c opt
do
     case "$opt" in
          a) echo "找到 -a 选项" ;;
          b) echo "找到 -b 选项 with 参数 $OPTARG";;
          c) echo "找到 -c 选项" ;;
          *) echo "Unknown 选项: $opt" ;;
     esac
done

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test10.sh -ab Bvalue test1 test2

Found the -a option
Found the -b option with parameter value Bvalue

Parameter 1: test1
Parameter 2: test2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test10.sh
#!/bin/bash
echo
while getopts :ab:cd opt
do
     case "$opt" in
          a) echo "Found the -a option" ;;
          b) echo "Found the -b option with parameter value $OPTARG";;
          c) echo "Found the -c option" ;;
          d) echo "Found the -d option" ;;
          *) echo "Unknown option: $opt" ;;
     esac
done
#
shift $[ $OPTIND - 1 ]
#
echo
count=1
for param in "$@"
do
     echo "Parameter $count: $param"
     count=$[ $count + 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 14.5 选项标准化

在编写shell脚本的时候,一切都在我们的掌握中,哪些选项和用法都是自定义的。但是,如果我们做的标准化,那么对用户更友好,下图展示了一些命令行选项的常用含义。

image-20240205175958386

# 14.6 获取用户输入

如果想要更多的交互性,bash shell提供了read命令。

# 14.6.1 基本的读取

read命令从标准输入(键盘)或者另一个文件描述符中接受输入,read命令hi将数据存入变量。

#一个变量
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test11.sh
#!/bin/bash
echo -n "Enter your name "
read name
echo "Hello, $name,welcome to my script"
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test11.sh
Enter your name shiyan
Hello, shiyan,welcome to my script
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
#两个变量的情况
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test11.sh
Enter your name shiyan  CF
Hello, shiyan,welcome to my CF
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test11.sh
#!/bin/bash
echo -n "Enter your name "
read name world
echo "Hello, $name,welcome to my $world"
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#read命令也提供了-p选项,允许直接指定提示符
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test11.sh
#!/bin/bash
: << \EOF echo -n "Enter your name "
read name world
echo "Hello, $name,welcome to my $world"
exit
EOF
read -p "Please enter you age:" age
if [[ $age =~ ^[0-9]+$ ]]
then
day=$[$age * 365]
echo "That mean you are over $day old"
else
echo "您输入的数字不规范"
fi
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

#如果指定了多个变量,那么输入的每个数据都会分配非变量列表的中下一个变量,如果变量数量不够,那么剩下的数据就会分配给最后一个变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test11.sh
Enter your first and last name:1 2 3 3 4  5 
you input is 1 2 3 3 4  5 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test11.sh
#!/bin/bash
: << \EOF echo -n "Enter your name "
read name world
echo "Hello, $name,welcome to my $world"
exit

read -p "Please enter you age:" age
day=$[$age * 365]
echo "That mean you are over $day old"
exit
EOF

read -p "Enter your first and last name:" 
echo "you input is $REPLY"
exit[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
#如果没有指定变量,那么read命令会将接收到的所有数据都放进特殊环境变量REPLY中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 14.6.2 超时

使用read命令的时候,脚本可能会一直苦等着用户输入,这时候我们可以使用-t选项来指定一个定时器。

-t选项会指定read命令等待输入的秒数,如果计时器超时,则read会返回非0退出校验码。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test16.sh
#!/bin/bash
# Using the read command with a timer
#
if read -t 5 -p "Enter your name: " name
then
     echo "Hello $name, welcome to my script."
else
     echo
     echo "Sorry, no longer waiting for name."
fi
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test16.sh
Enter your name: 
Sorry, no longer waiting for name.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test16.sh
Enter your name: shiyan
Hello shiyan, welcome to my script.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

另外,我们还可以不对输入进行计时,而是计算输入的字符的数量。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test13.sh
#!/bin/bash
read -n 1 -p "是否继续输入 [Y/N]" answer
case $answer in 
  Y|y) echo "继续";;
  N|n) echo "不继续"
  exit;;
esac
echo "结束"
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test13.sh
是否继续输入 [Y/N]y继续
结束
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test13.sh
是否继续输入 [Y/N]h结束
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 14.6.3 无显示读取

有时候,我们不想我们输入的内容展示在屏幕上,典型的例子就是输入密码的时候,这时候可以使用-s选项,

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test14.sh
Enter your password:your password is ss
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test14.sh
#!/bin/bash
read -s -p "Enter your password:" pass
echo "your password is $pass"
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9

read -s 命令的原理涉及到终端的工作方式和输入模式。在 Unix/Linux 系统中,终端处理用户输入的方式是通过输入模式来控制的。read -s 的工作原理如下:

  1. 设置终端为非回显模式(non-echo mode):当使用 read -s 命令时,Shell 会向终端发送一个信号,告诉终端要进入非回显模式。在这种模式下,终端会接收用户输入的字符,但不会将其显示在屏幕上。
  2. 读取用户输入:Shell 在非回显模式下等待用户输入。当用户输入字符时,终端会将输入的字符传递给 Shell。
  3. 存储用户输入:Shell 接收到用户输入的字符后,将其存储在指定的变量中,或者用于后续的操作。
  4. 退出非回显模式:一旦用户输入完成(通常通过按下 Enter 键),Shell 会告知终端退出非回显模式,终端将恢复到正常的回显模式,用户输入的字符也会显示在屏幕上。

通过这种方式,read -s 命令实现了隐藏用户输入的功能,这在需要输入敏感信息(如密码)时尤为有用,可以提高安全性。但需要注意的是,在非回显模式下,用户输入的字符不会显示,但仍然可以被其他程序捕获,因此敏感信息的处理需要谨慎。

如果需要显式地启用回显,可以通过修改终端属性来实现。在 Bash 中,可以使用 stty 命令来配置终端属性。例如,要启用回显,可以执行以下命令:

stty echo

如果要关闭 可以使用

stty off

# 14.6.4 从文件中读取

[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# ./test15.sh
Line 1 : 1
Line 2 : 2
Line 3 : 3
Line 4 : 4
Line 5 : 5
Line 6 : 6
Line 7 : 7

完成
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# cat test15.sh
#!/bin/bash
count=1
cat test.txt | while read line 
do 
  echo "Line $count : $line"
  count=$(( $count + 1 ))
done
echo
echo "完成"
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash14]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 14.7 实战演练

#!/bin/bash
# Check systems on local network allowing for
# a variety of input methods.
#
#
########### Determine Input Method ###################
#
# Check for command-line options here using getopts.
# If none, then go on to File Input Method
#
while getopts t: opt
do
     case "$opt" in
          t) # Found the -t option
             if [ $OPTARG = "IPv4" ]
             then
                  pingcommand=$(which ping)
             #
             elif [ $OPTARG = "IPv6" ]
             then
                 pingcommand=$(which ping6)
             #
             else
                 echo "Usage: -t IPv4 or -t IPv6"
                 echo "Exiting script..."
                 exit
             fi
             ;;
          *) echo "Usage: -t IPv4 or -t IPv6"
             echo "Exiting script..."
             exit;;
     esac
     #
     shift $[ $OPTIND - 1 ]
     #
     if [ $# -eq 0 ]
     then
          echo
          echo "IP Address(es) parameters are missing."
          echo
          echo "Exiting script..."
          exit
     fi
     #
     for ipaddress in "$@"
     do
          echo
          echo "Checking system at $ipaddress..."
          echo
          $pingcommand -q -c 3 $ipaddress
          echo
     done
     exit
done
#
########### File Input Method ###################
#
echo
echo "Please enter the file name with an absolute directory
reference..."
echo
choice=0
while [ $choice -eq 0 ]
do
     read -t 60 -p "Enter name of file: " filename
     if [ -z $filename ]
     then
          quitanswer=""
          read -t 10 -n 1 -p "Quit script [Y/n]? " quitanswer
          #
          case $quitanswer in
          Y | y) echo
                 echo "Quitting script..."
                 exit;;
          N | n) echo
                 echo "Please answer question: "
                 choice=0;;
          *)     echo
                 echo "No response. Quitting script..."
                 exit;;
          esac
     else
          choice=1
     fi
done
#
if [ -s $filename ] && [ -r $filename ]
     then
          echo "$filename is a file, is readable, and is not empty."
          echo
          cat $filename | while read line
          do
               ipaddress=$line
               read line
               iptype=$line
               if [ $iptype = "IPv4" ]
               then
                    pingcommand=$(which ping)
               else
                    pingcommand=$(which ping6)
               fi
               echo "Checking system at $ipaddress..."
               $pingcommand -q -c 3 $ipaddress
               echo
          done
          echo "Finished processing the file. All systems checked."
     else
          echo
          echo "$filename is either not a file, is empty, or is"
          echo "not readable by you. Exiting script..."
fi
#
#################### Exit Script #####################
#
exit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

# 15 呈现数据

到目前为止,我们学习到的输出要么是展示在屏幕上,要么是重定向到文件,本章将学习,如何将脚本的输出重定向到不同的位置。

# 15.1 理解输入和输出

有时候我们需要将脚本中的输出一部分输出到屏幕,一部分保存在文件中,下边将介绍如何用Linux标准的输入和输出将脚本输出送往特定的位置。

# 15.1.1 标准文件描述符

image-20240219224746925

每个进程最多可以打开9个文件描述符。

1、STDIN

STDIN文件描述符代表shell的标准输入,对于终端界面来说,标准输入就是键盘。

通常情况下,我们使用cat的时候,后边会加一个文件名,表示我们是希望从文件中读取数据,然后展示在屏幕上,但是,当我们只输入一个cat命令的时候,标准输入就会转变成键盘。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat 1test.txt 
1
2
3
4

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat    #当cat后边没有文件名的时候标准输入就变成了键盘。
1^Hsd
sd
asdasda
asdasda
dasd
dasd
aa
aa
^C
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2、STDOUT

STDOUT文件描述符代表shell的标准输出。在终端界面上,标准输出就是终端显示器。shell的所有输出(包括shell中运行的程序和脚本)会被送往标准输出,也就是显示器。我们可以使用 > 来改变默认的输出。但是只能改变标准输出而不能改变错误信息。

需要注意的是,错误的消息是和普通的消息分开的,对于错误的消息,我们需要使用另一种方法。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ls -l > 2test.txt  #标准输出本来是显示器,现在重定向到2test.txt文件中
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat 2test.txt 
total 4
-rw-r--r-- 1 root root 9 Feb 19 23:01 1test.txt
-rw-r--r-- 1 root root 0 Feb 19 23:03 2test.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7

3、STDERR

shell通过特殊的STDERR文件描述符处理错误信息。STDERR代表标准错误输出。默认情况下,STDERR和STDOUT指向同一个地方,也就是说所有的错误信息会默认送往显示器。

# 15.1.2 重定向错误

通过15.1.1的图片中我们已经知道了STDERR的文件描述符为2,重定向标准输出的时候使用的符号为> ,所以,当我们要重定向错误信息的时候,在重定向标准输出符号前指定一下文件描述符就好了。

重命名文件使用mv命令。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# mv 1test.txt  test1.txt
1

1、只重定向错误

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al badfile 2> test3.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat test3.txt
ls: cannot access badfile: No such file or directory
#发现错误已经重定向到文件text3.txt中 错误信息就不会显示在屏幕上了。
#发现正确的信息展示出来 然后错误信息输出到了test4.txt中
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ls -al test2.txt badfile 2> test4.txt
-rw-r--r-- 1 root root 104 Feb 19 23:03 test2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test4.txt
ls: cannot access badfile: No such file or directory
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 
1
2
3
4
5
6
7
8
9
10

2、重定向错误消息和正常输出

#将正常输出和错误信息 分开的情况
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ls -al test2.txt badfile 2> test5.txt 1> test6.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test6.txt
-rw-r--r-- 1 root root 104 Feb 19 23:03 test2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test5.txt
ls: cannot access badfile: No such file or directory
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 
#可以看到正确的信息输出到了test6.txt中,错误的信息输出到了test5.txt中了


#错误信息和正常输出重定向到一个文件的情况 需要注意的是,错误信息的优先级更高,展示在前边
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ls -al test2.txt badfile &> test5.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test5.txt
ls: cannot access badfile: No such file or directory
-rw-r--r-- 1 root root 104 Feb 19 23:03 test2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 15.2 在脚本中重定向输出

在脚本中重定向输出的方法有两种:

1、临时重定向每一行

2、永久重定向脚本中的所有命令

# 15.2.1 临时重定向

#虽然这次运行脚本的输出结果没有什么特别,但是一旦将错误信息重定向,那么就显示出来了。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test1.sh
错误信息重定向到ERR
普通信息不进行重定向
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test1.sh 2> test7.txt
普通信息不进行重定向
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test7.txt
错误信息重定向到ERR  #ERR已经被重定向到文件
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test1.sh
#!/bin/bash
echo "错误信息重定向到ERR" >&2
echo "普通信息不进行重定向"
1
2
3
4
5
6
7
8
9
10
11
12

# 15.2.2 永久重定向

如果脚本中有大量数据需要重定向,那么一行一行的重定向肯定非常麻烦。这时候我们可以使用exec命令。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test2.sh
#!/bin/bash
exec 1>testout.txt  #将标准输出重定向到文件中

echo "1"
echo "2"
echo "3"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test2.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat testout.txt
1
2
3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 
#exec命令会启动一个shell并将STDOUT文件描述符重定向到指定文件。


[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test3.sh
应该输出到out数据1
应该输出到out数据2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat testout.txt 
应该输出到out数据3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat testerr.txt 
应该输出到err数据1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test3.sh
#!/bin/bash
exec 2>testout.txt
echo "应该输出到out数据1"
echo "应该输出到out数据2"

exec 1>testerr.txt
echo "应该输出到err数据1"
echo "应该输出到out数据3" >&2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

#一旦重定向了STDOUT 和 STDERR,就不太容易将其恢复到原先的位置,如果需要来回切换,那么有个技巧可以使用。15.4中将会介绍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 15.3 在脚本中重定向输入

输出可以重定向,那么输入也可以重定向,我们可以使用exce命令将STDIN重定向为文件;

exec 0<testfile

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test4.sh
Line #1 : 1
Line #2 : 2
Line #3 : 3
Line #4 : 4
Line #5 : 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test1.txt
1
2
3
4

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test4.sh
#!/bin/bash
exec 0<test1.txt
count=1

while read line
do
  echo "Line #$count : $line"
  count=$[ $count + 1 ]
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 15.4 创建自己的重定向

15.1.1中说过,一个进程最多可以打开9个文件描述符,替代性文件描述符从3-8一共6个,都可以用作输入或者输出重定向。

# 15.4.1 创建输出文件描述符

仍然可以使用exec命令来分配用于输出的文件描述符。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test5.sh
标准输出1
标准输出3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test5out.txt 
标准输出2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test5.sh
#!/bin/bash
exec 3>test5out.txt

echo "标准输出1"
echo "标准输出2" >&3
echo "标准输出3"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 15.4.2 重定向文件描述符

#这节主要讲解了文件描述符可以使用类似变量一样的方法先将一个文件描述符保存在另一个地方,然后再还原回来
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test6.sh
#!/bin/bash
exec 3>&1
exec 1>test6out.txt

echo "会被重定向到test6out.txt文件中"

exec 1>&3
echo "这行会正常显示"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test6.sh
这行会正常显示
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test6out.txt 
会被重定向到test6out.txt文件中
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 15.4.3 创建输入文件描述符

和创建输出文件描述符一样,我们可以先用其他的文件描述符来保存标准输入文件描述符。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test7.sh
第1行的数据是1
第2行的数据是2
第3行的数据是3
第4行的数据是4
第5行的数据是
are you done now?n
this is end
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test7.sh
#!/bin/bash
exec 3<&0

exec 0<test1.txt
count=1
while read line
do
  echo "第$count行的数据是$line"
  count=$[ $count + 1 ]
done
exec 0<&3  #将标准输入恢复为键盘
read -p "are you done now?" answer
case $answer in
    Y|y) echo "bye";;
	N|n) echo "this is end";;
esac
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 15.4.4 创建读/写文件描述符

在对一个文件描述符对文件进行读和写两种操作的时候需要特别小心,这是因为shell会维护一个内部指针,指明该文件的当前位置,任何读写都会从文件指针上次的位置开始。

#这个脚本讲解了第一次读取文件之后,指针指向了第二行,再进行输入的时候,将后边的数据覆盖了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat testfile.txt
第1行
第2行
第3行[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test8.sh
Read : 第1行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat testfile.txt
第1行
新输入的测试数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat testfile.txt
第1行
第2行
第3行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test8.sh
#!/bin/bash
exec 3<> testfile.txt
read line <&3
echo "Read : $line"
echo "新输入的测试数据" >&3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 15.4.5 关闭文件描述符

如果我们创建了输入文件描述符或者输出文件描述符,那么shell脚本会在脚本退出的时候自动将其关闭,但是在一些情况下,我们需要在脚本结束前手动关闭文件描述符。

先说一下关闭文件描述符的方法:只需要将其重定向到特殊符号&-中即可。

exec 3>&-

#关闭文件描述符之后,如果再向其写入数据的话就会报错
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test9.sh
./test9.sh: line 5: 3: Bad file descriptor
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test7out.txt
this is a test line of data
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test9.sh
#!/bin/bash
exec 3>test7out.txt
echo "this is a test line of data" >&3
exec 3>&-
echo "this won't work " >&3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

#另外还需要注意一件事,在关闭了文件描述符之后,如果又打开了同一个输出文件,那么shell会用一个新文件来替换之前的文件,如果输出了数据,就会覆盖原来的数据。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test10.sh
这是第一次的数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test8out.txt
这是后来的数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test10.sh
#!/bin/bash
exec 3>test8out.txt
echo "这是第一次的数据" >&3
exec 3>&-

cat test8out.txt
exec 3> test8out.txt
echo "这是后来的数据" >&3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

#如果打开的不是同一个输出文件,那么不会有这样的问题
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test10.sh
这是第一次的数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test8out.txt
这是第一次的数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test9out.txt
这是后来的数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test10.sh
#!/bin/bash
exec 3>test8out.txt
echo "这是第一次的数据" >&3
exec 3>&-

cat test8out.txt
exec 3> test9out.txt
echo "这是后来的数据" >&3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 15.5 列出打开的文件描述符

lsof命令会列出整个Linux系统打开的所有文件描述符。

image-20240220231747447

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# lsof -a -p $$ -d 0,1,2
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    9402 root    0u   CHR  136,0      0t0    3 /dev/pts/0
bash    9402 root    1u   CHR  136,0      0t0    3 /dev/pts/0
bash    9402 root    2u   CHR  136,0      0t0    3 /dev/pts/0

#每个文件都显示为REG类型,说明这些是文件系统中的常规文件。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test11.sh
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
test11.sh 9830 root    0u   CHR  136,0      0t0       3 /dev/pts/0
test11.sh 9830 root    1u   CHR  136,0      0t0       3 /dev/pts/0
test11.sh 9830 root    2u   CHR  136,0      0t0       3 /dev/pts/0
test11.sh 9830 root    3w   REG  253,1        0 1320683 /root/bash15/test18file1
test11.sh 9830 root    6w   REG  253,1        0 1320685 /root/bash15/test18file2
test11.sh 9830 root    7r   REG  253,1       25 1320661 /root/bash15/testfile
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test11.sh
#!/bin/bash
# testing lsof with file descriptors

exec 3> test18file1
exec 6> test18file2
exec 7< testfile

lsof -a -p $$ -d0,1,2,3,6,7
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 15.6 抑制命令输出

有时候我们不想显示脚本输出,不想让错误输出到任何地方,包括文件和屏幕,这时候我们可以将STDERR重定向到一个名为null的特殊文件中,shell输出到null文件的任何数据都不会被保存,全部都会被丢弃。null的标准位置是/dev/null。

同时也可以将/dev/null作为输入文件,但是null中什么也没有,可以用来快速清除现有文件中的数据。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test2.txt
total 4
-rw-r--r-- 1 root root 9 Feb 19 23:01 1test.txt
-rw-r--r-- 1 root root 0 Feb 19 23:03 2test.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# &> test2.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test2.txt

1
2
3
4
5
6
7

# 15.7 使用临时文件

Linux系统有一个专供临时文件使用的特殊目录/tmp,大多数Linux发行版配置系统会在启动的时候自动删除/tmp目录的所有文件。

有一个专门用于创建临时文件的命令mktemp,该命令可以在tmp目录中创建唯一的临时文件。并且使用默认的umask值。

作为临时文件的属主,别的用户无法访问。

这时候可以用其他账户登录系统测试一下

# 15.7.1 创建本地临时文件

#创建本地临时文件
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XXXXXXX
testing.Y1qzFgP
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XXXXX
testing.tJ6yF
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XX
mktemp: too few X's in template ‘testing.XX’
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XXX
testing.9mA
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XXXXXXXXXXXXXX
testing.M4pngF9k2O4CRp
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
testing.N1wVgOBKhIiz9VDMP9GS9OHRIf3dbvFnk
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# mktemp testing.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
testing.SgiYLLRTuGtghpjmrTxA3QtTaxOLZ7EkR50EwIY9Z
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 


[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test12.sh
#!/bin/bash
tempfile=$(mktemp testing.XXX)
exec 3>$tempfile

echo "this script writes to temp file $tempfile"
echo "第一行" >&3
echo "第二行" >&3
echo "第三行" >&3

exec 3>&-
echo "Done creating temp file,the contents are:"
cat $tempfile
rm -f $tempfile 2> /dev/null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 15.7.2 在/tmp目录中创建临时文件

-t选项会强制mktemp命令在系统的临时目录中创建文件。在使用这个特性时,mktemp命令会返回所创建的临时文件的完整路径名,而不只是文件名。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# mktemp -t testing.XXXX
/tmp/testing.JNyo
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ls -la /tmp/testing.*
-rw------- 1 root root 0 Feb 21 13:21 /tmp/testing.JNyo
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6

由于mktemp命令会返回临时文件的完整路径名,因此可以在文件系统的任何位置引用该临时文件

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test13.sh
The temp file is located at: /tmp/tmp.JzSbp3
This is a test file.
This is the second line of the test.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ls -la /tmp/tmp.*
-rw------- 1 root root 58 Feb 21 13:25 /tmp/tmp.JzSbp3
-rw------- 1 root root 58 Feb 21 13:26 /tmp/tmp.K384OU
-rw------- 1 root root 58 Feb 21 13:26 /tmp/tmp.Ls9tnu
#可以看到 /tmp文件夹中已经有了上边脚本创建的文件
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test13.sh
#!/bin/bash
# creating a temp file in /tmp

tempfile=$(mktemp -t tmp.XXXXXX)

echo "This is a test file." > $tempfile
echo "This is the second line of the test." >> $tempfile

echo "The temp file is located at: $tempfile"
cat $tempfile[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 15.7.3 创建临时目录

-d选项会告诉mktemp命令创建一个临时目录。你可以根据需要使用该目录,比如在其中创建其他的临时文件

[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test14.sh
#!/bin/bash
# using a temporary directory

tempdir=$(mktemp -d dir.XXXXXX)
cd $tempdir
tempfile1=$(mktemp temp.XXXXXX)
tempfile2=$(mktemp temp.XXXXXX)
exec 7> $tempfile1
exec 8> $tempfile2

echo "Sending data to directory $tempdir"
echo "This is a test line of data for $tempfile1" >&7
echo "This is a test line of data for $tempfile2" >&8

echo "接下来是两个临时文件的内容:"
echo  "$tempfile1:" cat $tempfile1
echo  "$tempfile2:" cat $tempfile2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# ./test14.sh
Sending data to directory dir.UzlGUz
接下来是两个临时文件的内容:
temp.LPRwEY: cat temp.LPRwEY
temp.Fiu3QV: cat temp.Fiu3QV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 15.8 记录消息

有些时候,我们不仅需要将消息送到显示器,还需要将消息送到文件中,就可以使用tee命令。

它能将来自STDIN的数据同时送往两处。一处是STDOUT,另一处是tee命令行所指定的文件名:

#使用tee命令将date的输出同时送往test2.txt文件中
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# date | tee test2.txt
Wed Feb 21 13:39:02 CST 2024
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test2.txt
Wed Feb 21 13:39:02 CST 2024

#使用-a选项 向test2.txt文件中追加数据
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# who | tee -a test2.txt
root     pts/0        2024-02-21 08:23 (117.28.132.230)
root     pts/1        2024-02-21 12:32 (117.28.132.230)
root     pts/2        2024-02-21 13:20 (117.28.132.230)
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# cat test2.txt
Wed Feb 21 13:39:02 CST 2024
root     pts/0        2024-02-21 08:23 (117.28.132.230)
root     pts/1        2024-02-21 12:32 (117.28.132.230)
root     pts/2        2024-02-21 13:20 (117.28.132.230)
[root@iZuf6fdhtuwmr11b7hkk8pZ bash15]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 15.9 实战演练

#!/bin/bash
# read file and create INSERT statements for MySQL

outfile='members.sql'
IFS=','
while read lname fname address city state zip
do
   cat >> $outfile << EOF
   INSERT INTO members (lname,fname,address,city,state,zip) VALUES
('$lname', '$fname', '$address', '$city', '$state', '$zip');
EOF
done < ${1}


#源文件
$ cat members.csv
Blum,Richard,123 Main St.,Chicago,IL,60601
Blum,Barbara,123 Main St.,Chicago,IL,60601
Bresnahan,Christine,456 Oak Ave.,Columbus,OH,43201
Bresnahan,Timothy,456 Oak Ave.,Columbus,OH,43201

#运行脚本
$ ./test23 members.csv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 16 脚本控制

到目前为止,运行脚本的唯一方式就是有需要时直接在命令行中启动。这并不是唯一的方式,还有很多方式可以用来运行shell脚本。你也可以对脚本加以控制,包括向脚本发送信号、修改脚本的优先级,以及切换脚本的运行模式。

# 16.1 处理信号

Linux利用信号与系统中的进程进行通信。第4章介绍过不同的Linux信号以及Linux如何用这些信号来停止、启动以及“杀死”进程。你可以通过对脚本进行编程,使其在收到特定信号时执行某些命令,从而控制shell脚本的操作。

# 16.1.1重温Linux信号

image-20240221204442669

默认情况下,bash shell会忽略收到的任何SIGQUIT(3)信号和SIGTERM(15)信号,但是会处理收到的SIGHUP(1)信号和SIGNT(2)信号,shell会将信号传给shell脚本处理,但是脚本会忽略某些信号,所以不利于脚本运行,为了避免这种情况,我们需要在脚本中加入识别信号的代码。

# 16.1.2 产生信号

1、中断进程

CTRL + C

[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# sleep 60
^C

1
2
3

2、暂停进程

CTRL+Z

#当使用Ctrl + Z时,shell会通知我们进程已经停止了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# sleep 60
^Z
[1]+  Stopped                 sleep 60
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 
#使用exit命令时,会提示我们有停止的进程
root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# exit
logout
There are stopped jobs.
#通过ps -l查看已经停止的进程
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0 11728 11725  0  80   0 - 29052 do_wai pts/0    00:00:00 bash
0 T     0 11775 11728  0  80   0 - 27014 do_sig pts/0    00:00:00 sleep
0 R     0 11776 11728  0  80   0 - 38332 -      pts/0    00:00:00 ps
#知道进程号之后,直接杀死该进程
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# kill -9 11775
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 
#杀死进程之后
[1]+  Killed                  sleep 60
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 
#当有停止的进程的时候,输入两次exit即可退出进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 16.1.3 捕获信号

我们可以在信号出现的时候捕获,这样一来,就是由本地处理信号,而不再是由shell处理。

trap commands signals

signals表示我们想要捕获的信号,多个信号之间以空格分隔

commands表示的是捕获了信号的处理方式

[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ./test1.sh
count:1
count:2
^C捕获到了Ctrl + C信号#捕获到信号之后输出
count:3
^C捕获到了Ctrl + C信号
count:4
^C捕获到了Ctrl + C信号
count:5
结束
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# cat test1.sh
#1/bin/bash
trap  "echo 捕获到了Ctrl + C信号" SIGINT
count=1
while [ $count -le 5 ]
do
  echo "count:$count"
  sleep 1
  count=$[ $count+1 ]
done
echo "结束"
exit


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

警告 如果脚本中的命令被信号中断,使用带有指定命令的trap未必能让被中断的命令继续执行。为了保证脚本中的关键操作不被打断,请使用带有空操作命令的trap以及要捕获的信号列表。

# 16.1.4 捕获脚本退出

可以在shell脚本中捕获信号,也可以在shell脚本退出时捕获:

退出时的信号是EXIT。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ./test1.sh
count:1
count:2
count:3
count:4
count:5
结束了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# cat test1.sh
#!/bin/bash
#可以同时捕获两种信号
trap  "echo 捕获到了Ctrl + C信号" SIGINT
trap  "echo '结束了'" EXIT
count=1
while [ $count -le 5 ]
do
  echo "count:$count"
  sleep 1
  count=$[ $count+1 ]
done

exit
#提前终止 需要把Ctrl + C 的捕获去掉
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ./test1.sh
count:1
^C结束了

[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 16.1.5 修改或移除信号捕获

要想在脚本中的不同位置进行不同的信号捕获处理,只需重新使用带有新选项的trap命令即可。

#在一个脚本中设置了两种不同的处理方法对于一种信号
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# cat test2.sh
#!/bin/bash
#Modifying a set trap
#
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 3 ]
do
     echo "Loop #$count"
     sleep 1
     count=$[ $count + 1 ]
done
#
trap "echo ' I have modified the trap!'" SIGINT
#
count=1
while [ $count -le 3 ]
do
     echo "Second Loop #$count"
     sleep 1
     count=$[ $count + 1 ]
done
#
exit
$[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

如果在交互式shell会话中使用trap命令,可以使用trap -p查看被捕获的信号。如果什么都没有显示,则说明shell会话按照默认方式处理信号。

使用两个连字符或者单连字符移除已经设置好的信号捕获。

$[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# cat test2.sh
#!/bin/bash
#Modifying a set trap
#
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 3 ]
do
     echo "Loop #$count"
     sleep 1
     count=$[ $count + 1 ]
done
#
#trap "echo ' I have modified the trap!'" SIGINT
#移除对SIGINT的监听
trap -- SIGINT
count=1
while [ $count -le 3 ]
do
     echo "Second Loop #$count"
     sleep 1
     count=$[ $count + 1 ]
done
#
exit
$[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ./test2.sh
Loop #1
Loop #2
Loop #3
^C Sorry...Ctrl-C is trapped.#移除之前正常捕获
Second Loop #1
Second Loop #2
Second Loop #3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ./test2.sh
Loop #1
Loop #2
Loop #3
Second Loop #1
Second Loop #2
^C#移除之后提前退出
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 16.2 以后台模式运行脚本

``ps`命令是用于显示当前运行在系统中的进程信息的命令。

  • -e:显示所有进程。
  • -f:显示详细的进程信息。
  • -l:显示长格式的进程信息。
  • -u:显示属于指定用户的进程。
  • -aux:显示所有进程的详细信息,包括用户、PID、CPU占用等。

使用ps -e查看在后台运行的程序:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ps -e
  PID TTY          TIME CMD
    1 ?        00:01:15 systemd
    2 ?        00:00:00 kthreadd
    4 ?        00:00:00 kworker/0:0H
    6 ?        00:00:06 ksoftirqd/0
    7 ?        00:00:00 migration/0
    8 ?        00:00:00 rcu_bh

1
2
3
4
5
6
7
8
9

# 16.2.1 后台运行脚本

#运行脚本的时候在后边加一个& 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ./test2.sh &
[1] 11990 #11990是分配给后台进程的进程号  [1]是作业号
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# Loop #1
Loop #2
Loop #3
Second Loop #1
Second Loop #2
Second Loop #3

[1]+  Done                    ./test2.sh#表示后台进程结束 以及启动的命令
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 

#需要注意的是,后台进程的输出也会输出在屏幕上,最好是进行重定向,避免杂乱的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 16.2.2 运行多个后台作业

在终端会话中使用后台进程一定要小心。注意,在ps命令的输出中,每一个后台进程都和终端会话(pts/0)终端关联在一起。如果终端会话退出,那么后台进程也会随之退出。

image-20240221224814594

登出控制台之后,如果仍然希望在后台模式的脚本继续运行,那么需要借助其他手段。

# 16.3 在非控制台下运行脚本

有时候,即使我们退出了终端会话,也想在终端会话中启动脚本,可以使用nohup命令来实现。

nohup命令能阻断发给特定进程的SIGHUP信号。当退出终端会话时,这可以避免进程退出。

由于nohup命令会解除终端与进程之间的关联,因此进程不再同STDOUT和STDERR绑定在一起。为了保存该命令产生的输出,nohup命令会自动将STDOUT和STDERR产生的消息重定向到一个名为nohup.out的文件中。

注意 nohup.out文件一般在当前工作目录中创建,否则会在$HOME目录中创建。

#运行脚本之后,我们关闭窗口,然后再次进入查看输出
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# nohup test2.sh &
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# ls
nohup.out  test11.sh  test13.sh  test15.sh  test17.sh  test19.sh  test2.sh  test4.sh  test6.sh  test8.sh
test10.sh  test12.sh  test14.sh  test16.sh  test18.sh  test1.sh   test3.sh  test5.sh  test7.sh  test9.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# cat nohup.out
Loop #1
Loop #2
Loop #3
Second Loop #1
Second Loop #2
Second Loop #3

1
2
3
4
5
6
7
8
9
10
11
12
13

# 16.4 作业控制

前边我们了解了如何停止(Ctrl+Z)以及杀死(Ctrl+C,kill)作业,如果要重启停止的进程,则需要向其发送SIGCONT信号。

作业控制包括:启动、杀死 以及 恢复作业。

16.4.1 查看作业

jobs是作业控制中的关键命令。

jobs是作业控制中的关键命令,该命令允许用户查看shell当前正在处理的作业。

注意 带有加号的作业为默认作业。如果作业控制命令没有指定作业号,则引用的就是该作业。

带有减号的作业会在默认作业结束之后成为下一个默认作业。任何时候,不管shell中运行着多少作业,带加号的作业只能有一个,带减号的作业也只能有一个。

jobs -l查看作业的PID

image-20240222222737414

16.4.2 重启已停止的作业

如果只有一个默认作业,那么可以使用bg命令将其以后台模式重启。

如果存在多个作业,那么需要在bg命令后加上作业号,以便于控制。

如果想以前台模式重启作业,可以使用带有作业号的fg命令,

fg 2 bg 2

# 16.5 调整谦让度

调度优先级[也称为谦让度(nice value)]是指内核为进程分配的CPU时间(相对于其他进程)。

调度优先级是一个整数值,取值范围从-20(最高优先级)到+19(最低优先级)。默认为0.如果想要改变shell脚本的调度优先级,那么我们可以使用nice命令来实现。

# 16.5.1 nice命令

nice命令允许在启动命令时设置其调度优先级。使用-n选项。

例如:

nice -n ./test2.sh

image-20240222223854313

只有root用户才可以提高命令的优先级。普通用户只能降低。

另外 nice -n 10还可以写成nice -10,如果设置的优先级是负数,那么这种写法很容易造成混淆,因为出现了双连字符。

# 16.5.2 renice命令

有时候,如果我们想修改系统中已经在运行的命令的优先级,那么可以使用renice命令。renice通过指定运行进程的PID来改变其优先级。

需要注意的是:只能对属主为自己的进程使用renice且只能降低调度优先级。但是,root用户和特权用户可以使用renice命令对任意进程的优先级做任意调整。

# 16.6 定时运行作业

Linux系统提供了多个在预选时间运行脚本的方法:at命令、cron表以及anacron。

# 16.6.1 使用at命令调度作业

at命令允许指定Linux系统核实运行脚本。atd守护进程会检查系统的一个特殊目录(通常位于/var/spool/at或/var/spool/cron/atjobs),从中获取at命令提交的作业。

1、at命令的格式

at [-f filename] time

filename是我们要执行的脚本文件

time选项指定了我们希望何时运行该脚本,如果指定的时间已经过去,那么at命令会在第二天的同一时刻运行指定的作业。

指定时间的方式非常灵活。at命令能识别多种时间格式。

·标准的小时和分钟,比如10:15。

·AM/PM指示符,比如10:15 PM。

·特定的时间名称,比如now、noon、midnight或者teatime(4:00 pm)。

·标准日期,比如MMDDYY、MM/DD/YY或DD.MM.YY。

·文本日期,比如Jul 4或Dec 25,加不加年份均可。

·时间增量。

·Now + 25 minutes·10:15 PM tomorrow·10:15 + 7 days

提示

at命令可用的日期和时间格式有很多种,具体参见/usr/share/doc/at/timespec文件。

使用at命令的时候,该作业会被提交到作业队列,作业队列Linux队列有52中分别是A~Z 和 a~z。

在默认情况下,at命令提交的作业会被放入a队列。如果想以较低的优先级运行作业,可以用-q选项指定其他的队列。如果相较于其他进程你希望你的作业尽可能少地占用CPU,可以将其放入z队列。

2、获取作业的输出

当我们在Linux系统中使用at命令的时候,显示器不会关联到该作业,而是会提交给该作业用户的email地址,以作为STDOUT,STDERR.

at -f filename time 定时运行脚本

-q 指定作业要被提交至的队列

3、列出等待的作业

atq命令可以查看系统有哪些作业正在等待。

4、删除作业

一旦知道了队列中有哪些作业在等待,就可以使用atrm命令删除

image-20240222232611494

# 16.6.2 调度需要定期运行的脚本

如果脚本需要以某个频率固定时间执行,那么使用at就没有那么友好了,可以使用Linux系统的另一个命令:cron--可以理解成为定时任务。

1、cron时间表

分钟 时 日 月 周

如何判断某一天是不是一个月中的最后一天,有两种方法:

1-在脚本中加上判断语句

00 12 28-31 * * if [ "$(date +%d -d tomorrow)" = 01 ]; then command fi

2-command命令替换成脚本,然后在脚本中判断以及执行命令

00 12 28-31 * * /root/bash16/test4.sh > backup.out(需要有脚本的执行权限)

2、构建cron时间表

每个用户都可以构建自己的时间表

[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# crontab -e
no crontab for root - using an empty one
crontab: installing new crontab
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# crontab -l
00 12 28-31 * * echo "啦啦"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash16]# 

1
2
3
4
5
6
7

3、浏览cron目录

在/etc目录下,有预配的脚本文件,分别是cron.daily, cron.hourly, cron.monthly, cron.weekly这几种文件分别对应每天、每小时、每月、每周的计划。

4、anacron程序

cron的问题是需要Linux系统在运行,如果Linux系统没有在运行,那么作业不会运行,这样子可能导致我们错过某些作业。所以提出了anacron。

anacron在系统启动的时候会判断作业是否错过了时间,并且会立即运行这些作业。

但是anacron只处理/etc目录下的cron.daily, cron.hourly, cron.monthly, cron.weekly文件。

那么anacron命令是如何判断作业是否在正确的计划时间运行了呢?

每个cron目录都有一个时间戳文件,位于/var/spool/anacron,

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls /var/spool/anacron
cron.daily  cron.monthly  cron.weekly
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat /var/spool/anacron/cron.daily
20240223
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days   delay in minutes   job-identifier   command
1	5	cron.daily		nice run-parts /etc/cron.daily
7	25	cron.weekly		nice run-parts /etc/cron.weekly
@monthly 45	cron.monthly		nice run-parts /etc/cron.monthly
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#period in days 这一列定义了作业的运行频率anacron程序用该字段检查作业的时间戳文件。
#delay字段指定了在系统启动后,anacron程序需要等待多少分钟再开始运行错过的脚本
#注意 anacron不会运行位于/etc/cron.hourly目录的脚本。这是因为anacron并不处理执行时间需求少于一天的脚本。
#identifier字段表示出现在日志小子和错误email中的作业
#command字段包含了run-parts程序和一个cron脚本目录名。run-parts程序负责运行指定目录中的所有脚本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 16.7 使用新shell启动脚本

如果想要在登录是运行脚本,

$HOME/.bash_profile

$HOME/.bash_login

$HOME/.profile

应该放到上述文件的第一个文件中,因为下列文件中的第一个文件被运行之后,其他的会被忽略。

但是每次启动新shell的时候,bash shell都会运行.bashrc文件,因为我们可以在该文件中增加一条输出语句。

.bashrc文件通常也借由某个bash启动文件来运行,因为.bashrc文件会运行两次:一次是当用户登录bash shell时,另一次是当用户启动bash shell时。如果需要某个脚本在两个时刻都运行,可以将其放入该文件中。

image-20240223133737459

# 16.8 实战演练

$ cat trapandrun.sh
#!/bin/bash
# Set specified signal traps; then run script in background
#
####################### Check Signals to Trap #######################
#
while getopts S: opt   #Signals to trap listed with -S option
do
     case "$opt" in
          S) # Found the -S option
             signalList="" #Set signalList to null
             #
             for arg in $OPTARG
             do
                  case $arg in
                  1)   #SIGHUP signal is handled
                       signalList=$signalList"SIGHUP "
                  ;;
                  2)   #SIGINT signal is handled
                       signalList=$signalList"SIGINT "
                  ;;
                  20)  #SIGTSTP signal is handled
                       signalList=$signalList"SIGTSTP "
                  ;;
                  *)   #Unknown or unhandled signal
                       echo "Only signals 1 2 and/or 20 are allowed."
                       echo "Exiting script..."
                       exit
                  ;;
                  esac
             done
             ;;
         *)  echo 'Usage: -S "Signal(s)" script-to-run-name'
             echo 'Exiting script...'
             exit
             ;;
     esac
     #
done
#
####################### Check Script to Run #######################
#
shift $[ $OPTIND - 1 ] #Script name should be in parameter
#
if [ -z $@ ]
then
     echo
     echo 'Error: Script name not provided.'
     echo 'Usage: -S "Signal(s)" script-to-run-name'
     echo 'Exiting script...'
     exit
elif [ -O $@ ] && [ -x $@ ]
then
     scriptToRun=$@
     scriptOutput="$@.out"
else
     echo
     echo "Error: $@ is either not owned by you or not executable."
     echo "Exiting..."
     exit
fi
#
######################### Trap and Run ###########################
#
echo
echo "Running the $scriptToRun script in background"
echo "while trapping signal(s): $signalList"
echo "Output of script sent to: $scriptOutput"
echo
trap "" $signalList  #Ignore these signals
#
source $scriptToRun > $scriptOutput & #Run script in background
#
trap -- $signalList  #Set to default behavior
#
####################### Exit script #######################
#
exit
$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

# 17 创建函数

本章将介绍如何创建自己的脚本函数

# 17.1脚本函数基础

函数是一个脚本代码块,你可以为其命名并在脚本中的任何位置重用它。每当需要在脚本中使用该代码块时,直接写函数名即可(这叫作调用函数)。

# 17.1.1 创建函数

bash shell中创建函数的语法有两种:

1:

#name:函数名 不可以重复
function name {
	commands
}
1
2
3
4

2:

name() {
	commands
}
1
2
3

# 17.1.2 使用函数

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test1.sh
This is an example of a fcuntion
This is an example of a fcuntion
This is an example of a fcuntion
This is an example of a fcuntion
This is an example of a fcuntion
结束
This is an example of a fcuntion
结束
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test1.sh
function fun1 {
	echo "This is an example of a fcuntion"
}
count=1
while [ $count -le 5 ]
do
	fun1
	count=$[ $count + 1 ]
done

echo "结束"
fun1
echo "结束"

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
#函数需要在被调用之前定义好
#函数名字可以重复,但是第二个定义的函数会覆盖之前定义好的相同名字的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 17.2 函数返回值

有三种方法能为函数生成退出状态码

# 17.2.1 默认的退出状态码

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test2.sh
测试函数
尝试打开一个不存在的文件
ls: cannot access xxx: No such file or directory
返回值是2
xx
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test2.sh
#!/bin/bash
fun1(){
		echo "尝试打开一个不存在的文件"
		ls -l xxx
}

echo "测试函数"
fun1
echo "返回值是$?"
echo "xx"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

#我们使用标准变量$?来确定函数的退出状态码,但是他接收的总是最后一行语句的状态码,如果函数中间有报错,那么就不是我们想要达到的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 17.2.2 使用return 命令

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test3.sh
请输入一个值: 100
将这个值乘2
新的值是 200
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test3.sh
#!/bin/bash
function db1 {
	read -p "请输入一个值: " value
	echo "将这个值乘2"
	return $[ $value*2 ]
}
db1
echo "新的值是 $?"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
#需要注意的是,函数一旦返回就需要立即读取返回值
#退出码介于0~255之间 否则就出错了,例子如下
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test3.sh
请输入一个值: 562
将这个值乘2
新的值是 100
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 17.2.3 使用函数输出

我们可以将函数的输出保存在shell变量中

使用functionname是函数名

result=$(functionname)

可以将函数的返回值赋值到变量中

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test4.sh
Enter a value:200
The new value is 400
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test4.sh
#!/bin/bash
function db {
	read -p "Enter a value:" value
	echo $[ $value*2 ]
}
result=$(db)
echo "The new value is $result"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8
9
10
11
12
13

# 17.3 在函数中使用变量

# 17.3.1 向函数传递参数

前边讲过,bash shell会将函数当做脚本一样对待,之前我们学习过向脚本传参的方式,仍然可以用到函数中

同脚本一样,在调用函数的时候,参数需要和函数名放在同一行

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test5.sh
adding 10 and 15 :25
Let‘s try adding just one number:20
Let‘s try adding three number:-1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test5.sh
#!/bin/bash
function addem {
	if [ $# -eq 0 ] || [ $# -gt 2 ]
	then
	  echo -1
	elif [ $# -eq 1 ]
	then
	  echo $[ $1*2 ]
	else
	  echo $[ $1 + $2 ]
	fi
	
}
echo -n "adding 10 and 15 :"
value=$(addem 10 15)
echo $value
echo -n "Let‘s try adding just one number:"
value=$(addem 10)
echo $value
echo -n "Let‘s try adding three number:"
value=$(addem 1 2 3)
echo $value[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
#函数不能直接使用脚本的参数,如果想要在函数中使用脚本的参数那么需要手动将参数传进去

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test6.sh 1 2
The result is 2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test6.sh
#!/bin/bash
# trying to access script parameters inside a function

function badfunc1 {
   echo $[ $1*$2 ]
}

if [ $# -eq 2 ]
then
   value=$(badfunc1 $1 $2)
   echo "The result is $value"
else
   echo "Usage: badtest1 a b"
fi
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
#否则就会像下面这样
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test6.sh 1 2
./test6.sh: line 5: * : syntax error: operand expected (error token is "* ")
The result is 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test6.sh
#!/bin/bash
# trying to access script parameters inside a function

function badfunc1 {
   echo $[ $1*$2 ]
}

if [ $# -eq 2 ]
then
   value=$(badfunc1)
   echo "The result is $value"
else
   echo "Usage: badtest1 a b"
fi[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 17.3.2 在函数中处理变量

函数有两种类型的变量

1、全局变量

2、局部变量

1、全局变量

默认情况下,在脚本中定义的任何变量都是全局变量,在函数外定义的变量可以在函数内正常访问。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test7.sh
#!/bin/bash
function db1 {
	value=$[$value*2]
}
read -p "Enter -p a value:" value
db1
echo "The new value is :$value"

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test7.sh
Enter -p a value:10
The new value is :20
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14

2、局部变量

无须在函数中使用全部变量,任何在函数内部使用的变量都可以被生命为局部变量,只需要在变量钱加上local即可。

# 17.4 数组变量和函数

第五章中讨论过使用数组在单个变量中保存多个值的高级用法

# 17.4.1 向函数传递数组

如果是将数组作为单个参数传递的话,那么不会起作用

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test9.sh
#!/bin/bash
function testit {
	echo "参数是:$@"
	thisarray=$1
	echo "The received array is ${thisarray[*]}"
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
testit $myarray
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test9.sh
The original array is: 1 2 3 4 5
参数是:1
The received array is 1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
#可以看到函数其实只接收了一个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

新的写法如下

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test10.sh
#!/bin/bash

function testit {
	local newarray
	newarray=(`echo "$@"`)
	echo "新的数组是 : ${newarray[*]}"
}

myarray=(1 2 3 4 5)
echo "The origin arrray is ${myarray[*]}"
testit ${myarray[*]}  #可以看到这里传的是数组
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test10.sh
The origin arrray is 1 2 3 4 5
新的数组是 : 1 2 3 4 5
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test11.sh
原来的数组是: 1 2 3 4 5
新的数组是 : 2 4 6 8 10
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test11.sh
#!/bin/bash
function arrydblr {
	local origarray
	local newarray
	local elements
	
	local i
	origarray=($(echo "$@"))
	newarray=($(echo "#@"))
	elements=$[$#-1]
	
	for (( i=0;i<=elements;i++)){
		newarray[$i]=$[ ${origarray[$i]}*2]
	}
	echo ${newarray[*]}
}
myarray=(1 2 3 4 5)
echo "原来的数组是: ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=$(arrydblr $arg1)
echo "新的数组是 : ${result[*]}"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 17.5 函数递归

例子:阶乘

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test12.sh
请输入:5
5的阶乘结果是120
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test12.sh
#!/bin/bash
function factorial {
	if [ $1 -eq 1 ]
	then 
	  echo 1
	else
	  local temp=$[$1-1]
	  local result=$(factorial $temp)
	  echo $[$result * $1]
	fi
}

read -p "请输入:" value
result=$(factorial $value)
echo "$value的阶乘结果是$result"
#创建一个函数不是很难,如果我们想将函数用到其他脚本中呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 17.6 创建库

使用教本可以帮我们省去一些重复性的工作,但是如果我们想要在多个脚本中使用,那么如果在每个脚本中都定义这个函数,又很麻烦。

其实bash shell允许我们创建函数库文件。

当我们有了库文件之后,那么如何使用呢?

$ cat myfuncs
# my script functions

function addem {
   echo $[ $1 + $2 ]
}

function multem {
   echo $[ $1 * $2 ]
}

function divem {
   if [ $2 -ne 0 ]
   then
      echo $[ $1 / $2 ]
   else
      echo -1
   fi
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

和环境变量一样,shell函数仅在定义它的shell会话内有效,如果在shell命令界面运行myfuncs脚本,那么shell会创建一个新的shell并在其中运行这个脚本。这种情况下,以上三个函数会定在新的shell中。当你运行另一个要用到这些函数的脚本时,它们是无法使用的。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test13.sh
./test13.sh: line 3: addem: command not found
结果是: 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test13.sh
#!/bin/bash
./myfuncs
result=$(addem 10 15)
echo "结果是: $result"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
1
2
3
4
5
6
7
8
9

那么如何解决呢?

使用source命令,他会在当前的shell上执行命令,而不是创建新的shell。

source命令还有一个别名,称作点号操作符。

如果想要运行刚才我们的脚本,可以写成../myfuncs如果myfuncs文件没有和脚本在同一个目录下,需要修改一下路径。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test14.sh
adding 15
multiplying 50
divding 2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test14.sh
adding 15
multiplying 50
divding 2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test14.sh
#!/bin/bash
source ./myfuncs
value1=10
value2=5
result1=$(addem $value1 $value2)
result2=$(multem $value1 $value2)
result3=$(divem $value1 $value2)

echo "adding $result1"
echo "multiplying $result2"
echo "divding $result3"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 17.7 在命令行中使用函数

有时候可以使用脚本函数执行一下复杂的操作,但有时候,我们想要在命令行直接使用函数。那么如何在shell中定义函数呢?

# 17.7.1 在命令行中创建函数

一共有两种方法

一种是采用单行方式来定义函数:

#在命令行中定义函数的时候,需要在每个命令后边加上分号,这样shell就能区分出来命令的起止。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# function add { echo $[$1 + $2];}
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# add 1 2
3
#需要加分号
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# function add2 { read -p "Enter value:" value; echo $[$value*2]; }
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# add2
Enter value:10
20
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

#多行方式定义函数
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# function add3 {}
-bash: syntax error near unexpected token `{}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# function add3 {
> echo $[ $1+$2 ]
> }
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# add3 1 2
3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
#需要注意的是,如果我们函数的名字和内建命令或者另一个命令相同,那么就会覆盖原来的命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 17.7.2 在.bashrc文件中定义函数

在上一章节中,我们定义的函数在退出shell 的时候,函数就会消失。

如果想要持久化,那么我们将函数定义到每次shell启动时,都会读取的地方,就是.bashrc文件。

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# cat .bashrc
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
	. /etc/bashrc
fi

echo "欢迎登录新的shell!"

function addem {
   echo $[ $1+$2 ];
}
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在我们登录新的shell的时候,直接使用函数就可以,当然,也可以将函数定义在别的文件中,然后在.bashrc文件中引入

更棒的是,shell还会将定义好的函数传给子shell进程,这样一来,这些函数就能够自动用于该shell会话中的任何shell脚本了。

# 17.8 实战演练

这一章将学习如何下载各种shell脚本函数并将其应用到自己的应用程序中。

# 17.8.1 下载及安装

ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz
tar -zxvf shtool-2.0.8.tar.gz
1
2
wget http://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz
tar -zxvf shtool-2.0.8.tar.gz
cd shtool-2.0.8/
rm -f ../shtool-2.0.8.tar.gz
./configure
make
make test
make install
1
2
3
4
5
6
7
8

# 17.8.2 构建库

# 17.8.3 shtool库函数

image-20240307220613042

shtool函数的使用格式如下:

shtool [options] [function [options] [args]]

# 17.8.4 使用库

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# ./test16.sh
centos 7.9.2009 (AMD64)
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# cat test16.sh
#!/bin/bash
#在函数中使用shtool函数的例子
shtool platform
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

1
2
3
4
5
6
7
8

# 18 图形化桌面环境

1、创建文本菜单

2、创建文本窗口部件

3、图形化窗口部件

本章将深入介绍一些可以为交互式脚本增添活力的方法,

# 18.1 创建文本菜单

交互式shell最常用的方法是使用菜单,菜单式脚本通常会清空显示区域,然后显示可用的菜单项列表,用户可以按下与每个菜单项关联的字母或数字来选择相应的选项。

image-20240307224839489

# 18.1.1 创建菜单布局

显示菜单之前,我们应该先清除屏幕上东西,默认情况下,echo只显示可打印文本字符,如果想显示非打印字符,如换行或者空格 可以使用-e选项

[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# echo -e "1. \t啦啦啦"
1. 	啦啦啦
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# echo "1. \t啦啦啦"
1. \t啦啦啦
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 

#使用-en选项会去掉结尾的换行符,效果就是光标会一直在行尾等待用户输入 效果如下
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# echo -en "\t\t lalala"
		 lalala[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# echo -e "\t\t lalala"#可以看到这一行和lalala在同一行
		 lalala
[root@iZuf6fdhtuwmr11b7hkk8pZ bash17]# 
1
2
3
4
5
6
7
8
9
10
11

# 18.1.2 创建菜单函数

通常的做法是为没一个菜单项创建一个单独的shell函数。

通常我们

18.2.1 dialog软件包

#安装dialog
yum install dialog

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# dialog --inputbox "输入你的年龄" 10 20 2>age.txt
1
2
3
4

image-20240308091947708

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# dialog --msgbox hahahahah 10 20

1
2

image-20240308092109102

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# dialog --title Testing --msgbox hahahahah 10 20
1

image-20240308092155663

2、yesno部件

# 19 初识sed和gawk

到目前为止,shell脚本最常见的用途是处理文本文件。检查日志文件、读取配置文件以及处理数据元素,shell脚本可以将文本文件中各种数据的日常处理任务自动化。

# 19.1 文本处理

可以自动格式化、插入、修改或删除文本元素的简单的命令行编辑器。

# 19.1.1 sed编辑器

命令行选项:

image-20240308095021146

1、在命令行中定义编辑器命令

替换命令(s)

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# echo "this is a test" | sed 's/test/big test/'
this is a big test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#注意最后还有一个/
1
2
3
4

需要注意的是,sed并不会修改文本文件的数据,只是将修改后的文件输出出来,原来的文件还是老样子。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat dara1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 's/dog/cat/' dara1.txt
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat dara1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

2、在命令行中使用多个编辑器命令

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -e 's/brown/red/; s/dog/cat/' dara1.txt
The quick red fox jumps over the lazy cat.
The quick red fox jumps over the lazy cat.
The quick red fox jumps over the lazy cat.
The quick red fox jumps over the lazy cat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat dara1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#这行命令的意思是将red替换成brown  cat替换成dog   两个命令都会应用于文件的每一行数据,并且命令之间必须以分号分隔开  命令和分号之间不能有空格
#如果不想用分号,可以用bash shell中的次提示符来分割命令 就像下边这样
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -e '
> s/brown/red/
> s/dog/cat/
> s/fox/toad/' dara1.txt
The quick red toad jumps over the lazy cat.
The quick red toad jumps over the lazy cat.
The quick red toad jumps over the lazy cat.
The quick red toad jumps over the lazy cat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

3、从文件中读取编辑器命令

如果有大量的命令,我们可以事先将命令写进文件中。然后使用-f命令使用即可。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -f test1.sh dara1.txt
The quick green toad jumps over the lazy cat.
The quick green toad jumps over the lazy cat.
The quick green toad jumps over the lazy cat.
The quick green toad jumps over the lazy cat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat dara1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat test1.sh   #这种情况下命令行之间不用加分号
s/brown/green/
s/fox/toad/
s/dog/cat/[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 19.1.2 gawk编辑器

gawk比sed的流编辑提升了一个“段位”,它提供了一种编程语言,而不仅仅是编辑器命令。

  • 定义变量来保存数据。
  • 使用算术和字符串运算符来处理数据。
  • 使用结构化编程概念(比如if-then语句和循环)为数据处理添加处理逻辑。
  • 提取文件中的数据将其重新排列组合,最后生成格式化报告。

1、gawk命令格式

gawk options program file

image-20240314211016941

2、从命令行读取gawk脚本

必须将gawk命令放到花括号之间

需要将gawk命令放到单引号中

如果只是在命令行中输入

gawk '{ print "Hello world"}' 那么什么也不会发生,因为gawk会从STDIN接受数据,会一直等待。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk '{ print "Hello world"}'
xx
Hello world
cc
Hello world
casd
Hello world
asdfsf
Hello world
sfs
Hello world
#如果想要终止命令 可以使用Ctrl+D(可以生成EOF  end-of-file)

1
2
3
4
5
6
7
8
9
10
11
12
13

3、使用数据字段变量

gawk会自动为每一行的各个数据元素分配一个变量。

  • $0代表整个文本行。
  • $1代表文本行中的第一个数据字段。
  • $2代表文本行中的第二个数据字段。
  • $n代表文本行中的第n个数据字段。

分割的依据是字段分隔符,通常情况下是(空格或者制表符)。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk '{print $1}' data2.txt
One
Two
Three
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk '{print $2}' data2.txt
line
lines
lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk '{print $0}' data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk '{print $9}' data2.txt



[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#如果想要指定其他的字段分隔符,可以使用-F选项指定
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk -F: '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
operator
games
ftp
nobody
systemd-network
dbus
polkitd
sshd
postfix
chrony
nscd
tcpdump
rpc
rpcuser
nfsnobody
test
rich
Tim
Barabra
tss
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

4、在脚本中使用多条命令

使用分号分割命令

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# echo "My name is shiyan" | gawk '{$4="hahaha"; print $0 }'
My name is hahaha
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# echo "My name is shiyan" | gawk '{
> $4="hahaha"
> print $0 }'
My name is hahaha
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8

5、从文件中读取脚本

和sed编辑器一样 使用-f来读取脚本

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk -F: -f test2.sh /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
sync's home directory is /sbin
shutdown's home directory is /sbin
halt's home directory is /sbin
mail's home directory is /var/spool/mail
operator's home directory is /root
games's home directory is /usr/games
ftp's home directory is /var/ftp
nobody's home directory is /
systemd-network's home directory is /
dbus's home directory is /
polkitd's home directory is /
sshd's home directory is /var/empty/sshd
postfix's home directory is /var/spool/postfix
chrony's home directory is /var/lib/chrony
nscd's home directory is /
tcpdump's home directory is /
rpc's home directory is /var/lib/rpcbind
rpcuser's home directory is /var/lib/nfs
nfsnobody's home directory is /var/lib/nfs
test's home directory is /home/test
rich's home directory is /home/rich
Tim's home directory is /home/Tim
Barabra's home directory is /home/Barabra
tss's home directory is /dev/null
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat test2.sh
{ print $1 "'s home directory is " $6 }
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat test2.sh
{
text = "'s home directory is"
print $1 text $6  #可以看到直接打印了text 说明在gawk脚本中,应用变量的时候不用加$
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

6、在处理数据前运行脚本

gawk还允许指定脚本什么时候运行,有时候我们想在数据读取之前执行一些操作。


[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk 'BEGIN {print "Hello world"}' 
Hello world
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#可以我们还没有输入数据 直接就打印了Hello World  这是因为BEGIN关键字在处理任何数据之前仅应用指定的脚本。 就是BEGIN关键字对第一个花括号起作用了的意思,如果想要正常读取数据,可以这么操作:
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk 'BEGIN {print "这是一个标题"}
> {print $0}' data3.txt
这是一个标题
Line 1
Line 2
Line 3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#定义了两个花括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14

7、在处理数据后运行脚本

END

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk 'BEGIN {print "这是一个标题"}
{print $0}
> END {print "结束了"}' data3.txt
这是一个标题
Line 1
Line 2
Line 3
结束了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# gawk -f test3.sh /etc/passwd
The latest list of users and shells
UserID  	 Shell
------- 	 -------
root       	 /bin/bash
bin       	 /sbin/nologin
daemon       	 /sbin/nologin
adm       	 /sbin/nologin
lp       	 /sbin/nologin
sync       	 /bin/sync
shutdown       	 /sbin/shutdown
halt       	 /sbin/halt
mail       	 /sbin/nologin
operator       	 /sbin/nologin
games       	 /sbin/nologin
ftp       	 /sbin/nologin
nobody       	 /sbin/nologin
systemd-network       	 /sbin/nologin
dbus       	 /sbin/nologin
polkitd       	 /sbin/nologin
sshd       	 /sbin/nologin
postfix       	 /sbin/nologin
chrony       	 /sbin/nologin
nscd       	 /sbin/nologin
tcpdump       	 /sbin/nologin
rpc       	 /sbin/nologin
rpcuser       	 /sbin/nologin
nfsnobody       	 /sbin/nologin
test       	 /bin/sh
rich       	 /bin/bash
Tim       	 /bin/bash
Barabra       	 /bin/bash
tss       	 /sbin/nologin
This concludes the listing
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat test3.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 19.2 sed编辑器基础命令

将介绍一些运用于脚本的基础命令和功能。

# 19.2.1 更多的替换选项

1、替换标志

#只替换了第一处
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data4.txt
This is a test of the test script.
This is the second test of the test script.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 's/test/trial/' data4.txt
This is a trial of the test script.
This is the second trial of the test script.
#如果想替换每行中的所有文本,必须使用替换标志,共有四种:
1
2
3
4
5
6
7
8
  • 数字,指明新文本将替换行中的第几处匹配。
  • g,指明新文本将替换行中所有的匹配。
  • p,指明打印出替换后的行。
  • w file,将替换的结果写入文件。
#数字 告诉sed编辑器用新文本替换第几处
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 's/test/trial/2' data4.txt
This is a test of the trial script.
This is the second test of the trial script.
#g global 替换所有的匹配
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 's/test/trial/g' data4.txt
This is a trial of the trial script.
This is the second trial of the trial script.
#p 会打印出来包含替换命令中指定匹配模式的文本行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 's/test/trial/p' data5.txt
This is a trial line.
This is a trial line.
This is a different line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -n 's/test/trial/p' data5.txt
This is a trial line.
#-n选项会抑制sed编辑器的输出,而替换标志p会输出替换后的行。将二者配合使用的结果就是只输出被替换命令修改过的行。
#替换标志w会产生同样的输出,不过会将输出保存到指定文件中,sed编辑器的正常输出会被保存在STDOUT中,只有那些包含匹配模式的行才会被保存在指定的输出文件中。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed  's/test/trial/w data6.txt' data5.txt
This is a trial line.      #正常输出
This is a different line.  #正常输出
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data6.txt
This is a trial line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -n 's/test/trial/w data6.txt' data5.txt  #禁止输出
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data6.txt
This is a trial line.    #w选项的输出被输出到了data6.txt中
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

2、替代字符

以上的案例中,我们都是用/来表示分隔符,如果我们替换的文本中刚好包含/那么必须使用\来转义就会很繁琐,这时候我们可以使用其他字符来表示分割

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 's!/bin/bash!/bin/esh!' /etc/passwd
1

# 19.2.2 使用地址

默认情况下,sed的命令会应用于所有的文本行,如果我们不想将命令应用于所有的文本行,那么可以使用行寻址。

  • 以数字形式表示的行区间。
  • 匹配行内文本的模式。

1、数字形式的行寻址

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '2,3s/dog/cat/' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#我们发现只替换了第2 3行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '1,3s/dog/cat/' data7.txt
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '2,$s/dog/cat/' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '1s/dog/cat/' data7.txt
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#$表示最后一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

2、使用文本模式过滤

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# grep /bin/bash /etc/passwd
root:x:0:0:root:/root:/bin/bash
rich:x:1001:1002::/home/rich:/bin/bash
Tim:x:1002:1004:TimXx:/home/Tim:/bin/bash
Barabra:x:1003:1005:BaraBara:/home/Barabra:/bin/bash
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/rich/s/bash/csh/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
......
test:x:1000:1000::/home/test:/bin/sh
rich:x:1001:1002::/home/rich:/bin/csh#被替换为了csh

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

3、命令组

如果需要匹配多种模式,那么可以使用花括号将一组命令组合在一起.

#只将第二行的fox替换为toad  dog替换为cat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '2{
> s/fox/toad/
> s/dog/cat/
> }' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown toad jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11

# 19.2.3 删除行

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 'd' data7.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '3d' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '2,3d' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '3,$d' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#也可以使用文本匹配模式
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/dog/d' data7.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/dosg/d' data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

另外,还可以使用两个文本模式来删除某个区间内的行,但是需要小心。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data8.txt
This is line number 1.
This is line number 2.
This is the 3rd line.
This is the 4th line.
This is line number 1 again; we want to keep it.
This is more text we want to keep.
Last line in the file; we want to keep it.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/1/,/3/d' data8.txt
This is the 4th line.
1
2
3
4
5
6
7
8
9
10

为什么只剩下了第四行,是因为第五行中的1 是匹配的,但是在后边的行中没有找到匹配3的行,所以会一直删除到文章末尾。

# 19.2.4 插入和附加文本

插入(insert)(i)命令会在指定行前增加一行。

附加(append)(a)命令会在指定行后增加一行。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# echo "Test Line 3" | sed 'i\Test line 1'
Test line 1  #加在了Test Line 3之前
Test Line 3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# echo "Test Line 3" | sed 'a\Test line 1'
Test Line 3
Test line 1  #加在了Test Line 3之后
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

#同样可以使用行寻址
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '3i\  lallaal' data8.txt
This is line number 1.
This is line number 2.
  lallaal
This is the 3rd line.
This is the 4th line.
This is line number 3 again; we want to keep it.
This is more text we want to keep.
This is the 3rd linesss.
Last line in the file; we want to keep it.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '3a\  lallaal' data8.txt
This is line number 1.
This is line number 2.
This is the 3rd line.
  lallaal
This is the 4th line.
This is line number 3 again; we want to keep it.
This is more text we want to keep.
This is the 3rd linesss.
Last line in the file; we want to keep it.

#如果有多行数据想要加到文章的末尾,记得可以使用$符号,他代表文章末尾。
#如果想要一次性添加多行文本,那么需要在行结束的时候加上\  而且这两行文本在命令行也要分行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '1i\
> lallaal\
> hhahahah
> ' data8.txt
lallaal
hhahahah
This is line number 1.
This is line number 2.
This is the 3rd line.
This is the 4th line.
This is line number 3 again; we want to keep it.
This is more text we want to keep.
This is the 3rd linesss.
Last line in the file; we want to keep it.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '1i\  lallaal\n hahaahhaha' data8.txt
  lallaal
 hahaahhaha
This is line number 1.
This is line number 2.
This is the 3rd line.
This is the 4th line.
This is line number 3 again; we want to keep it.
This is more text we want to keep.
This is the 3rd linesss.
Last line in the file; we want to keep it.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 19.2.5 修改行

修改命令可以修改数据流中整行文本的内容,但是必须单独指定某一行。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '2c\ lallalalalalal' data7.txt
The quick brown fox jumps over the lazy dog.
 lallalalalalal
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data7.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#同样可以使用文本模式来匹配
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/3th line/c\ "改变这一行"' data9.txt
This is line number 1.
This is line number 2.
 "改变这一行"
This is the 4th line.
#下边的例子展示了会改变所有匹配到的行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/have 6 Infinity/c\
"Snap! This is changed ling of text"
' data10.txt
I have 2 Infinity Stones
I need 4 more Infinity Stones
"Snap! This is changed ling of text"
I need 4 Infinity Stones
"Snap! This is changed ling of text"
I want 1 more Infinity Stone
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data10.txt
I have 2 Infinity Stones
I need 4 more Infinity Stones
I have 6 Infinity Stones!
I need 4 Infinity Stones
I have 6 Infinity Stones...
I want 1 more Infinity Stone
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#如果想要指定行区间,那么可能结果不会如愿,因为sed会替换掉两行文本
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '2,3c\
> lalalalalaal
> ' data9.txt
This is line number 1.
lalalalalaal
This is the 4th line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9

# 19.2.6 转换命令

转换命令是唯一可以单独处理单个字符的sed编辑器命令。

#可以看到 7替换了1 8替换了2 并不是789替换了123
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed 'y/123/789/' data11.txt
This is line 7.
This is line 8.
This is line 9.
This is line 4.
This is line 5.
This is line 7 again.
This is line 9 again.
This is the last file line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data11.txt
This is line 1.
This is line 2.
This is line 3.
This is line 4.
This is line 5.
This is line 1 again.
This is line 3 again.
This is the last file line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#转换命令是一个全局命令,他会对文本行中匹配到的所有指定字符进行转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 19.2.7 再探打印

还有三个命令也能打印数据流中的信息:

  • 打印(p)命令用于打印文本行。
  • 等号(=)命令用于打印行号。
  • 列出(l)命令用于列出行。

1、打印行

除了之前我们了解到的p的作用之外,如果需要在使用替换或修改命令做出改动之前查看相应的行,可以使用打印命令。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -n '/3/{
p
s/line/test/p
}' data11.txt
This is line 3.
This is test 3.
This is line 3 again.
This is test 3 again.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#可以看到在文本被替换之前,打印出来了原来的数据
1
2
3
4
5
6
7
8
9
10

2、打印行号

每次出现一个换行符,sed编辑器就认为有一行文本结束了。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data11.txt
This is line 1.
This is line 2.
This is line 3.
This is line 4.
This is line 5.
This is line 1 again.
This is line 3 again.
This is the last file line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '=' data11.txt
1
This is line 1.
2
This is line 2.
3
This is line 3.
4
This is line 4.
5
This is line 5.
6
This is line 1 again.
7
This is line 3 again.
8
This is the last file line.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#-n禁止输出  =输出行号 p输出匹配的文本
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -n '/again/{
> =
> p
> }' data11.txt
6
This is line 1 again.
7
This is line 3 again.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

3、列出行

列出命令可以打印数据流中的文本和不可打印字符。


[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed -n 'l' data12.txt
This\tline \tcontains \ttabs$
This line does contain tabs$
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data12.txt
This	line 	contains 	tabs
This line does contain tabs
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9

# 19.2.8 使用sed处理文件

1、写入文件

使用w命令用来向文件写入行

可以使用相对路径或绝对路径,但不管使用哪种,运行sed编辑器的用户都必须有文件的写权限。地址可以是sed支持的任意类型的寻址方式,比如单个行号、文本模式、行区间或文本模式区间。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '1,2w data15.txt' data14.txt
1
2
3
4
5

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data15.txt
1
2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#将data14.txt文件中1,2行的的内容覆盖到data15.txt中,如果data15.txt文件不存在,那么创建并且写入值,如果存在那么覆盖原来的内容。
1
2
3
4
5
6
7
8
9
10
11
12

2、从文件中读取数据

#将data13.txt的数据插入到data14.txt内容的第三行后边
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '3r data13.txt' data14.txt
1
2
3
This is line number 1
This is line number 2
4
5

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data14.txt
1
2
3
4
5

[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat data13.txt
This is line number 1
This is line number 2
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 
#同样可以使用文本匹配模式 以及 行寻址,同样,如果想在末尾加入文件,那么可以使用美元符号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

读取命令还有一种很酷的用法是和删除命令配合使用,利用另一个文件中的数据来替换文件中的占位文本。

#用data14.txt的文本内容替换了notice.std文本中的LIST
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# cat notice.std
Would the following people:
LIST
please report to the ship's captain.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# sed '/LIST/{
> r data14.txt
> d
> }' notice.std
Would the following people:
1
2
3
4
5

please report to the ship's captain.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash19]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 19.3 实战演练

$ cat ChangeScriptShell.sh
#!/bin/bash
# Change the shebang used for a directory of scripts
#
################## Function Declarations ##########################
#
function errorOrExit {
        echo
        echo $message1
        echo $message2
        echo "Exiting script..."
        exit
}
#
function modifyScripts {
        echo
        read -p "Directory name in which to store new scripts? " newScriptDir
        #
        echo "Modifying the scripts started at $(date +%N) nanoseconds"
        #
        count=0
        for filename in $(grep -l "/bin/sh" $scriptDir/*.sh)
        do
                newFilename=$(basename $filename)
                cat $filename |
                sed '1c\#!/bin/bash' > $newScriptDir/$newFilename
                count=$[$count + 1]
        done
        echo "$count modifications completed at $(date +%N) nanoseconds"
} 
#
################# Check for Script Directory ######################
if [ -z $1 ]
then
        message1="The name of the directory containing scripts to check"
        message2="is missing. Please provide the name as a parameter."
        errorOrExit
else
        scriptDir=$1
fi
#
################ Create Shebang Report ############################
#
sed -sn '1F;
1s!/bin/sh!/bin/bash!' $scriptDir/*.sh |
gawk 'BEGIN {print ""
print "The following scripts have /bin/sh as their shebang:"
print "==================================================="}
{print $0}
END {print ""
print "End of Report"}'
#
################## Change Scripts? #################################
#
#
echo
read -p "Do you wish to modify these scripts' shebang? (Y/n)? " answer
#
case $answer in
Y | y)
        modifyScripts
        ;;
N | n)
        message1="No scripts will be modified."
        message2="Run this script later to modify, if desired."
        errorOrExit
        ;;
*)
        message1="Did not answer Y or n."
        message2="No scripts will be modified."
        errorOrExit
        ;;
esac
$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 20 正则表达式

本章内容

  • 正则表达式基础
  • 定义BRE模式
  • 扩展正则表达式

# 20.1 正则表达式基础

# 20.1.1 定义

正则表达式是一种可供Linux工具过滤文本的自定义模板。

Linux工具(比如sed或gawk)会在读取数据时使用正则表达式对数据进行模式匹配。如果数据匹配模式,它就会被接受并进行处理。

image-20240315172325696

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls
age.txt                 duoblebracket.sh                     test12
andboolean.sh           file1                                test1.sh
bash13                  file2                                test2
bash14                  is-it-empty.sh                       test3
bash15                  jump_point.sh                        test3.sh
bash16                  log.240131                           test3.txt
bash17                  PackageMgrCheck.sh                   test4.sh
bash18                  rpm.ist                              test4.txt
bash19                  rpm.list                             test5
can-read-it.sh          sales                                test6
can-run-it.sh           saless                               test6.sh
can-write-to-it.sh      sentinel                             test7
check_default_group.sh  ShortCase.sh                         test8
check_file_dates.sh     springboot_01_03-0.0.1-SNAPSHOT.jar  test9
dir-or-file.sh          test1                                test9.sh
do-i-own-it.sh          test10                               testdir
doubleparenteses.sh     test11                               update_file.sh
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# ls -al rpm*
-rw-r--r-- 1 root root 16808 Jan 31 13:01 rpm.ist
-rw-r--r-- 1 root root 16808 Feb  1 14:56 rpm.list
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 20.1.2 正则表达式的类型

image-20240315172720639

# 20.2 定义BRE模式

最基本的BRE模式是匹配数据流中的文本字符。

# 20.1.1 普通文本

区分大小写

# 20.2.2 特殊字符

.*[]^${}/\+?|() 这些都是特殊字符,如果想要使用这些特殊字符,那么必须进行转义。

# 20.2.3 锚点字符

有两个特殊字符可以用来将模式锁定在数据流中的行首或者行尾

1、锁定行首

^ (脱字符)可以指定位于数据流中文本行行首的模式。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "The Book store" | sed -n "/^book/p"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "The Book store" | sed -n "/^Book/p"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "The Book store" | sed -n "/^The/p"
The Book store
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
1
2
3
4
5

如果将脱字符放到正则表达式开头之外的地方,那么他就跟特殊字符一样,没有什么特殊含义了。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "This ^ is a test" | sed -n '/s ^ /p'
This ^ is a test
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4

如果正则表达式模式中只有脱字符,就不必用反斜线来转义。但如果在正则表达式中先指定脱字符,随后还有其他文本,那就必须在脱字符前用转义字符。

image-20240316175538234

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "This ^ is a test" | sed -n '/^ is /p'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "This ^ is a test" | sed -n '/\^ is /p'
This ^ is a test
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5

2、锚定行尾

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "This is a good book" | sed -n '/book$/p'
This is a good book

1
2
3

3、组合锚点

#查找特定文本
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/^this is a test$/p' data1.txt
this is a test
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# cat data1.txt
this is a test of using both anchors
I said this is a test
this is a test
I'm sure this is a test.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

#查找空行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/^$/p' data2.txt

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed '/^$/d' data2.txt
this is noe test line.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 20.2.4 点号字符

点号字符可以匹配除换行符之外的任意单个字符。点号字符必须匹配一个字符,如果在点号字符的位置没有可匹配的字符,那么模式就不成立。

注意是必须要匹配一个。

#可以看到at也可以匹配空格字符
root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# cat data3.txt
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/.at/p' data3.txt
The cat is sleeping.
That is a very nice hat.
This test is at line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13

# 20.2.5 字符组

点号字符很有用,但是我们不能指定想要匹配的指定字符,这时候我们可以使用字符组。

方括号用来定义字符组,在方括号中加入我们希望出现的字符即可。

#这时候我们看到,空格字符被过滤掉了 只有ch留了下来
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/[ch]at/p' data3.txt
The cat is sleeping.
That is a very nice hat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#另外,在不太确定某个字符的大小写时就可以使用字符组
#我们还可以将多个字符组组合在一起,来检查数字是否具备正确的格式
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/[0123456789][0123456789][0123456789][0123456789][0123456789]/p' data4.txt
60633
46201
223001
22203
#出现了意外,我们明明写了五个字符组,为什么六位数字的数字也出现了,是因为我们没有限制长度,这时候可以加上锚点字符,如下:
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p' data4.txt
60633
46201
22203
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 20.2.6 排除行字符组

正则表达式中,我们也可以使用反转字符组的作用:匹配字符组中没有的字符。

#使用排除方法的时候,我们发现包含hat  cat被过滤掉了,因为他们包含ch,而只有一个空格的被留下来了。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/[^ch]at/p' data3.txt
This test is at line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# cat data3.txt
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#但即使是排除型,字符组仍必须匹配一个字符,以at为起始的行还是不能匹配模式
1
2
3
4
5
6
7
8
9
10
11
12

# 20.2.7 区间

使用单连字符在字符组中表示字符区间。

返回刚才的例子我们可以这么写:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data4.txt
60633
46201
22203
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#同理,也适用于字母
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/[c-h]at/p' data3.txt
The cat is sleeping.
That is a very nice hat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#指定多个区间
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# sed -n '/[a-ch-m]at/p' data3.txt
The cat is sleeping.
That is a very nice hat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "I'm getting too fat" | sed -n '/[a-ch-m]at/p' 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 20.2.8 特殊的字符组

image-20240316182915249

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "abc" | sed -n '/[[:digit:]]/p'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "abc" | sed -n '/[[:alpha:]]/p'
abc
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5

# 20.2.9 星号

在字符后边放置星号表名该字符必须在匹配模式的文本中出现0次或者n次。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "abc" | sed -n '/[[:alpha:]]/p'
abc
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ik" | sed -n '/ie*k/p'
ik
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "iek" | sed -n '/ie*k/p'
iek
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ieek" | sed -n '/ie*k/p'
ieek
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ieeek" | sed -n '/ie*k/p'
ieeek
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#可以用来接受单词拼写错误的情况 比如e可以写也可以不写
#另一个方便的写法是将点号和星号匹配起来,因为点可以匹配任意字符,星号可以匹配任意数量,这样就可以匹配任意数量的任意字符了。
#应用于字符组:  表名a和e字母以任意形式出来都可以只要a或者e出现或者不出现都可以  但是如果出现别的单词就不行了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ik" | sed -n '/ie*k/p'
ik
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "iek" | sed -n '/ie*k/p'
iek
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ieek" | sed -n '/ie*k/p'
ieek
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ieeek" | sed -n '/ie*k/p'
ieeek
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | sed -n '/b[ae]*t/p'
bt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bat" | sed -n '/b[ae]*t/p'
bat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | sed -n '/b[ae]*t/p'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "btt" | sed -n '/b[ae]*t/p'
btt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baaaeet" | sed -n '/b[ae]*t/p'
baaaeet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baaeaeaeet" | sed -n '/b[ae]*t/p'
baaeaeaeet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baaeaekaeet" | sed -n '/b[ae]*t/p'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 20.3 扩展正则表达式

警告 记住,sed和gawk的正则表达式引擎之间是有区别的。gawk可以使用大多数扩展的正则表达式符号,并且能够提供了一些sed所不具备的额外过滤功能。但正因如此,gawk在处理数据时往往比较慢。

本节将介绍可用于gawk脚本中常见的ERE模式符号

# 20.3.1 问号

问号与型号类似,但是问号表名前边的字符只出现一次或者0次 ,不会匹配出现多次的字符:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | gawk '/be?t/{print $0}'
bt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk '/be?t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beet" | gawk '/be?t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beeet" | gawk '/be?t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#和星号一样,问号也可以和字符组一起使用,但是a或者e只能出现一次,如果都出现了,那么就是出现了两次,就不成立了
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beeet" | gawk '/b[ae]?t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | gawk '/b[ae]?t/{print $0}'
bt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bat" | gawk '/b[ae]?t/{print $0}'
bat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beat" | gawk '/b[ae]?t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk '/b[ae]?t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 20.3.2 加号

加号也是类似于星号和问号的模式,但是加号表名前边的字符可以出现一次或者多次,如果没有出现那么不匹配。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk '/be+t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | gawk '/be+t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beet" | gawk '/be+t/{print $0}'
beet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beeet" | gawk '/be+t/{print $0}'
beeet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#同样适用于字符组
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | gawk '/b[ea]+t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bat" | gawk '/b[ea]+t/{print $0}'
bat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk '/b[ea]+t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baet" | gawk '/b[ea]+t/{print $0}'
baet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baeet" | gawk '/b[ea]+t/{print $0}'
baeet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "ebaeet" | gawk '/b[ea]+t/{print $0}'
ebaeet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beaeet" | gawk '/b[ea]+t/{print $0}'
beaeet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 20.3.3 花括号

ERE中的花括号允许为正则表达式指定具体的可重复次数,这通常称为区间,可以使用两种格式来指定区间

  • m:正则表达式恰好出现m次。
  • m, n:正则表达式至少出现m次,至多出现n次。

警告 在默认情况下,gawk不识别正则表达式区间,必须指定gawk的命令行选项--re-interval才行。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | gawk --re-interval '/be{1}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk --re-interval '/be{1}t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beet" | gawk --re-interval '/be{1}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beet" | gawk --re-interval '/be{1}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beet" | gawk --re-interval '/be{1,2}t/{print $0}'
beet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beeet" | gawk --re-interval '/be{1,2}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bt" | gawk --re-interval '/be{1,2}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beeet" | gawk --re-interval '/be{1,2}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

#同样适用于字符组
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "beet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
baet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "baeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bet
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 20.3.4 竖线符号

竖线符号允许在检查数据流时,以逻辑OR方式指定正则表达式要使用的两个或多个模式。如果其中任何一个模式匹配了数据流文本,就视为匹配。如果没有模式匹配,则匹配失败。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "The cat is aslleep" | gawk '/cat|dog/{print $0}'
The cat is aslleep
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "The dog is aslleep" | gawk '/cat|dog/{print $0}'
The dog is aslleep
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "The sheep is aslleep" | gawk '/cat|dog/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#竖线符号 两侧的子表达式可以采用正则表达式可用的任何模式符号
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "He has a cat." | gawk '/[ch]at|dog/{print $0}'
He has a cat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "He has a hat." | gawk '/[ch]at|dog/{print $0}'
He has a hat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "He has a aog." | gawk '/[ch]at|dog/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "He has a dog." | gawk '/[ch]at|dog/{print $0}'
He has a dog.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "He dog a fat." | gawk '/[ch]at|dog/{print $0}'
He dog a fat.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 20.3.5 表达式分组

也可以使用圆括号对正则表达式进行分组,分组之后,每一组会被视为一个整体,可以像对普通字符一样对该组应用特殊字符。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "Sat" | gawk '/Sat(urday)?/{print $0}'
Sat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "urday" | gawk '/Sat(urday)?/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 
#下边这种模式理解了上边没理解
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "cat" |gawk '/(c|b)a(b|t)/{print $0}'
cat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "cab" |gawk '/(c|b)a(b|t)/{print $0}'
cab
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bat" |gawk '/(c|b)a(b|t)/{print $0}'
bat
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "bab" |gawk '/(c|b)a(b|t)/{print $0}'
bab
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "tab" |gawk '/(c|b)a(b|t)/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# echo "tac" |gawk '/(c|b)a(b|t)/{print $0}'
[root@iZuf6fdhtuwmr11b7hkk8pZ bash20]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 20.4 实战演练

# 20.4.1 目录文件计数

# 20.4.2 验证电话号码

# 20.4.3解析email地址

# 21 sed进阶

本章将介绍sed编辑器所提供的更多高级功能

# 21.1 多行命令

sed编辑器提供了三个可用于处理多行文本的特殊命令

  • N:加入数据流中的下一行,创建一个多行组进行处理。
  • D:删除多行组中的一行。
  • P:打印多行组中的一行。

# 21.1.1 next多行命令

1、单行命令

单行next命令会告诉sed编辑器移动到数据流的下一行,不用在返回到命令列表的最开始的位置。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '/Header/{n ; d}' data1.txt
Header Line
Data Line #1

End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data1.txt
Header Line

Data Line #1

End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13

先用脚本查找含有单词Header的那一行,找到之后,单行next命令会让sed编辑器移动到文本的下一行,也就是我们想删除的空行.先用脚本查找含有单词Header的那一行,找到之后,单行next命令会让sed编辑器移动到文本的下一行,也就是我们想删除的空行:

2、合并文本行

单行next命令会将数据流中的下一行移入sed编辑器的工作空间(称为模式空间)。

多行版本的next(N)命令则是将下一行添加到模式空间中已有文本之后。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '/First/{N ; s/\n/,/}' data2.txt
Header Line
First Data Line,Second Data Line
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#如果要在数据文件中查找一个可能会分散在两行中的文本短语,那么这是一个很管用的方法。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data3.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
Thank you for your cooperation.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'N; s/System Admin/DevOps Engineer/' data3.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All DevOps Engineers should attend.
Thank you for your cooperation.
#如果他们分散在两行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'N; s/System.Admin/DevOps Engineer/' data3.txt
On Tuesday, the Linux DevOps Engineer group meeting will be held.
All DevOps Engineers should attend.
Thank you for your cooperation.
#如果你不想让他们变成一行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'N; s/System\nAdmin/DevOps\nEngineer/
> s/System Admin/DevOps Engineer/
> ' data3.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All DevOps Engineers should attend.
Thank you for your cooperation.
#如果我们要找的数据在最后一行 那么可以这样写
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data4.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '
s/System Admin/DevOps Engineer/
N
s/System\nAdmin/DevOps\nEngineer/
' data4.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All DevOps Engineers should attend.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 21.1.2 多行删除命令

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data4.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
#单行删除命令和N配合使用时,会删除所有符合条件的行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'N; /System\nAdmin/d' data4.txt
All System Admins should attend.

#多行删除命令和N配合使用时,会删除该行中的换行符及其之前的所有字符  可以看到第二行依然完好
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'N; /System\nAdmin/D' data4.txt
Admin group meeting will be held.
All System Admins should attend.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#这里有个例子,删除Header前边的空行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data5.txt

Header Line
First Data Line

End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '/^$/{N ; /Header/D}' data5.txt
Header Line
First Data Line

End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#如果模式空间中含有单词Header,则D命令会删除模式空间中的第一行。如果不结合使用N命令和D命令,则无法做到在不删除其他空行的情况下只删除第一个空行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 21.1.3 多行打印命令

多行打印命令沿用了同样的方法,只打印模式空间的第一行;

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n 'N ; /System\nAdmin/P' data3.txt
On Tuesday, the Linux System
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data3.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
Thank you for your cooperation.

1
2
3
4
5
6
7
8

更厉害的用处如下:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat corruptData.txt
Header Line#
@
Data Line #1
Data Line #2#
@
End of Data Lines#
@
$
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '
> N
> s/#\n@//
> P
> D
> ' corruptData.txt
Header Line
Data Line #1
Data Line #2
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

数据文件被破坏了,在一些行的末尾有#,接着在下一行有@。为了解决这个问题,可以使用sed将Header Line#行载入模式空间,然后用N命令载入第二行(@),将其附加到模式空间内的第一行之后。替换命令用空值替换来删除违规数据(#\n@),然后P命令只打印模式空间中已经清理过的第一行。D命令将第一行从模式空间中删除,并返回到脚本的开头,下一个N命令将第三行(Data Line #1)文本读入模式空间,继续进行编辑循环。

# 21.2 保留空间

模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时保存着待检查的文本,但它并不是sed编辑器保存文本的唯一空间。

sed编辑器还有另一块称作保留空间(hold space)的缓冲区。当你在处理模式空间中的某些行时,可以用保留空间临时保存部分行。

image-20240317181708542

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '/First/ {
> h ;p ;
> n ; p;
> g ; p }
> ' data2.txt
First Data Line
Second Data Line
First Data Line
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

我们来一步一步地讲解这段代码。

(1) sed脚本使用正则表达式作为地址,过滤出含有单词First的行。

(2) 当出现含有单词First的行时,{}中的第一个命令h会将该行复制到保留空间。这时,模式空间和保留空间中的内容是一样的。

(3) p命令会打印出模式空间的内容(First Data Line),也就是被复制进保留空间中的那一行。

(4) n命令会提取数据流中的下一行(Second Data Line),将其放入模式空间。现在,模式空间和保留空间的内容就不一样了。

(5) p命令会打印出模式空间的内容(Second Data Line)。

(6) g命令会将保留空间的内容(First Data Line)放回模式空间,替换模式空间中的当前文本。模式空间和保留空间的内容现在又相同了。

(7) p命令会打印出模式空间的当前内容(First Data Line)。

通过保留空间来回移动文本行,可以强制First Data Line输出在Second Data Line之后。如果去掉第一个p命令,则可以将这两行以相反的顺序输出:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '/First/ {
h ;   
n ; p;
g ; p }
' data2.txt
Second Data Line
First Data Line
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9

# 21.3 排除命令

#感叹号用于排除命令
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '/Header/!p' data2.txt
First Data Line
Second Data Line
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data4.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'N;
> s/System\nAdmin/DevOps\nENgineer/
> s/System Admin/DecOps Engineer/
> ' data4.txt
On Tuesday, the Linux DevOps
ENgineer group meeting will be held.
All System Admins should attend.
#$!N表示对最后一行不执行N命令,会执行s/System\nAdmin/DevOps\nENgineer/
#s/System Admin/DecOps Engineer/  这两个命令
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '$!N;
s/System\nAdmin/DevOps\nENgineer/
s/System Admin/DecOps Engineer/
' data4.txt
On Tuesday, the Linux DevOps
ENgineer group meeting will be held.
All DecOps Engineers should attend.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '{1!G ; h; $p }' data2.txt
End of Data Lines
Second Data Line
First Data Line
Header Line
#如果p命令前不加$符号,那么每次将保留空间拷贝到模式空间都会打印
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '{1!G ; h; p }' data2.txt
Header Line
First Data Line
Header Line
Second Data Line
First Data Line
Header Line
End of Data Lines
Second Data Line
First Data Line
Header Line
#如果G前边不加1!,那么最后会多一行空行。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '{G ; h; $p }' data2.txt
End of Data Lines
Second Data Line
First Data Line
Header Line

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

image-20240323180304103

image-20240317181708542

# 24.1 分支

分支(b)命令,


#这行命令表示当涉及到2、3行的时候就跳过,否则就执行下边 s/Line/Replacment/这行#命令
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{2,3b ;
> s/Line/Replacment/}
> ' data2.txt
Header Replacment
First Data Line
Second Data Line
End of Data Replacments
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

#另外执行一个格式,指定label,当满足条件的时候 就跳到标签为jump脚本行。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{/First/b jump1;
> s/Line/Replacament/
> :jump1
> s/Line/Jump Replacament/}
> ' data2.txt
Header Replacament
First Data Jump Replacament
Second Data Replacament
End of Data Replacaments
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#下边是一个例子
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "This, is, a, test, to, remove, commas." |
> sed -n {'
> :start
> s/,//1p
> b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "This, is, a, test, to, remove, commas." |
> sed -n {'
> :start
> s/,//1p  #会先执行一次这一行命令
> /,/b start  #匹配到有逗号的时候
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 21.4.2 测试

测试命令 t

t命令可以理解为,如果t命令前边的命令能匹配的上,那么就执行t前边的命令,否则就执行t后边的命令。

#这行命令的意思是说,如果能够匹配得上前边的命令,那么就执行前边,否则执行后边的命令,我们可以看到其他的Line都被替换为了Replacement
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{s/First/matched/ ; t
s/Line/Replacement/}
' data2.txt
Header Replacement
matched Data Line
Second Data Replacement
End of Data Replacements
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#使用替换命令替换掉句子中的逗号
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "This, is, a, test, to, replace, commas."|
> sed -n '{
> :start
> s/,//1p   #如果这一行修改为 s/,//1p;p;那么可以知道会先执行一次这行命令
> t start
> }'
This is, a, test, to, replace, commas.
This is a, test, to, replace, commas.
This is a test, to, replace, commas.
This is a test to, replace, commas.
This is a test to replace, commas.
This is a test to replace commas.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 21.5 模式替换

既然我们已经知道了如果使用sed命令来替换文本,但是有时候不清楚替换了哪些文本。

当匹配一个单词的时候,

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "The cat sleeps in his hat" | sed 's/cat/"cat"/'
The "cat" sleeps in his hat
#但是使用.匹配多个单词的时候呢??
#正确的应该是这样子的
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "The cat sleeps in his hat" | sed 's/.at/oat/g'
The oat sleeps in his oat
#但是却出现了这种情况
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "The cat sleeps in his hat" | sed 's/.at/".at"/g'
The ".at" sleeps in his ".at"

1
2
3
4
5
6
7
8
9
10

# 21.5.1 &符号

为了解决上边的问题,sed提出了一个方案,&符号可以代表替换命令中的匹配模式

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "The cat sleeps in his hat" | sed 's/.at/"&"/g'
The "cat" sleeps in his "hat"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# echo "The cat sleeps in his hat" | sed 's/.at/"&"/g' | sed 's/.at/oat/g'
The "oat" sleeps in his "oat"
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6

# 21.5.2 替换单独的单词

# 21.6 在脚本中使用sed

# 21.6.1 使用包装器

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# ./reverse.sh data2.txt 
End of Data Lines
Second Data Line
First Data Line
Header Line
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat reverse.sh 
#!/bin/bash
sed -n '{1!G; h; $p;}' $1
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data2.txt 
Header Line
First Data Line
Second Data Line
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 21.6.2 重定向输出

主要讲解了将输出重定向到变量中。

在默认情况下,sed编辑器会将脚本的结果输出到STDOUT。但你可以在shell脚本中通过各种标准方法重定向sed编辑器的输出。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# ./fact.sh 20
The result is 2,432,902,008,176,640,000
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# ./fact.sh 5
The result is 120
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# ./fact.sh 6
The result is 720
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# ./fact.sh 7
The result is 5,040
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat fact.sh 
#!/bin/bash

factorial=1
counter=1
number=$1

while [ $counter -le $number ]
do
   factorial=$[$factorial*$counter]
   counter=$[$counter +1]
done

result=$(echo $factorial |
sed '{
:start
s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/   #用来添加逗号
t start
}')

echo "The result is $result"
exit
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 21.7 创建sed实用工具

# 21.7.1 加倍行间距

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 'G' data2.txt
Header Line

First Data Line

Second Data Line

End of Data Lines

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '$!G' data2.txt #最后一行不加空行
Header Line

First Data Line

Second Data Line

End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 21.7.2 对可能含有空行的文件加倍行间距

如果文本文件已经有一些空行,这时如果再加的话,那么就会有两行空行了,这时怎么办呢?

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{/^$/d; $!G}' data6.txt#先删除原来的空行
Line one.

Line two.

Line three.

Line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{$!G}' data6.txt
Line one.

Line two.



Line three.

Line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data6.txt
Line one.
Line two.

Line three.
Line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 21.7.3 给文件中的行编号

行号出现在了实际行的上方。更好的解决办法是将行号和文本放在同一行。

你已经知道如何用N命令合并行,在sed脚本中使用这个命令并不难。棘手之处在于,无法将两个命令放到同一个脚本中。在获得了等号命令的输出之后,可以通过管道将输出传给另一个sed编辑器脚本,由后者使用N命令来合并这两行。还需使用替换命令将换行符更换成空格或制表符。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '=' data2.txt | sed 'N; s/\n/ /' 
1 Header Line
2 First Data Line
3 Second Data Line
4 End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7

有些命令也可以添加行号:

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# nl data2.txt
     1	Header Line
     2	First Data Line
     3	Second Data Line
     4	End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat -n data2.txt
     1	Header Line
     2	First Data Line
     3	Second Data Line
     4	End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

#用书中的脚本没有测试成功,自己写的可以了,其中test7.txt中是五个空格
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n 'l' test7.txt
a     b$
a\tb$
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# nl data2.txt | sed 's/     //; s/\t//'
1Header Line
2First Data Line
3Second Data Line
4End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# nl data2.txt
     1	Header Line
     2	First Data Line
     3	Second Data Line
     4	End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# nl data2.txt | sed -n 'l'
     1\tHeader Line$
     2\tFirst Data Line$
     3\tSecond Data Line$
     4\tEnd of Data Lines$
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 21.7.4 打印末尾行

#直接打印最后一行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed -n '$p' data2.txt
End of Data Lines
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#滚动窗口
 #11,$D 的意思是,如果有11行,那么就删除最后一行,以为我们要保留十行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{
:start
$q ; N ; 11,$D        
b start
}' data7.txt
Line12
Line13
Line14
Line15
Line16
Line17
Line18
Line19
Line20
Line21
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 21.7.5 删除行

#1、删除连续的空行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '/./,/^$/!d' data9.txt
Line one.

Line two.

Line three.

Line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data9.txt


Line one.

Line two.


Line three.


Line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#2、删除开头的空行
#这意味着含有字符的第一行之前的任何行都会被删除。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '/./,$!d' data9.txt
Line one.

Line two.


Line three.


Line four.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#1、删除结尾的空行
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed '{
:start
/^\n*$/{$d;N;b start}
}' data11.txt
Line one.
Line two.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data11.txt
Line one.
Line two.




[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 21.7.6 删除html标签

[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# cat data12.txt
<html>
<head>
<title>This is the page title</title>
</head>
<body>
<p>
This is the <b>first</b> line in the Web page.
This should provide some <i>useful</i>
information to use in our sed script.
</body>
</html>
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 's/<[^>]*>//g' data11.txt
Line one.
Line two.




[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 's/<[^>]*>//g' data12.txt


This is the page title



This is the first line in the Web page.
This should provide some useful
information to use in our sed script.


[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# sed 's/<[^>]*>//g; /^$/d' data12.txt
This is the page title
This is the first line in the Web page.
This should provide some useful
information to use in our sed script.
[root@iZuf6fdhtuwmr11b7hkk8pZ bash21]# 
#参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 21.8 实战演练


1

# 22 gawk进阶

# 22.1 使用变量

内建变量

自定义变量

# 22.1.1 内建变量

1、字段和记录分隔符变量

OFS变量默认是一个空格。

在默认情况下,字段分隔符是一个空白字符,也就是空格或者制表符。

image-20240417213732681


[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1.txt
data11 data12 data13
data21 data22 data23
data31 data32 data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","; OFS="--"} {print $1,$2,$3}' data1.txt
data11--data12--data13
data21--data22--data23
data31--data32--data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","; OFS=","} {print $1,$2,$3}' data1.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data1.txt
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS="-"; OFS=","} {print $1,$2,$3}' data1.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS="-"; OFS="--"} {print $1,$2,$3}' data1.txt
data11--data12--data13
data21--data22--data23
data31--data32--data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data1.txt
data11-data12-data13-data14-data15
data21-data22-data23-data24-data25
data31-data32-data33-data34-data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

FIELDWIDTHS适用于宽度已经定义好的字段长度,一旦设置了FIELDWIDTHS,gawk就会忽略FS,因为这两中都是用来分割的,两种都使用就会冲突。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data2.txt
100 5.324 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1 $2 $3 $4}' data2.txt
1005.3247596.37
115-2.349194.00
05810.1298100.1
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data2.txt
1005.3247596.37
115-2.349194.00
05810.1298100.1
#print中$1和$2之间的逗号是不允许修改的
1
2
3
4
5
6
7
8
9
10
11
12
13

警告 一定要记住,一旦设定了FIELDWIDTHS变量的值,就不能再改动了。这种方法并不适用于变长的数据字段。

默认情况下,RS和ORS会被设置为换行符。即认为没一行就是一条记录,但是有时候我们会遇到四行算是一条记录的情况,这时候需要将FS(输入字段分隔符)设置为换行符,RS(输入记录分割符)设置为空行,就可以分割字段和记录了。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data3.txt
Ima Test
123 Main Street
Chicago, IL 60601
(312)555-1234

Frank Tester
456 Oak Street
Indianapolis, IN 46201
(317)555-9876

Haley Example
4231 Elm Street
Detroit, MI 48201
(313)555-4938
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS="\n";RS=""} {print $1,$4}' data3.txt
Ima Test (312)555-1234
Frank Tester (317)555-9876
Haley Example (313)555-4938
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS="\n";RS=""} {print $1,$2,$3,$4}' data3.txt
Ima Test 123 Main Street Chicago, IL 60601 (312)555-1234
Frank Tester 456 Oak Street Indianapolis, IN 46201 (317)555-9876
Haley Example 4231 Elm Street Detroit, MI 48201 (313)555-4938
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

2.记录变量

image-20240417220144196

ARGV&ARGC用法

#gawk不会将程序脚本视为命令行参数的一部分,所以下边这个命令中的两个参数分别是gawk 和 data1.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{print ARGC,ARGV[0], ARGV[1] }' data1.txt
2 gawk data1.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5

提示 跟shell变量不同,在脚本中引用gawk变量时,变量名前不用加美元符号。

ENVIRON用法

#说明环境变量都存储在ENVIRON数组中
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{ print ENVIRON["HOME"] ; print ENVIRON["PATH"]}'
/root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6

NF的用法

#相当于sed中的$,表示最后一行 那么NF表示总数也就是最后一个变量
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=":";OFS=":"}{print $1,$NF}' /etc/passwd
root:/bin/bash
bin:/sbin/nologin
daemon:/sbin/nologin
adm:/sbin/nologin
lp:/sbin/nologin
sync:/bin/sync
shutdown:/sbin/shutdown
halt:/sbin/halt
mail:/sbin/nologin
operator:/sbin/nologin
games:/sbin/nologin
ftp:/sbin/nologin
nobody:/sbin/nologin
systemd-network:/sbin/nologin
dbus:/sbin/nologin
polkitd:/sbin/nologin
sshd:/sbin/nologin
postfix:/sbin/nologin
chrony:/sbin/nologin
nscd:/sbin/nologin
tcpdump:/sbin/nologin
rpc:/sbin/nologin
rpcuser:/sbin/nologin
nfsnobody:/sbin/nologin
test:/bin/sh
rich:/bin/bash
Tim:/bin/bash
Barabra:/bin/bash
tss:/sbin/nologin
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

FNR&NR的用法

#每次处理完一个文件,FNR就会被重置   NR不会重置,而是会累计
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' data1.txt data2.txt
data11-data12-data13-data14-data15 FNR=1
data21-data22-data23-data24-data25 FNR=2
data31-data32-data33-data34-data35 FNR=3
1005.3247596.37 FNR=1
115-2.349194.00 FNR=2
05810.1298100.1 FNR=3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","}{print $1,"FNR="FNR "NR="NR} END{print "There were"}' dat[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","}{print $1,"FNR="FNR "NR="NR} END{print "There were",NR,"records processed"}' data1.txt data2.txt
data11-data12-data13-data14-data15 FNR=1NR=1
data21-data22-data23-data24-data25 FNR=2NR=2
data31-data32-data33-data34-data35 FNR=3NR=3
1005.3247596.37 FNR=1NR=4
115-2.349194.00 FNR=2NR=5
05810.1298100.1 FNR=3NR=6
There were 6 records processed
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 22.1.2 自定义变量

gawk允许我们在脚本中定义自己的变量,gawk自定义的变量不能由数字开头。

1、在脚本中给变量赋值

[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# gawk 'BEGIN {
testing="This is a test"
print testing
}'
This is a test
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# 
#另外还可以计算
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# gawk '{
> x=4
> x=x*2+3
> print x
> }'

11

11

11

11
^C
[root@iZuf6fdhtuwmr11b7hkk8pZ ~]# gawk '{x=4; x=x*2+4;print x}'

12

12
^C
#不知道为什么,点击回车之后不会退出命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

2、在命令行中给变量赋值

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data1.txt
data11-data12-data13-data14-data15
data21-data22-data23-data24-data25
data31-data32-data33-data34-data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -f test1.sh n=2 data1.txt
data12
data22
data32
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat test1.sh
BEGIN{FS="-"}
{print $n}
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
#这段代码的意思是test1.sh脚本中编写了命令,用“-”分割然后输出出来data1.txt中的第n个变量,n是在命令行中输入的,如果输入n=3,那么就输出data13这一列了。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data1.txt
data11-data12-data13-data14-data15
data21-data22-data23-data24-data25
data31-data32-data33-data34-data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -f test1.sh n=2 data1.txt
data12
data22
data32
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat test1.sh
BEGIN{FS="-"}
{print $n}
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14

但是,使用命令行参数来定义变量会出现一个问题,就是变量在BEGIN部分不可用,可以在命令行中使用-v参数

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -f test2.sh n=3 data1.txt
The starting value is: 
data13
data23
data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -v n=3 -f test2.sh  data1.txt
The starting value is: 3
data13
data23
data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -f test2.sh -v n=3 data1.txt
The starting value is: 3
data13
data23
data33
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 22.2 处理数组

gawk的数组相当于map

# 22.2.1 定义数组变量

#gawk的数组相当于map,还可以是普通的数组,数组的情况下还可以进行运算
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{capition["xx"]="ss";print capition["xx"]}'
ss
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{capition[1]=1;capition[2]=2;print capition[1]+capition[2]}'
3
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7

# 22.2.2 遍历数组变量

遍历gawk的数组的时候有点像map的循环,拿到keyset()

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{
var["a"]=1
var["b"]=2
var["c"]=3
var["d"]=4
for(test in var)
{
  print "index:",test,"value:",var[test]
}
> }'
index: a value: 1
index: b value: 2
index: c value: 3
index: d value: 4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 22.2.3 删除数组元素

gawk中删除数组元素使用的是delete命令

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{
var["a"]=1
var["b"]=2
var["c"]=3
var["d"]=4
for(test in var)
{
  print "index:",test,"value:",var[test]
}
print "-------"
> delete var["a"]
> for(test in var)
> {
>  print "index:",test,"value:",var[test]
> }
> }'
index: a value: 1
index: b value: 2
index: c value: 3
index: d value: 4
-------
index: b value: 2
index: c value: 3
index: d value: 4
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 22.3 使用模式

主要讲的是正则表达式

# 22.3.1 正则表达式

注意:使用正则表达式的时候,正则表达式必须出现在对应脚本的左花括号之前。

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS="-"} /11/{print $1}' data1.txt
data11
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data1.txt
data11-data12-data13-data14-data15
data21-data22-data23-data24-data25
data31-data32-data33-data34-data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 
1
2
3
4
5
6
7

# 22.3.2 匹配操作符

解释一下这段命令:

1、$0 表示整行数据

2、$2 表示匹配第二个字段

3、/^data2表示以data2开头的字段

所以这段命令的意思是:用每行的第二个字段去匹配data2,如果能够匹配成功,那么打印一整行数据


[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data1.txt
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $0}' data1.txt
data21,data22,data23,data24,data25
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $1}' data1.txt
data21
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 


#
19.1.2中 指定行中划分数据字段的字段分隔符
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -F: '$1 ~/rich/{print $1}' /etc/passwd
rich
#使用!用来排除正则表达式的匹配:
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -F: '$1 !~/rich/{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
.....


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 22.2.3 数学表达式

#还有其他的数学表达式 : x==y / x<=y / x<y / x>=y /x>y
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -F: '$4 == 0 {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
operator:x:11:0:operator:/root:/sbin/nologin
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

#还可以用来匹配字符串
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -F, '$1 == "data" {print $0}' data1.txt
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -F, '$1 == "data11" {print $0}' data1.txt
data11,data12,data13,data14,data15
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 22.4 结构化命令

# 22.4.1 if语句

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{if($1>50) print $1}' data4.txt
78
79
56
67
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat data4.txt
1
2
6
78
79
56
43
25
67
46
37
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{
if($1>60){
 print $1
}
}' data4.txt
78
79
67
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 
#if else
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{
if($1>60){
 print $1
}
else {
> x = $1/2
> print x
> }
> }' data4.txt
0.5
1
3
78
79
28
21.5
12.5
67
23
18.5
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

#写在单行的形式,但是if后边只能有一条语句
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{if($1>60) print $1*2; else print $1/2}' data4.txt
0.5
1
3
156
158
28
21.5
12.5
134
23
18.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 22.4.2 while语句

#类似java语法  还支持break和continue
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{
> total = 0
> i = 0
> while(i<4){
>  total = total + $i
>  i = i+1
> }
> avg = total/3
> print "Average:",avg
> }' data5.txt
Average: 171.667
Average: 191
Average: 225
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 22.4.3 do-while语句

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{
total=0
i=1
do{
 total += $i
 i++
}while(total < 150 )
print total}' data5.txt
250
160
315
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13

# 22.4.4 for语句

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{
> total=0
> for(i=1;i<4;i++){
>  total += $i
> }
> print total
> }' data5.txt
385
413
530
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '{
total=0
for(i=1;i<4;i++){
 total += $i
}
print total/3
}' data5.txt
128.333
137.667
176.667
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 22.5 格式化打印

gawk中可以使用printf来格式化输出。

image-20240419230545825

显示浮点数和字符串:

#格式化浮点数打印
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{
x = 10000
printf "The result is: %5.2f\n",x
}'
The result is: 10000.00

#格式化字符串打印
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{FS="\n";RS=""} {printf "%-16s %s\n", $1,$4}' data3.txt
Ima Test         (312)555-1234
Frank Tester     (317)555-9876
Haley Example    (313)555-4938
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 22.6 内建函数

# 22.6.1 数学函数

image-20240421141504591

#随机数和限定区间
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{x=exp(100); print x}'
26881171418161356094253400435962903554686976
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{x=exp(1000); print x}'#超过限定区间会报错
gawk: cmd. line:1: warning: exp: argument 1000 is out of range
inf

1
2
3
4
5
6
7

除了标准数学函数,gawk还支持一些按位操作数据的函数。

·and(v1, v2):对v1和v2执行按位AND运算。

·compl(val):对val执行补运算。

·lshift(val, count):将val左移count位。

·or(v1, v2):对v1和v2执行按位OR运算。

·rshift(val, count):将val右移count位。

·xor(v1, v2):对v1和v2执行按位XOR运算。

位操作函数在处理数据中的二进制值时特别有用。

# 22.6.2 字符串函数

image-20240421142610484

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{x="testing"; print toupper(x); print length(x) }'
TESTING
7
1
2
3

将字符串分割成数组:

#案例中data1.txt分别用逗号和横杠分割 所以两个命令中的FS变量不一样
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN {FS=","} {
split($0,var)
print var[1], var[5]
}' data1.txt
data11-data12-data13-data14-data15 
data21-data22-data23-data24-data25 
data31-data32-data33-data34-data35 
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN {FS="-"} {
split($0,var)
print var[1], var[5]
}' data1.txt
data11 data15
data21 data25
data31 data35
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 22.6.3 时间函数

image-20240421143712585

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk 'BEGIN{date = systime() ;day = strftime("%A,%b %d,%Y", date);print day}'Sunday,Apr 21,2024
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3

# 22.7 自定义函数

# 22.7.1 定义函数

使用function关键字。

函数名唯一。

可以传入变量

可以return返回值

# 22.7.2 使用自定义函数

定义函数的时候,必须定义在所有代码块(包括BEGIN)之前。

#带有参数和不带参数的函数写法
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '
function myscript(){
  print "Hello World"
}
BEGIN{myscript()}'
Hello World

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk '
function myscript(){
  printf "%-16s - %s\n",$1 ,$4
}
BEGIN{FS="\n"; RS=""}
{
  myscript()
}' data3.txt
Ima Test         - (312)555-1234
Frank Tester     - (317)555-9876
Haley Example    - (313)555-4938
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 22.7.3 创建函数库

使用两个脚本配合的函数

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat test3.sh
BEGIN{ FS="\n"; RS=""}
{
    myprint()
}
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat funclib
function myprint()
{
  printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
  return int(limit * rand())
}
function printthird()
{
  print $3
}
#myscript()函数在funclib文件里边,所以运行test3.sh脚本的时候,再使用-f引入funclib脚本就可以使用了。
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# gawk -f funclib -f test3.sh data3.txt
Ima Test         - (312)555-1234
Frank Tester     - (317)555-9876
Haley Example    - (313)555-4938
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 22.8 实战演练

这是一个用来计算两个队伍平均分的脚本

[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# ./test4.sh
Total for team1 is 635 ,the average is 105.833
Total for team2 is 706 ,the average is 117.667
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat test4.sh
#!/bin/bash

for team in $(gawk -F, '{print $2}' scores.txt | uniq)
do
   gawk -v team=$team 'BEGIN{FS=","; total=0}
   {
      if ($2==team)
      {
          total += $3 + $4 + $5;
      }
   }
   END {
      avg = total / 6;
      print "Total for", team, "is", total, ",the average is",avg
   }' scores.txt
done
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# cat scores.txt
Rich Blum,team1,100,115,95
Barbara Blum,team1,110,115,100
Christine Bresnahan,team2,120,115,118
Tim Bresnahan,team2,125,112,116
[root@iZuf6fdhtuwmr11b7hkk8pZ bash22]# 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 23 使用其他shell

# 23.1 什么是dash shell

# 23.2 dash shell的特性

# 23.2.1 dash命令行选项

# 23.2.2 dash环境变量

# 23.2.3 dash内建命令

# 23.3 dash脚本编程

# 23.3.1 创建dash脚本

# 23.3.2 不能使用的特性

# 23.4 zsh shell

# 23.5 zsh shell的组成

# 23.5.1 shell选项

# 23.5.2 内建命令

# 23.6 zsh脚本编程

# 23.6.1 数学运算

# 23.6.2 结构化命令

# 23.6.3 函数

# 23.7 实战演练

# 24 编写简单的脚本使用工具

# 24.1 备份

# 24.1.1 日常备份

# 24.1.2 创建按小时归档的脚本

# 24.2 删除账户

# 24.2.1 功能需求

# 24.2.2 创建脚本

# 24.2.3 运行脚本

# 24.3 系统监控

# 24.3.1 获得默认的shell审计功能

# 24.3.2 权限审计功能

# 24.3.3 创建脚本

# 24.3.4 运行脚本

# 25 井井有条

# 25.1 理解版本控制

# 25.1.1 工作目录

# 25.1.2 暂存区

# 25.1.3 本地仓库

# 25.1.4 远程仓库

# 25.1.5 分支

# 25.1.6 克隆

# 25.1.7 使用Git作为VCS

# 25.2 设置git环境

# 25.3 使用git提交文件

# -eq 等于

# -ge 大于等于

# -gt 大于

# -le 小于等于

# -lt 小于

# -ne 不等于

# 创建脚本

touch test2.sh test3.sh test4.sh test5.sh test6.sh test7.sh test8.sh test9.sh test10.sh test11.sh test12.sh test13.sh test14.sh test15.sh test16.sh test17.sh test18.sh test19.sh test1.sh

chmod u+x test2.sh test3.sh test4.sh test5.sh test6.sh test7.sh test8.sh test9.sh test10.sh test11.sh test12.sh test13.sh test14.sh test15.sh test16.sh test17.sh test18.sh test19.sh test1.sh
1
2
3

# Git

git reset --hard
git reset --hard:撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交
1
2

# 正则表达式必知必会

# Linux命令从小白到大神

最近更新: 8/27/2025, 8:54:07 AM