shell脚本编程大全


一、命令行

shell提供的文本命令行界面(command line interface CLI)。CLI只能接受文本输入,也只能显示出文本和基本的图形输出。
Linux发行版通常使用Ctrl+Alt组合键配合F1~F7来进入虚拟控制台。

二、shell

1、登录时系统启动的shell依赖于用户账号的配置。/etc/passwd的最后一个字段。
2、默认bash shell提示符是美元符号($),表示shell在等待用户输入。

三、文件系统

1、Linux将文件存储在单个目录结构中,这个目录被称为虚拟目录(virtual directory)。虚拟目录将安装在PC上的所有存储设备的文件路径纳入单个结构中。
2、Linux虚拟目录中比较复杂的部分是它如何协调管理各个存储设备。在Linux PC上安装的第一块硬盘称为根驱动器。根驱动器包含了虚拟目录的核心,其他目录都是从那里开始构建的。
3、Linux会在根驱动器上创建一些特别的目录,称之为挂载点,挂载点是虚拟目录用于分配额外存储设备的目录。虚拟目录会让文件和目录出现在这些挂载点目录中,然而时间上他们却存储在另一驱动器上。
4、touch命令可以用来改变文件的修改时间。这个操作并不需要改变文件的内容。如果只想改变访问时间,可用-a参数。

四、shell命令

1、kill,只能用PID不能用命令名;killall,支持进程名而不是PID结束进程,支持通配符。默认信号为15 TREM,尽可能终止。
2、mount:Linux文件系统将所有的磁盘都并入一个虚拟目录下,在使用新的存储媒体之前,需要把它放到虚拟目录下,这项工作称为挂载(mount)。默认情况下,mount命令会输出当前系统上挂载的设备列表。
3、sort,排序,-t:分隔符;-k:第几列;-n:按字符串数值来排序;-r:反序排序;
4、grep,过滤,如果要指定多个匹配模式,可用-e参数来指定每个模式,彼此是或的关系;

五、shell的父子关系

1、可以通过命令列表的方式实现在一行中依次运行一系列命令。只需要在命令中加入分号(;)即可。
2、进程列表:命令包含在括号中,生成了一个子shell来执行对应的命令(ls; pwd;)。另一种进程列表是将命令放入花括号中,并在命令尾部加上分号,使用花括号进行命令分组并不会像进程列表那样创建出子shell{ command; }。($BASH_SUBSHELL:0-非子shell;其他值,子shell)。

六、内建命令

1、外部命令,有时候也称为文件系统命令,是存在于bash shell外的程序。并不是shell程序的一部分,通常位于/bin、/usr/bin、/sbin、/usr/sbin等。当外部命令执行时,会创建出一个子进程。这种操作被称为衍生(forking),当进程必须执行衍生操作时,他需要花费时间和精力来设置新子进程的环境,多少还是有代价的。
2、内建命令,不需要使用子进程来执行。它们已经和shell编译成了一体,作为shell工具的组成部分。不需要借助外部程序来运行。
3、type,分辨命令是否内建。有些命令有多种实现,type -a显示出每个命令的两种实现。which命令只显示出了外部命令文件。

七、环境变量

1、bash shell用一个叫做环境变量(environment variable)的特性来存储有关shell会话和工作环境的信息。这项特性允许在内存中存储数据,以便程序或shell中运行的脚本能够轻松访问到。也是一种存储持久数据的简便方法。
2、全局变量:env,printenv
3、局部变量:set,全部变量。
4、所有的环境变量名均使用大写字母。局部变量或是shell脚本,请使用小写字母,变量名区分大小写。变量名、等号和值之间没有空格。
5、创建全局变量的方法:创建局部变量,然后再把它导出到全局环境中。export。
6、删除环境变量:unset。
7、使用变量,要加$;操作变量,不加$。

root@master:~# my_variable='study now'
root@master:~# echo $my_variable 
study now
root@master:~# unset my_variable 
root@master:~# echo $my_variable 

root@master:~# 

8、PATH环境变量定义了用于进行命令和程序查找的目录。经常的操作是将某个目录加入到path变量中:PATH=$PATH:/usr/local/cathy/bin。这种修改只能持续到退出或重启系统。
9、环境变量持久化:在登录Linux系统中启动shell时,默认情况下bash会在几个文件中查找命令。这些文件称为启动文件或环境文件。bash检查的启动文件取决于启动bash shell的方式,bash shell有三种方式:登录时作为默认登录shell、作为非登录shell的交互式shell、作为运行脚本的非交互shell。
10、登录shell。当你登录linux系统时,bash shell会作为登录shell启动。登录shell会从五个不同的启动文件中读取命令:/etc/profile、$HOME/.bash_profile、$HOME/.bashrc、$HOME/.bash_login、$HOME/.profile。
11、交互式shell进程:如果你的shell不是登录系统时启动的(比如是在命令行提示符下敲入bash时启动),那么你启动的shell叫做交互式shell。只会访问$HOME/.bashrc文件。
12、非交互式shell:系统在运行shell脚本时使用的shell。由父shell设置但并未导出的变量都是局部变量。子shell无法继承。
13、数组变量。数组是能够存储多个值的变量,把值放在括号中,值与值之间用空格分隔。默认使用echo数组名,只会显示第一个值。要引用单独的数组元素,需要使用数值索引值(下标由0开始)。

root@master:~# mytest=(one two three four five) 
root@master:~# echo $mytest
one
root@master:~# echo ${mytest[2]}
three
root@master:~# echo $mytest[2]    # --- xxx 错误使用
one[2]
root@master:~# echo ${mytest[*]}
one two three four five
root@master:~# unset mytest[2]    # --- 将mytest下标2的值删除
root@master:~# echo ${mytest[*]}
one two four five
root@master:~# echo ${mytest[2]}

root@master:~# echo ${mytest[3]}
four
root@master:~# unset mytest       # --- 删除整个数组
root@master:~# echo ${mytest[*]}

root@master:~# 

八、文件系统权限

1、Linux权限系统的核心是用户。每个能进入linux系统的用户都会被分配唯一的用户账号,用户对系统中各种对象的访问权限取决于他们登录系统时用的账号。
2、/etc/passwd,用户相关的信息。UID,0-500,系统账号的id范围,所有运行在后台的服务都需要用一个系统用户账号登录到Linux系统中。
3、useradd,添加用户。-G,加入附属组。
4、userdel,删除用户,默认情况下仅会清理/etc/passwd中用户信息,不会删除系统中属于该用户的任何文件。-r,删除HOME目录及邮件目录。
5、usermod,修改用户。-l,修改登录名。-L,锁定账号,使用户无法登录。-p,修改账号密码。-U,解除账号锁定。-G,添加附属组。-g,替换默认组。
6、passwd,修改密码。chpasswd,从标准输入自动读取登录名和密码对列表,给密码加密,然后为用户账号设置。
7、chsh,修改用户默认登录shell。
8、chage,管理用户账号的有效期。-m,设置修改密码之间最少要多少天。
9、默认文件权限,umask值只是个掩码,它会屏蔽掉不想授权该安全级别的权限。要把umask值从对象的全权限值中减掉。对文件来说,全权限值是666;对目录来说是777.
10、共享文件。SUID:当文件被用户使用时,程序会以文件属主的权限运行(/usr/bin/passwd -rwsr-xr-x)。SGID,对文件来说,程序会以文件属组的权限运行,对目录来说,目录中创建的新文件会以目录的默认属组作为默认属组(drwxr-sr-x)。STID,粘着位,进程结束后文件还驻留(黏着)在内存中。

九、管理文件系统

1、Linux文件系统为我们在硬盘中存储的0和1和应用中使用的文件和目录之间搭建起了一座桥梁。
2、ext(extended filesystem)文件系统:使用虚拟目录来操作硬件设备,在物理设备上按定长的块来存储数据。ext文件系统采用名为索引节点的系统来存放虚拟目录中所存储的文件的信息。
3、ext2文件系统,为解决ext的2G容量,ext2支持2T。为减轻ext的碎片化存储,ext2采用分块存储。限制:索引节点表虽然支持文件系统保存文件的更多信息,但会对系统造成致命的问题。文件系统每次存储或更新文件时,它都要用新信息来更新索引节点表。问题在于这种操作并非总是一气呵成。如果计算机系统在存储文件和更新索引节点表之间发生了什么,比如说断电或系统崩溃时,二者的内容就不同步了。
4、日志文件系统:先将文件的更改写入到临时文件(称作日志,journal)中。在数据成功写到存储设备和索引节点表后,再删除对应的日志条目。如果系统在数据被写入存储设备之前崩溃或断电了,日志文件系统下次会读取日志文件并处理上次留下的未写入的数据。
5、三种日志方式:数据模式:索引节点和文件都会被写入日志;有序模式:只有索引节点数据会被写入日志,但只有数据成功写入后才删除日志;回写模式,只有索引节点数据会被写入日志,但不控制文件数据何时写入。
6、ext3文件系统,增加了一个日志文件,以将准备写入存储设备的数据先记入日志。默认情况下,ext3采用有序模式的日志功能。
7、ext4文件系统,支持数据加密和压缩。
8、xfs文件系统,采用回写模式的日志。
9、写时复制(copy-on-write,COW)文件系统:如果要修改数据,会使用克隆或可写快照,修改过的数据并不会直接覆盖当前数据,而是被放入文件系统中另一个位置。即便是数据修改已经完成,之前的旧数据也不会被重写。ZFS\Btrf文件系统。
10、磁盘的分区可以按主分区或者扩展分区创建。主分区可以被文件系统直接格式化,而扩展分区只能用来创建逻辑分区,进而将逻辑分区进行格式化。
11、再将数据存储到分区之前,必须使用某种文件系统对其进行格式化。下一步是将其挂载到虚拟目录下的某个挂载点,这样就可以将数据存储在新文件系统中了。
12、逻辑卷管理。如果用标准分区在硬盘上创建了文件系统,为已有文件系统添加额外的空间是一种痛苦的体验。只能在用一块物理硬盘的可用空间范围内调整分区大小。优秀的做法是,通过将另一块硬盘的分区加入到已有文件系统,动态的添加存储空间。linux逻辑卷管理器(logical volume manager,LVM)软件包正好可以用来做这个。可以再无需重建整个文件系统的情况下,轻松的管理磁盘空间。
13、逻辑卷管理的核心在于如何处理安装在系统上的硬盘分区。在逻辑卷管理的世界中,硬盘分区称为物理卷(physical volume,pv)。每个物理卷都会映射到硬盘上特定的物理分区。多个物理卷集中在一起形成一个卷组(volume group,vg)。最后是逻辑卷(logical volume,lv)。逻辑卷为linux提供了创建文件系统的分区环境,可以使用任何一种文件系统来格式化逻辑卷,然后将它加入linux虚拟目录中的某个挂载点。
14、lvcreate -l 100%FREE vg0 -n lv0; lvextend -l 200%FREE lv0

root@master:~# lvcreate -l 50%FREE vg0 -n lv0
  Logical volume "lv0" created.
root@master:~# lvs
  LV   VG  Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  vg0 -wi-a----- <5.00g
root@master:~# lvextend -l 200%FREE /dev/vg0/lv0  
  Size of logical volume vg0/lv0 changed from <5.00 GiB (1279 extents) to 9.99 GiB (2558 extents).
  Logical volume vg0/lv0 successfully resized.
root@master:~# vgs
  VG  #PV #LV #SN Attr   VSize VFree
  vg0   2   1   0 wz--n- 9.99g    0 
root@master:~# lvs
  LV   VG  Attr       LSize Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  lv0  vg0 -wi-a----- 9.99g

十、构建基本shell脚本

1、vim在内存缓冲区中处理数据。
2、最大命令行字符数255.
3、shell脚本的第一行必须是指定要使用的shell,例如:#!/bin/bash
4、echo命令可用单引号或双引号来划定文本字符串。
5、echo -n:表示当前行输出后不换行。这样下行数据就和本行数据合并在一行显示了。
6、要显示美元符($),可以使用反斜线进行转义。
7、可以通过${variable},花括号形式来引用变量。
8、shell脚本会自动决定变量值的数据类型。
9、将命令输出赋给变量:反引号、$()格式。
10、执行算术运算:expr 1 + 5;必须有空格。expr 2 \* 5;必须有转义字符。
11、执行数学表达式:$[ operation ];bash shell 数学运算符只支持证书运算;
12、内建的bash计算器:bc。浮点运算(加减乘不算,只有除法才会受到scale影响)是由内建变量scale控制的,默认值是0,不包含小数位,必须将这个值设置为你希望在计算结果中保留的小数位数。
13、在脚本中使用bc,用命令替换运行bc命令,并将输出赋值给一个变量。

root@master:~/tmp/shell# bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
1.2*3
3.6
5/2
2
scale=3
5/2
2.500
1.2*2
2.4
quit
root@master:~/tmp/shell# var1=$(echo "scale=3; 5/3" | bc)
root@master:~/tmp/shell# echo $var1
1.666

14、shell中运行的每个命令都使用退出状态码告诉shell它已经运行完毕。退出状态码是一个0-255的整数,在命令结束运行时由命令传给shell。
15、linux提供了一个专门的变量$?来保存上一个已经执行的命令的退出状态码。成功为0,否则正数。
16、默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。

十一、使用结构化命令

1、bash shell的if语句会运行if后面的命令。如果命令的退出状态码是0,then的语句就会被执行。否则,else部分被执行。
2、test命令,如果test命令中列出的条件成立,test命令就会退出并返回状态码0。如果test后不加命令,则会以非零的状态码退出,并执行else语句块。
3、test命令可以判断三类条件:数值比较、字符串比较()、文件比较。
4、数值比较:-eq、-ge、-gt、le、-lt、-ne。但是涉及浮点值时,数值条件测试会有一个限制,不能在test命令中使用浮点值。

root@master:~/tmp/shell# test 1.2 -eq 2
-bash: test: 1.2: integer expression expected

5、字符串比较:=、!=、>、<、-n(0则真)、-z(不0则真)。大小于号必须经过转义,否则被shell当做重定向符号。未被定义的变量,长度为0.
6、文件比较:-d、-e、-f、-r、-w、-s、-x、-O、-G、-nt、-ot
7、复合条件测试: && ||
8、双括号允许在比较过程中使用高级数学表达式。(( expression )),不需要将双括号中表达式里的大小于号转义。

root@master:~/tmp/shell# cat test23.sh   
#!/bin/bash
# using doble parenthesis

var1=10

if (( $var1 ** 2 > 90 )); then
        (( var2=$var1 ** 2 ))
        echo "the square of $var1 is $var2"
fi
root@master:~/tmp/shell# bash test23.sh   
the square of 10 is 100

9、使用双方括号命令提供了针对字符串比较的高级特性。双方括号里的expression使用了test命令中采用的标准字符串比较。但它提供了test命令未提供的另一个特性—模式匹配。
双等号(==),将右边的字符串(r*)视为一个模式,并应用模式匹配规则。

root@master:~/tmp/shell# cat test24.sh 
#!/bin/bash
# using pattern matching

if [[ $USER == r* ]]; then
        echo "hello $USER"
else
        echo "sorry,i dont know you"
fi
root@master:~/tmp/shell# bash test24.sh 
hello root

10、for循环假定每个值都是用空格分割的。在某个值两边使用双引号时,shell并不会将双引号当成值的一部分。

root@master:~/tmp/shell# cat test3 
#!/bin/bash
# an example of how to properly define values

for test in Nevada "New Hampshire" "New York"; do
    echo "Now going to $test"
done
root@master:~/tmp/shell# bash test3 
Now going to Nevada
Now going to New Hampshire
Now going to New York
root@master:~/tmp/shell#

11、字段分隔符(internal field separator)IFS,默认情况下为空格、换行符、TAB键。
12、IFS=‘\n’:将\和n作为分隔符。IFS=‘XY’:将X和Y作为分隔符。IFS=$‘\n’:真正将换行符作为分隔符。IFS=$‘\n’:;" :将换行符、冒号、分号和双引号作为字段分隔符。
13、for命令可以自动遍历目录中的文件,进行此操作时,必须在文件名或路径名中使用通配符。它会强制shell使用文件扩展匹配。在Linux中,目录名和文件名中包含空格是合法的。要适应这种情况,应该将$file变量用双引号圈起来。“$file”
14、C语言风格的for循环: (( variable assignment; condition; iteration process )) (( i=1; i<=10; i++ ))
15、while命令允许定义一个要测试的命令,然后循环一组命令,只要定义的测试命令返回的退出状态码是0,继续。否则,停止。
16、while命令可以在while语句行定义多个测试语句。只有最后一个测试命令的退出码会被用来决定什么时候结束循环。
17、until命令和while刚好相反,只有测试命令的状态码不为0,bash shell才会执行循环中列出的命令。
18、跳出循环:break,跳出单层循环,可接受单个命令行参数值:break n,其中n指定了要跳出的循环层级。默认为1。continue,跳出单次循环。

root@master:~/tmp/shell# cat test21.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@master:~/tmp/shell# bash test21.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

十二、处理用户输入

1、bash的位置参数,$0:脚本名,$1:脚本名后的第一个参数,以此类推,知道第九个参数$9,后面的参数为${10}。
2、每个参数都是用空格分开的。要在参数值中包含空格,必须要用引号(单引号或双引号均可)。将文本字符串作为参数传递时,引号并非数据的一部分。
3、$0表示脚本名,但是当传给$0变量的实际字符串并不仅仅是脚本名,而是完整的脚本路径时,变量$0就会使用整个路径。幸好,basename命令会返回不包含路径的脚本名。

root@master:~/tmp/shell# cat test5b.sh 
#!/bin/bash
# Using basename with the $0 parameter

name=$(basename $0)
echo
echo The script name is: $name
root@master:~/tmp/shell# bash /root/tmp/shell/test5b.sh 

The script name is: test5b.sh

4、-n测试来检查命令行参数$1是否有数据。有-then,无-else/elif。
5、$#表示脚本运行时携带的命令行参数的个数。
6、如何表示最后一个命令行参数。${$#} XXX是错的。 ${!#} okk。
需要注意的是,当没有命令行参数时$#的值时0,params变量的值也一样,但 ${!#}的变量会返回命令行用到的脚本名。

root@master:~/tmp/shell# cat test10b.sh 
#!/bin/bash
# Grabbing the last paremeter
params=$#
echo
echo ---The last parameter was ${$#}

echo ---The last parameter was ${!#} 
root@master:~/tmp/shell# bash test10b.sh 8 8 9 0 9 7

---The last parameter was 26525
---The last parameter was 7
root@master:~/tmp/shell# bash test10b.sh 8 8 9 0 9 7

---The last parameter was 26526
---The last parameter was 7
root@master:~/tmp/shell# bash test10b.sh 8 8 9 0 9 7

---The last parameter was 26665
---The last parameter was 7
root@master:~/tmp/shell# bash test10b.sh

---The last parameter was 36532
---The last parameter was test10b.sh
root@master:~/tmp/shell# bash /root/tmp/shell/test10b.sh

---The last parameter was 37214
---The last parameter was /root/tmp/shell/test10b.sh

7、$*和$@都可以存储所有的命令行参数,$*会将所有参数当成一个单词保存。$@会将命令行上所提供的的所有参数当做多个独立的单词。----- “$*”–(要加引号才可以才for时当一个单词)

root@master:~/tmp/shell# cat test12.sh 
#!/bin/bash
# testing $* and $@
echo
count=1
for param in "$*"; do
        echo "\$* Parameter #$count = $param"
        count=$[ $count + 1 ]
done

echo
count=1
for param in "$@"; do
        echo "\$@ Paramter #$count = $param"
        count=$[ $count + 1]
done

root@master:~/tmp/shell# bash test12.sh a b c

$* Parameter #1 = a b c

$@ Paramter #1 = a
$@ Paramter #2 = b
$@ Paramter #3 = c

8、分离参数和选项。Linux使用特殊字符,双破折线(–),来表明选项列表结束。在双破折线后,脚本就可以将剩下的命令行参数当做参数,而不是选项来处理了。
9、getopt命令,能够识别命令行参数。在每个需要参数值的选项字母后加一个冒号。如果指定了一个不在选项中的字母,默认情况下会报错。在getopt命令后加-q选项,可以忽略错误消息。

root@master:~/tmp/shell# getopt ab:cd -a -b test1 -cd test2 test3
 -a -b test1 -c -d -- test2 test3
root@master:~/tmp/shell# getopt ab:cd -a -b test1 -cd test2 test3 -e
getopt: invalid option -- 'e'
 -a -b test1 -c -d -- test2 test3
root@master:~/tmp/shell# getopt -q ab:cd -a -b test1 -cd test2 test3 -e
 -a -b 'test1' -c -d -- 't

10、getopts是shell内建命令。与近亲getopt很像,但多了一些扩展功能。getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。会将所有未定义的选项统一输出成问号。

root@master:~/tmp/shell# cat test19.sh 
#!/bin/bash
# simple demonstration of the getopts command

echo
while getopts :ab:c opt; do
        case "$opt" in
                a) echo "Found the -a option" ;;
                b) echo "Found the -b option, with value $OPTARG" ;;
                c) echo "Found the -c option" ;;
                *) echo "Unkonwn option: $opt" ;;
        esac
done
root@master:~/tmp/shell# bash test19.sh -a -b "good good study" -c -d -e     

Found the -a option
Found the -b option, with value good good study
Found the -c option
Unkonwn option: ?
Unkonwn option: ?

11、getopts知道何时停止处理选项,并将参数留给你处理。在getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,就可以使用shift命令和OPTIND值来移动参数。(为什么加1呢,因为有个–特殊字符)。

root@master:~/tmp/shell# cat test20.sh 
#!/bin/bash
# Processing options & paramters with getopts

echo
while getopts :ab:cd opt; do
        case "$opt" in
                a) echo "Found the -a option" ;;
                b) echo "Found the -b option, with 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@master:~/tmp/shell# bash test20.sh -a -b "my love" -c -d test1 test7            

Found the -a option
Found the -b option, with value my love
Found the -c option
Found the -d option

Parameter 1: test1
Parameter 2: test7

12、获得用户的输入。read命令从标准输入(键盘)或另一个文件描述符中接受输入。read命令会将提示符后输入的所有数据分配给单个变量,要么就指定多个变量。输入的每个数据值都会分配给变量列表中的下一个变量。如果变量数量不够,剩下的数据就全部分配给最后一个变量。read -p选项,允许在read命令行指定提示符。

root@master:~/tmp/shell# cat test22.sh 
#!/bin/bash
# testing the read command
echo
read -p "Enter your name: " first last
echo "check your name is $last, $first"
root@master:~/tmp/shell# bash test22.sh 

Enter your name: cui qiannan
check your name is qiannan, cui
root@master:~/tmp/shell# bash test22.sh 

Enter your name: cui qian nan
check your name is qian nan, cui
root@master:~/tmp/shell# bash test22.sh 

Enter your name: cui
check your name is , cui

13、也可以在read命令行中不指定变量,默认会将所有的数据都放进特殊的环境变量REPLY中。read -t选项可以指定一个计时器(s),计时结束后未输入,read命令会返回一个非零退出状态码。-nX,可以让read命令来统计输入的字符数,达到预设字符数时,就自动退出,无需按回车键,将输入的数据复制给变量。-s选项可以避免在read命令中输入的数据出现在显示器上。

root@master:~/tmp/shell# cat test26.sh 
#!/bin/bash
# getting just one character of input

read -n1 -s -t 3 -p "Do you want to continue [Y/N]? " answer
case $answer in
        Y | y ) echo
                echo "fine, continue on..." ;;
        N | n ) echo
                echo "OK, goodbye"
                exit ;;
esac
echo "this is the end of the script."
root@master:~/tmp/shell# bash test26.sh 
Do you want to continue [Y/N]? this is the end of the script.
root@master:~/tmp/shell# bash test26.sh 
Do you want to continue [Y/N]? 
fine, continue on...
this is the end of the script.
root@master:~/tmp/shell# bash test26.sh 
Do you want to continue [Y/N]? 
OK, goodbye

14、从文件中读取数据。每次调用read命令,它都会从文本文件中读取一行文本,当文本中再没有内容时,read命令会退出,并返回非零退出状态码。和while一起用正好,非零退出码刚好退出while循环。


root@master:~/tmp/shell# cat test28.sh 
#!/bin/bash
# reading data from file
count=1
cat test | while read line; do
    echo "Line $count: $line"
    count=$[ $count + 1 ]
done
echo "Finished processing the file"
root@master:~/tmp/shell# cat test
The quick brown dog jumps over the lazy fox.
This is a test, this is only a test.
O Romeo, Romeo! Wherefore art thou Romeo?
root@master:~/tmp/shell# bash test28.sh 
Line 1: The quick brown dog jumps over the lazy fox.
Line 2: This is a test, this is only a test.
Line 3: O Romeo, Romeo! Wherefore art thou Romeo?
Finished processing the file
root@master:~/tmp/shell# 

十三、呈现数据

1、每个进程一次最多有九个文件描述符。其中0表示STDIN标准输入,1表示STDOUT标准输出,2表示STDERR标准错误输出。
2、STDIN文件描述符代表shell的标准输入。对终端界面来说,标准输入是键盘。在使用输入重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是从键盘上键入的。
3、STDOUT文件描述符代表shell的标准输出。在终端界面来说,标准输出就是显示器。
4、STDERR文件描述符代表shell的错误消息输出。shell或shell中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。默认情况下,输出位置是终端显示器。但STDERR并不会随STDOUT的重定向而发生改变。
5、2>,只重定向错误。1>或>重定向标准输出,不包括错误。&>重定向标准和错误输出。
6、临时重定向。如果有意在脚本中生成错误消息,可以将单独的一行重定向到STDERR。需要做的是使用输出重定向符来将输出信息重定向至STDERR文件描述符。在重定向到文件描述符时,必须在文件描述符数字之前加一个&。echo “test” >&2
7、永久重定向。在脚本中永久将某个特定文件描述符重定向至某一位置。exec。

root@master:~/tmp/shell# cat test10.sh 
#!/bin/bash
# redirecting all output to a file
exec > testout

echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line."
echo "without having to redirect every individual line." >&2
root@master:~/tmp/shell# bash test10.sh   
without having to redirect every individual line.
root@master:~/tmp/shell# cat testout    
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line.

8、创建输出文件描述符。可以用exec命令来给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到重新分配。

root@master:~/tmp/shell# cat test13.sh 
#!/bin/bash
# using an alternative file descriptor

exec 3> test3out

echo "This should display on the monitor."
echo "and this should be stored in the file." >&3
echo "Then this should be back on the monitor."
root@master:~/tmp/shell# bash test13.sh 
This should display on the monitor.
Then this should be back on the monitor.
root@master:~/tmp/shell# cat test3out 
and this should be stored in the file.

9、如何恢复已重定向的文件描述符?可以分配另外一个文件描述符给标准文件描述符,反之亦然。这意味着可以将STDOUT的原来位置重定向到另一个文件描述符,然后再利用该文件描述符重定向回STDOUT。
首先,脚本将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT。这意味着任何发送到文件描述符3的输出都将出现在显示器上。
第二个exec命令将STDOUT重定向至文件,shell现在会将发送到STDOUT的输出直接重定向到输出文件中。但是,文件描述符3仍然指向STOOUT原来的位置,也就是显示器。如果此时将输出数据发送到文件描述符3,它仍然会出现在显示器上,尽管STDOUT已经被重定向了。
在向STDOUT(现在指向一个文件)发送一些输出之后,脚本将STDOUT重定向到文件描述符3的当前位置(显示器)。这意味着将STDOUT又指向了原来的位置,显示器。

root@master:~/tmp/shell# cat test14.sh   
#!/bin/bash
# storing STDOUT, then coming back to it

exec 3>&1
exec 1>test14out

echo "This should store in the output file"
echo "along with this line."
echo "test error output" >&2
echo "test 3 output" >&3
exec 1>&3

echo "Now things should be back to normal"

root@master:~/tmp/shell# bash test14.sh  
test error output
test 3 output
Now things should be back to normal
root@master:~/tmp/shell# cat test14out  
This should store in the output file
along with this line.

10、关闭文件描述符。exec 3>& - 。一旦关闭了文件描述符,再往进写任何数据,shell都会产生错误信息。在关闭文件描述符时还要注意另一件事。如果随后在脚本中打开了同一个输出文件,shell会用一个新文件来替换已有文件。这意味着如果输出数据,就会覆盖已有文件。

root@master:~/tmp/shell# cat test17.sh 
#!/bin/bash
# testing closing file descriptors

exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-

echo "This won't work" >&3
cat test17file

exec 3> test17file
echo "This'll be bad" >&3
root@master:~/tmp/shell# bash test17.sh 
test17.sh: line 8: 3: Bad file descriptor
This is a test line of data
root@master:~/tmp/shell# cat test17file 
This'll be bad

11、记录消息。在需要将输出同时发送到显示器和文件中时,可以使用tee命令。相当于管道的一个T型接头。将从STDIN过来的数据同时发往STDOUT和tee命令行所指定的文件。默认是覆盖文件中的内容,可以使用-a选项追加至文件末尾。
12、注意一:EOF结束时必须顶格写,否则报错。注意二:使用’$lnam’???单引号竟然还赋值成功了。??单引号难道不是强制引用吗?

root@master:~/tmp/shell# cat test23.sh                 
#!/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
root@master:~/tmp/shell# bash test23.sh members.csv    
root@master:~/tmp/shell# 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
root@master:~/tmp/shell# cat members.sql 
        INSERT INTO members (lname, fname, address, city, state, zip) VALUES
        ('Blum', 'Richard', '123 Main St.', 'Chicago', 'IL', '60601');
        INSERT INTO members (lname, fname, address, city, state, zip) VALUES
        ('Blum', 'Barbara', '123 Main St.', 'Chicago', 'IL', '60601');
        INSERT INTO members (lname, fname, address, city, state, zip) VALUES
        ('Bresnahan', 'Christine', '456 Oak Ave.', 'Columbus', 'OH', '43201');
        INSERT INTO members (lname, fname, address, city, state, zip) VALUES
        ('Blum', 'Richard', '123 Main St.', 'Chicago', 'IL', '60601');

十四、控制脚本

1、Linux利用信号与运行在系统中的进程进行通信。1-SIGHUP-挂起进程、2-SIGINT-终止进程、3-SIGQUIT-停止进程、9-SIGKILL-无条件终止进程、15-SIGTERM-尽可能终止进程、17-SIGSTOP-无条件停止进程,但不是终止进程、18-SIGTSTP-停止或暂停进程,但不终止进程、19-SIGCONT-继续运行停止的进程
2、ctrl+c:2-SIGINT-终止进程。
3、ctrl+z:18-SIGTSTP-暂停或停止进程。可以使用ps查看已停止的作业,其状态显示为T。
4、捕获信号。trap commands signals。
可以在trap命令后使用单破折号或两个破折号来恢复信号的默认行为。trap - SIGINT。

root@master:~/tmp/shell# cat test1.sh 
#!/bin/bash
# Testing signal trapping

trap "echo 'Sorry! I have trapped Ctrl-C' " SIGINT

echo This is a test script

count=1
while [ $count -le 5 ]; do
        echo "Loop #$count"
        sleep 1
        count=$[ $count+1 ]
done

echo "This is the end of the test script"
root@master:~/tmp/shell# bash test1.sh 
This is a test script
Loop #1
Loop #2
^CSorry! I have trapped Ctrl-C
Loop #3
Loop #4
^CSorry! I have trapped Ctrl-C
Loop #5
This is the end of the test script
root@master:~/tmp/shell# cat test2.sh 
#!/bin/bash
# trapping the script exit

trap "echo Goodbay..." EXIT

count=1
while [ $count -le 5 ]; do
        echo "Loop #$count"
        sleep 1
        count=$[ $count + 1 ]
done
root@master:~/tmp/shell# bash test2.sh 
Loop #1
Loop #2
^CGoodbay...

root@master:~/tmp/shell# bash test2.sh 
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbay...
root@master:~/tmp/shell# 

5、后台运行脚本,在命令后加上&即可。当后台进程运行时,仍然会使用终端显示器来显示STDOUT和STDERR消息。在退出终端时,如果存在被停止的进程,会出现警告信息。
6、nohup command &;命令不会在终端退出时退出。输出会追加至nohup.sh。
7、jobs -l;列出进程PID及作业号;jobs命令输出的加号和减号,带加号的作业会被当做默认作业,在使用作业控制命令时,如果未在命令行行指定任何作业号,改作业会被当做作业控制命令的操作对象。当前的默认作业完成作业处理后,带减号的作业成为下一个默认作业。默认作业是最后启动的进程。
8、重启停止的作业。bg,后台模式重启。fg,前台模式重启。后可加具体作业号。不加为默认作业号。
9、在多任务操作系统中,内核负责将CPU时间分配给系统上运行的每个进程。调度优先级是内核分配给进程的CPU时间(相对于其他进程)。在Linux系统中,由shell启动的所有进程的调度优先级默认都是相同的。为0。调度优先级是个整数值,从-20(最高)到+10(最低)。“好人难做”,值越大,分到的时间越少。
10、nice -n X command;nice -X command; 运行作业时指定优先级。仅限root使用。
11、renice -n X -p PID;调整正在运行的命令的优先级。
12、at,执行计划任务。at [-f filename] time;任何输出都会以邮件形式发送给该用户。可以使用-M选项来屏蔽作业产生的输出信息。
13、cron,分时日月周命令。

十五、创建函数

1、函数是一个脚本代码块,可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用函数名即可(调用函数)。
2、创建函数。shell脚本从上到下执行,如果函数未被定义就使用,会报错。

function name {
	command
}

name() {
	command
}

root@master:~/tmp/shell# cat test2
#!/bin/bash
# using a function located in the middle of a script

count=1
echo "This line comes before the function definition"

function func1 {
    echo "This is an example of a function"
}

while [ $count -le 3 ]; do
    func1
    count=$[ $count + 1 ]
done
func2
echo "Now this is the end of the script"

func2() {
    echo "this is an example of a function"
}
root@master:~/tmp/shell# bash test2
This line comes before the function definition
This is an example of a function
This is an example of a function
This is an example of a function
test2: line 15: func2: command not found
Now this is the end of the script

3、返回值:默认情况下,函数最后一条命令返回的退出状态码为函数的退出状态码;可以使用return来指定一个整数值来定义函数的退出状态码(0-255之间,任何大于256的值都会产生一个错误值);将函数的输出保存在变量中,来传递。

root@master:~/tmp/shell# cat test4.sh 
#!/bin/bash
# testing the exit status of a function
func1() {
    echo "try to display a non-existent file"
    ls -l badfile
}
echo "testing the func1: "
func1
echo "The exit status of func1 is: $?"

# testing the exit status of a function2
func2() {
    echo "try to display a non-existent file"
    ls -l badfile
    echo "This was a test of a bad command"
}
echo "testing the func2: "
func2
echo "The exit status of func2 is: $?"

# using the return command in a function
db1() {
    read -p "Enter a value: " value
    echo "doubling the value"
    return $[ $value * 2 ]
}
db1
echo "The new value is $?"

# using the echo to return a value
db2() {
    read -p "Enter a value2: " value2
    echo $[ $value2 * 2 ]
}
result=$(db2)
echo "The new value2 is $result"

root@master:~/tmp/shell# bash test4.sh 
testing the func1: 
try to display a non-existent file
ls: cannot access 'badfile': No such file or directory
The exit status of func1 is: 2
testing the func2: 
try to display a non-existent file
ls: cannot access 'badfile': No such file or directory
This was a test of a bad command
The exit status of func2 is: 0
Enter a value: 200
doubling the value
The new value is 144
Enter a value2: 200
The new value2 is 400

发现一个神奇的shell,执行顺序很诡异。请大家自行琢磨,有想法的留言讨论。
result值在db2函数的第一行read执行完成后被赋予,竟然没有继续执行db2函数中的第二行,而是转而执行了外面的第二行,后再次转向执行db2函数的第三行,直接省略了db2函数的第二行echo,后再执行了db2函数的第四行,完成db2函数的执行,再执行外层第三行。诡异,实在是诡异。
想通了:result变量保存的是函数的全部返回,在显示或引用result变量时,函数的全部返回都会输出。

root@master:~/tmp/shell# cat test4.sh     
# using the echo to return a value
db2() {
    read -p "Enter a value2: " value2
    echo 20
    echo 200
    ls test
}
result=$(db2)
echo "The new value2 is $result"
echo "The new value2 is $result"
ls test2
root@master:~/tmp/shell# bash test4.sh 
Enter a value2: 1
The new value2 is 20
200
test
The new value2 is 20
200
test
test2

4、在函数中使用变量。函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。$0:函数名。$1:第一个参数。$#:参数个数。

root@master:~/tmp/shell# cat test7 
#!/bin/bash
# trying to access script paramters inside a function

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

if [ $# -eq 2 ]; then
    value=$(func7 $1 $2)
    echo "The result is $value"
else
    echo "Usage: badtest1 a b"
fi
root@master:~/tmp/shell# bash test7
Usage: badtest1 a b
root@master:~/tmp/shell# bash test7 1 2
The result is 2

5、变量的作用域。作用域是变量可见的区域。分为全局变量和局部变量。全局变量是在shell脚本中任何地方都有效的变量。函数内部使用的任何变量都可以被声明为局部变量。要实现这一点,只要在变量声明的前面加上local关键字就可以了。local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,那么shell将会保持这两个变量的值时分离的。
6、数组变量。将数组变量作为函数参数,函数只会取数组变量的第一个值。要解决这个问题,必须将数组变量的值分解为单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量。
将命令行参数的值全部读入数组变量中。array=($(echo “$@”))
$*:将命令行参数当成一个整体存入。
$@:将参数单独存入,每个是单独的。

root@master:~/tmp/shell# cat test11.sh  
#!/bin/bash
# ADDING VALUES IN AN ARRAY

function addarray {
    local sum=0
    local newarray
    newarray=($(echo $@))
    for value in ${newarray[*]}; do
            sum=$[$sum + $value]
    done
    echo $sum
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=$(addarray $arg1)
echo "The resule is $result"
root@master:~/tmp/shell# bash test11.sh 
The original array is: 1 2 3 4 5
The resule is 15

root@master:~/tmp/shell# cat test12.sh 
#!/bin/bash
# returning an array value

function arraydblr {
    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 "The original array is: ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=($(arraydblr $arg1))
echo "The new array is: ${result[*]}"
root@master:~/tmp/shell# bash test12.sh 
The original array is: 1 2 3 4 5
The new array is: 2 4 6 8 10

7、递归函数:局部函数变量的一个特点是自成体系。除了从脚本命令行处获得的变量,自成体系的函数不需要使用任何外部资源。这个特性是的函数可以递归的调用。也就是说,函数可以调用自己来得到结果。通常递归函数都有一个最终可以迭代到的基准值。许多高级数学算法用递归对复杂的方程进行逐级规约,直到基准值定义的那级。递归算法的经典例子就是阶乘。一个数的阶乘是该数之前的所有数乘以该数的值。

root@master:~/tmp/shell# cat test13.sh 
#!/bin/bash
# using recursion

function factorial {
    if [ $1 -eq 1 ]; then
            echo 1
    else
            local temp=$[ $1 - 1 ]
            local result=$(factorial $temp)
            echo $[ $result * $1 ]
    fi
}

read -p 'Enter value:' value
result=$(factorial $value)
echo "The factorial of $value is: $result"
root@master:~/tmp/shell# bash  test13.sh 
Enter value:3
The factorial of 3 is: 6

8、创建库。bash shell 允许创建函数库文件,然后再很多个脚本中引用该库文件。第一步是创建库函数文件,第二步是在使用这些函数的脚本中包含库文件。shell函数仅会在定义它的shell会话内有效。bash x.sh或 ./x.sh会新起shell来运行脚本,那么在当前shell(父shell)中还是无法使用库文件中的函数。source x.sh或 . x.sh,会在当前shell上下文中执行命令,这样就可以在当前shell及其子shell中使用该库文件中的函数了。
最佳的库文件载入地点是.bashrc文件。bash每次在启动时都会在主目录下找到这个文件。使用读取函数文件的方式,source或.将库文件中的函数添加到.bashrc脚本中。

十六、sed和gawk

1、sed编辑器(stream editor)又被称为流编辑器。流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在一个命令文本文件中。
2、默认情况下,sed会将指定的命令应用到stdin输入流上。这样你就可以直接将数据流通过管道输入sed编辑器处理。

root@master:~/tmp/shell# echo 'This is a test' | sed 's/test/big test/'
This is a big test

3、要在sed命令行上执行过个命令时,只要用-e选项就可以了。命令必须用分号隔开,并且在命令末尾和分号之间不能有空格。

root@master:~/tmp/shell# cat data1.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@master:~/tmp/shell# sed -e 's/brown/green/; s/dog/cat/' data1.txt 
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.

4、如果有大量要处理的命令,那么将它们放进一个单独的文件中,可以使用-f选项来指定文件。在文件中,不用在每条命令后面放一个分号。sed编辑器知道每行都是一条单独的命令。

root@master:~/tmp/shell# cat data1.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@master:~/tmp/shell# cat script1.sed 
s/brown/green/
s/fox/elephant/
s/dog/cat/
root@master:~/tmp/shell# sed -f script1.sed data1.txt   
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.

5、gwak程序是Unix中的原始awk程序的GNU版本。gwak程序让流编辑器迈上了一个新台阶,它提供了一种编程语言而不只是编辑器命令。在gawk编程语言中,可以做如下事:定义变量来保存数据;使用算术和字符串操作符来处理数据;使用结构化编程概念来为数据处理增加处理逻辑;通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。
6、gawk会从stdin接收数据,会针对数据流中每行文本执行程序脚本。gawk的主要特性之一是其处理文本文件中数据的能力。它会自动给一行中的每个数据元素分配一个变量。$0代表整个文本行、$1代表第一个数据字段,以此类推。每个数据字段都是通过字段分隔符划分的。默认的字段分隔符是任意的空白字符。
7、在程序脚本中使用多个命令。在命令之间放个分号即可。从文件中读取程序,使用-f选项即可。可以在程序文件中指定多条命令,每个命令可以单独放一行,就不用使用分号了。
script3.gawk程序脚本定义了一个变量来保存print命令中用到的文本字符串。注意gawk程序在引用变量值时并未像shell脚本一样使用美元符。

root@master:~# echo "My name is Rich" | gawk '{$4="cathy"; print $0}'
My name is cathy
root@master:~# cat script3.gawk                                      
{
text="'s home directory is "
print $1 text $6
}
root@master:~# gawk -F: -f script3.gawk /etc/passwd                  
root's home directory is /root
daemon's home directory is /usr/sbin
bin's home directory is /bin

8、在处理数据前运行脚本。BEGIN关键字。它会强制gawk在读取数据前执行BEGIN关键字后指定的程序脚本。在处理数据后运行脚本。END关键字。会在处理完数据后执行。

root@master:~# gawk 'BEGIN {print "The data3 File Contents:"} {print $0}' data3.txt 
The data3 File Contents:
Line 1
Line 2
Line 3
root@master:~# gawk 'BEGIN {print "hello world"}'
hello world

root@master:~# cat data3.txt
Line 1
Line 2
Line 3
root@master:~# gawk 'BEGIN {print "The data3 File Contents:"} {print $0} END {print "End of File"}' data3.txt  
The data3 File Contents:
Line 1
Line 2
Line 3
End of File

root@master:~# cat script4.gawk 
BEGIN {
print "The latest list of users and shells"
print " UserID \t Shell"
print " ------ \t -----"
FS=":"
}

{
print $1 "     \t " $7
}

END {
print "This concludes the listing"
}
root@master:~# gawk -f script4.gawk /etc/passwd
The latest list of users and shells
 UserID          Shell
 ------          -----
root             /bin/bash
daemon           /usr/sbin/nologin
bin              /usr/sbin/nologin
sys              /usr/sbin/nologin
sync             /bin/sync
games            /usr/sbin/nologin
man              /usr/sbin/nologin

9、sed更多的替换选项。默认情况下只替换每行中出现的第一处。要让替换命令能够替换一行中不同地方出现的文本必须使用替换标记。替换标记会在替换命令字符串之后设置。s/pattern/replacement/flags。有四种可用的替换标记:数字,表明新文本将替换第几处模式匹配到的地方;g,表示新文本将替换所有匹配的文本;p,表示原先行的内容要打印出来;w file,将替换的结果写到文件中。
-n选项将禁止sed编辑器输出。但p替换标记会输出修改过的行。将两者配合使用的效果就是只输出被替换命令修改过的行。

root@master:~# cat data4.txt 
This is a test of the test script.
This is the second test of the test script
root@master:~# sed 's/test/trial/' data4.txt 
This is a trial of the test script.
This is the second trial of the test script
root@master:~# sed 's/test/trial/2' data4.txt 
This is a test of the trial script.
This is the second test of the trial script
root@master:~# sed 's/test/trial/g' data4.txt  
This is a trial of the trial script.
This is the second trial of the trial script

root@master:~# cat data5.txt 
This is a test line.
This is a different line.
root@master:~# sed -n 's/test/trial/p' data5.txt  
This is a trial line.
root@master:~# sed -n 's/test/trial/w test.txt' data5.txt  
root@master:~# cat test.txt 
This is a trial line.

10、使用寻址。默认情况下,在sed编辑器中使用的命令会作用于文本数据的所有行。如果只想将命令作用于特定行或某些行,则必须使用行寻址。在sed编辑器中有两种形式的行寻址。以数字形式表示行区间,用文本模式来过滤出行。
以数字方式的行寻址。在命令行指定的地址可以是单个行号,或是用起始行号、逗号以及结尾行号指定的一定区间范围内的行。$表示尾行。
使用文本模式过滤器。/pattern/command,必须使用正斜线将要制定的pattern封起来。sed编辑器会将该命令作用到包含指定文本模式的行上。

root@master:~/tmp/shell# sed '2s/dog/cat/' data1.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 dog.
The quick brown fox jumps over the lazy dog.
root@master:~/tmp/shell# sed '2,3s/dog/cat/' data1.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@master:~/tmp/shell# sed '2,$s/dog/cat/' data1.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@master:~/tmp/shell# grep cathy /etc/passwd
cathy:x:1001:1001::/home/cathy:/bin/sh
root@master:~/tmp/shell# sed -n '/cathy/s/sh/bash/p' /etc/passwd                              
cathy:x:1001:1001::/home/cathy:/bin/bash

root@master:~/tmp/shell# sed '2{
> s/fox/elephant/
> s/dog/cat/
> }' data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown elephant jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

11、删除行。删除命令d名副其实,它会删除匹配指定寻址模式的所有行。使用该命令的时候要特别小心,如果忘记加入寻址模式的话,流中的所有文本行都会被删除。
12、插入和附加文本。i,在指定行前增加一个新行;a,在指定行后增加一个新行。
不加指定行时,默认在所有行前(i)或后(a)加上附加文本。
只想加到首行,1i;只想加在末尾行,$a;

root@master:~/tmp/shell# cat data1.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@master:~/tmp/shell# sed 'd' data1.txt 

root@master:~/tmp/shell# cat data6.txt 
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
root@master:~/tmp/shell# sed '/number 1/d' data6.txt 
This is line number 2.
This is line number 3.
This is line number 4.
root@master:~/tmp/shell# sed '/1/,/3/d' data6.txt 
This is line number 4.

root@master:~/tmp/shell# echo "Test Line 2" | sed 'a Test Line 1' 
Test Line 2
Test Line 1
root@master:~/tmp/shell# echo "Test Line 2" | sed 'i Test Line 1' 
Test Line 1
Test Line 2

root@master:~/tmp/shell# sed '3i This is an inserted line.' data6.txt 
This is line number 1.
This is line number 2.
This is an inserted line.
This is line number 3.
This is line number 4.
root@master:~/tmp/shell# sed '3a This is an appended line.' data6.txt 
This is line number 1.
This is line number 2.
This is line number 3.
This is an appended line.
This is line number 4.

root@master:~/tmp/shell# sed 'i This is an inserted line.' data6.txt    
This is an inserted line.
This is line number 1.
This is an inserted line.
This is line number 2.
This is an inserted line.
This is line number 3.
This is an inserted line.
This is line number 4.
root@master:~/tmp/shell# sed 'a This is an inserted line.' data6.txt  
This is line number 1.
This is an inserted line.
This is line number 2.
This is an inserted line.
This is line number 3.
This is an inserted line.
This is line number 4.
This is an inserted line.

root@master:~/tmp/shell# sed '1i This is an inserted line.' data6.txt 
This is an inserted line.
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
root@master:~/tmp/shell# sed '$a This is an inserted line.' data6.txt   
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is an inserted line.

root@master:~/tmp/shell# sed '1i This is an inserted line. \
> This is another line of new text.' data6.txt
This is an inserted line. 
This is another line of new text.
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

13、修改行。c。允许修改数据流中整行文本的内容。
在修改命令中可以使用地址区间,但sed编辑器会用这一行文本来替换数据流中的两行文本,而不是逐一修改这两行文本。

root@master:~/tmp/shell# sed '3c\
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.
root@master:~/tmp/shell# sed '/number 3/c \
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.

oot@master:~/tmp/shell# cat data8.txt 
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is line number 1 again.
This is yet another line.
This is the last line in the file.
root@master:~/tmp/shell# sed '/number 1/c\
This is a changed line of text.' data8.txt
This is a changed line of text.
This is line number 2.
This is line number 3.
This is line number 4.
This is a changed line of text.
This is yet another line.
This is the last line in the file.

root@master:~/tmp/shell# sed '2,3c\
> this is a new line of text.' data6.txt
This is line number 1.
this is a new line of text.
This is line number 4.

14、转换命令。y。转换命令会对inchars和outchars值进行一对一的映射。incharts中的第一个字符会被转换为outcharts中的第一个字符,第二个字符会被转换为outcharts中的第二个字符。这个映射过程会一直持续到处理完指定字符。如果incharts和outcharts的长度不同,则sed编辑器会产生一条错误消息。转换命令是一个全局命令,也就是说,它会在文本行中找到的所有指定字符进行自动转换,而不会考虑他们出现的位置。

15、回顾打印。p命令用来打印文本行。=命令用来打印行号。l命令用来列出行。

root@master:~/tmp/shell# echo "this is a test." | sed 'p'
this is a test.
this is a test.
root@master:~/tmp/shell# sed -n '/number 1/p' data6.txt  
This is line number 1.
root@master:~/tmp/shell# sed -n '1,3p' data6.txt            
This is line number 1.
This is line number 2.
This is line number 3.
root@master:~/tmp/shell# sed -n '/3/{
> p
> s/line/test/p
> }' data6.txt
This is line number 3.
This is test number 3.
root@master:~/tmp/shell# sed '=' data1.txt 
1
The quick brown fox jumps over the lazy dog.
2
The quick brown fox jumps over the lazy dog.
3
The quick brown fox jumps over the lazy dog.
4
The quick brown fox jumps over the lazy dog.

16、写入文件,w。从文件中读取数据,r。在读取命令中使用地址区间,只能指定单独的一个行号或文本模式地址。sed编辑器会将文件中的所有文本行插入到指定地址后。

root@master:~/tmp/shell# cat data12.txt 
This is an added line.
This is the second added line.
root@master:~/tmp/shell# sed '3r data12.txt' data6.txt 
This is line number 1.
This is line number 2.
This is line number 3.
This is an added line.
This is the second added line.
This is line number 4.
root@master:~/tmp/shell# sed '/number 2/r data12.txt' data6.txt 
This is line number 1.
This is line number 2.
This is an added line.
This is the second added line.
This is line number 3.
This is line number 4.
root@master:~/tmp/shell# sed '$r data12.txt' data6.txt 
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is an added line.
This is the second added line.

root@master:~/tmp/shell# cat data11.txt 
Blum,   R   Browncoat
McGuiness,  A   Alliance
root@master:~/tmp/shell# cat notice.std 
Would the following people:
LIST
please report to the ship's captain.
root@master:~/tmp/shell# sed '/LIST/{   
r data11.txt
d
}' notice.std 
Would the following people:
Blum,   R   Browncoat
McGuiness,  A   Alliance
please report to the ship's captain.

十七、正则表达式

1、正则表达式是定义的模式模板,Linux工具可以用它来过滤文本。Linux工具能够在处理数据时使用正则表达式对数据进行模式匹配。如果数据匹配模式,它就会被接受并进一步处理;否则会被过滤掉。正则表达式模式利用通配符来描述数据流中的一个或多个字符。
正则表达式是通过正则表达式引擎实现的。正则表达式引擎是一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。在Linux中,有两种流行的正则表达式引擎:POSIX基础正则表达式(Basic regular expression,BRE)引擎和POSIX扩展正则表达式(extended regular expression,ERE)引擎。
2、正则表达式匹配模式区分大小写;正则表达式识别的特殊字符包含: .*[]^${}+?|()
锚字符:^行首,$行尾。默认情况下,当指定一个正则表达式模式时,只要模式出现在数据流的任何位置,他都会被匹配。有两个特殊字符可以将模式锁定在数据流中的行首或行尾。如果将脱字符(^)或美元符($)放在模式开头或结尾外的其他位置,那么他就跟普通字符一样,不再是特殊字符了。
点字符用来匹配除换行符外的任意单个字符。方括号[],用来匹配文本模式中某个位置的一组字符,方括号中包含希望所有出现在该字符组中的字符。
排除型字符组,[^xy]。寻找组中没有的字符。
区间[0-9][1-6];根据Linux系统采用的字符集,正则表达式会包括此区间内的任意字符。
*号,该字符出现0或多次。组合.*,能够匹配任意数量的任意字符。
3、扩展正则表达式。问号?,表明前面的字符可以出现0或1次。加号+,前面的字符可以出现1次或多次。
花括号{},允许为可重复的正则表达式指定一个上线,这通常称为间隔,可以用两种格式来指定区间,m:正则表达式准确出现m次;m,n:正则表达式至少出现m次,至多出现n次。
4、目录文件计数

root@master:~/tmp/shell# cat countfiles 
#!/bin/bash
# count number of files in your PATH
mypath=$(echo $PATH | sed 's/:/ /g')
count=0

for directory in $mypath; do
    files=$(ls $directory | wc -l)
    echo "$directory - $files"
    count=$[ $count + $files ]
done

echo "count: $count"
root@master:~/tmp/shell# bash countfiles  
/usr/local/sbin - 0
/usr/local/bin - 1
/usr/sbin - 404
/usr/bin - 1082
/sbin - 404
/bin - 1082
/usr/games - 0
/usr/local/games - 0
/snap/bin - 8
count: 2981

十八、sed进阶

1、多行命令。有时需要对跨多行的数据执行特定操作。sed编辑器包含了三个可用来处理多行文本的特殊命令。N:将数据流中的下一行加进来创建一个多行组来处理。D:删除多行组中的一行。P:打印多行组中的一行。
2、单行的next命令,小写的n命令会告诉sed编辑器移动到数据流中的下一文本行,而不用重新回到命令的最开始再执行一遍。记住,通常sed编辑器在移动到数据流中的下一文本行之前,会在当前行上执行完所有定义好的命令。单行next命令改变了这个流程。

root@master:~/tmp/shell# cat data1.txt 
This is the header line.

This is a data line.

This is the last line.
root@master:~/tmp/shell# sed '/^$/d' data1.txt         
This is the header line.
This is a data line.
This is the last line.
root@master:~/tmp/shell# sed '/header/{n;d}' data1.txt         
This is the header line.
This is a data line.

This is the last line.

3、合并文本行。单行next命令会将数据流中的下一文本行移动到sed编辑器的工作空间(称为模式空间)。多行版本的next命令(N)会将下一文本行添加到模式空间中已有的文本后。

root@master:~/tmp/shell# cat data2.txt 
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.

root@master:~/tmp/shell# sed '/first/{N; s/\n/ /}' data2.txt 
This is the header line.
This is the first data line. This is the second data line.
This is the last line.

root@master:~/tmp/shell# cat data4.txt 
On Tuesdat, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
Thank you for your attendance.

root@master:~/tmp/shell# sed 'N; s/System Administrator/Desktop User/' data4.txt
On Tuesdat, the Linux System
Administrator's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.

root@master:~/tmp/shell# sed 'N; s/System.Administrator/Desktop User/' data4.txt
On Tuesdat, the Linux Desktop User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.
root@master:~/tmp/shell# cat data4.txt 
On Tuesdat, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
Thank you for your attendance.

root@master:~/tmp/shell# sed 'N; s/System.Administrator/Desktop User/' data4.txt
On Tuesdat, the Linux Desktop User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.
root@master:~/tmp/shell# sed 'N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesdat, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.

# 脚本有个小问题,脚本总是在执行sed编辑器命令前将下一行文本读入到模式空间,但当它读到最后一行文本时,就没有下一行可读了,所以N命令会叫sed编辑器停止。如果要匹配的文本正好在数据流的最后一行上,命令就不会发现要匹配的数据。
# 由于system Administrator文本出现在了数据流中的最后一行,N命令会错过它,因为没有其他行可读入到模式空间跟这行合并。可以轻松解决此问题----将单行命令放到N命令前面,并将多行命令放到N命令后面。
root@master:~/tmp/shell# cat data5.txt 
On Tuesdat, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.

root@master:~/tmp/shell# sed 'N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data5.txt
On Tuesdat, the Linux Desktop
User's group meeting will be held.
All System Administrators should attend.

root@master:~/tmp/shell# sed '
> s/System Administrator/Desktop User/
> N
> s/System\nAdministrator/Desktop\nUser/
> ' data5.txt
On Tuesdat, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.

十九、gawk进阶

1、内建变量:数据字段变量$1$2;输入字段分隔符FS;输入记录分隔符RS;输出字段分隔符OFS;输出记录分隔符ORS;数据字段确切宽度FIELDWIDTHS;已处理的输入记录数NR;数据文件中的字段总数;(跟shell变量不同,在脚本中引用gawk变量时,变量名前不加美元符)。
FNR和NR的区别,如果只使用一个数据文件作为输入,FNR和NR的值是相同的;如果使用多个数据文件作为输入,FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数直到处理完所有的数据文件。
2、自定义变量:区分大小写。在脚本中给变量赋值;在命令行上给变量赋值,会有一个问题,这个值在代码的BEGIN部分不能使用,需要使用-v参数来解决。

root@master:~/tmp/shell# cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
root@master:~/tmp/shell# gawk -F, '{print $1,$2}' data1  
data11 data12
data21 data22
data31 data32
root@master:~/tmp/shell# gawk -F, '{print $1 $2}' data1  
data11data12
data21data22
data31data32
root@master:~/tmp/shell# gawk -F, '{print $1","$2}' data1
data11,data12
data21,data22
data31,data32
root@master:~/tmp/shell# gawk 'BEGIN{FS=","; OFS="-"} {print $1 $2}' data1 
data11data12
data21data22
data31data32
root@master:~/tmp/shell# gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2}' data1 
data11-data12
data21-data22
data31-data32
root@master:~/tmp/shell# gawk 'BEGIN{FS=","; OFS="-"} {print $1","$2}' data1
data11,data12
data21,data22
data31,data32

root@master:~/tmp/shell# cat data2
Rm
123 Main Street
Chicogo, IL 60601
(312)555-1234

Frank
456 Oak Street
Indian, IN 46201
(317)555-9876

HS
4321 Elm Street
Detroit, MI 48201
(313)555-4938
root@master:~/tmp/shell# gawk 'BEGIN{FS="\n";RS=""} {print $1,$4}' data2  
Rm (312)555-1234
Frank (317)555-9876
HS (313)555-4938

root@master:~/tmp/shell# gawk -F, '{print $1,"FNR="FNR, "NR="NR}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6

root@master:~/tmp/shell# cat script1
BEGIN{FS=","} {print $n}
root@master:~/tmp/shell# gawk -f script1 n=2 data1
data12
data22
data32
root@master:~/tmp/shell# gawk -f script1 n=3 data1
data13
data23
data33

root@master:~/tmp/shell# cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
root@master:~/tmp/shell# gawk -f script2 n=3 data1
The starting value is 
data13
data23
data33
root@master:~/tmp/shell# gawk -v n=3 -f script2 data1   
The starting value is 3
data13
data23
data33

3、处理数组。gawk使用关联数组提供数组功能。关联数组和数字数组的不同之处在于它的索引值可以是任意文本字符串。和字典很像。遍历数组变量,注意变量中存储的是索引值而不是数组元素值,索引值不会按照任何顺序返回。

root@master:~/tmp/shell# gawk 'BEGIN{capital["Illinois"]="Springfield"
> print capital["Illinois"]
> }'
Springfield
root@master:~/tmp/shell# gawk 'BEGIN{var[1]=1
> var[2]=2
> total=var[1]+var[2]
> print total
> }'
3
root@master:~/tmp/shell# gawk 'BEGIN{
> var["a"]=1
> var["b"]=2
> var["c"]=c
> for (test in var)
> {print "Index:"test," - Value:",var[test]}
> }'
Index:a  - Value: 1
Index:b  - Value: 2
Index:c  - Value: 
# 为啥这里的索引c没有值呢???

4、正则表达式必须出现在他要控制的程序脚本的左花括号前。

root@master:~/tmp/shell# gawk -F, '/11/{print $1}' data1
data11

5、匹配操作符(~)允许将正则表达式限定在记录中的特定数据字段。

root@master:~/tmp/shell# gawk -F, '$1 ~ /data2/{print $0}' data1
data21,data22,data23,data24,data25

6、数字表达式(==、<=、>=、<、>)。表达式必须完全匹配。

root@master:~/tmp/shell# gawk -F: '$4 == 0 {print $1}' /etc/passwd
root
root@master:~/tmp/shell# gawk -F, '$1 == "data1" {print $0}' data1   
root@master:~/tmp/shell# gawk -F, '$1 == "data11" {print $0}' data1
data11,data12,data13,data14,data15

7、结构化命令。

root@master:~/tmp/shell# cat data4
10
5
13
23
4
5
24
root@master:~/tmp/shell# gawk '{if ($1>20) print $1}' data4
23
24
root@master:~/tmp/shell# gawk '{if ($1>20) print $1*2}' data4 
46
48
root@master:~/tmp/shell# gawk '{if ($1>20) print $1; else print $1*2,"=double",$1}' data4
20 =double 10
10 =double 5
26 =double 13
23
8 =double 4
10 =double 5
24

8、格式化输出。printf。需要在printf命令的末尾手动添加换行符来生成新行。

root@master:~/tmp/shell# gawk -F, '{printf "%s", $1} END{printf "\n"}' data1
data11data21data31
root@master:~/tmp/shell# gawk -F, '{printf "%s", $1}' data1                 
data11data21data31root@master:~/tmp/shell#

9、函数

root@master:~/tmp/shell# cat scores.txt   
RB,team1,100,115,95
BB,team1,110,115,100
CB,team2,120,115,118
TB,team2,125,112,116
root@master:~/tmp/shell# cat bowling.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@master:~/tmp/shell# bash bowling.sh 
Total for team1 is 635 ,the average is 105.833
Total for team2 is 706 ,the average is 117.667

二十、编写简单的脚本实用工具

1、文件目录归档

root@master:~# mkdir /archive
root@master:~# ls -ld /archive/
drwxr-xr-x 2 root root 4096 Aug 14 07:29 /archive/
root@master:~# useradd archive
root@master:~# chmod 775 /archive/
root@master:~# chgrp archive /archive/
root@master:~# ls -ld /archive/       
drwxrwxr-x 2 root archive 4096 Aug 14 07:29 /archive/
root@master:/archive# mkdir conf
root@master:/archive# mkdir bin

root@master:/archive# cat conf/Files_To_Backup 
/root/tmp
/root/demo
/root/tigera-operator.yaml

root@master:/archive# cat bin/Daily_Archive.sh 
#!/bin/bash
#
# Daily_Archive - Archive designated file & directories
#######################################################
#
# Gather Current Date
#
DATE=$(date +%Y%m%d)
#
# Set Archive File name
#
FILE=archive$DATE.tar.gz
#
# Set Configuration and Destination File
#
CONFIG_FILE=/archive/conf/Files_To_Backup
DESTINATION=/archive/$FILE
#
############# Main Script ################
#
# Check Backup Config file exists
#
if [ -f $CONFIG_FILE ]; then  # Make sure the config file still exists.
        echo  # If it exists, do nothing but continue on.
else          # If it not exists, issue error & exit script.
        echo
        echo "$CONFIG_FILE does not exists."
        echo "Backup not completed due to missing Configuration File"
        echo
        exit
fi
#
# Build the names of all the files to backup
#
FILE_NO=1               # Start on Line 1 of Config File.
exec < $CONFIG_FILE     # Redirect Std Input to name of Config File
read FILE_NAME          # Read 1st record
#
while [ $? -eq 0 ]; do  # Create list of files to backup.
        # Make sure the file or directory exists.
        if [ -f $FILE_NAME -o -d $FILE_NAME ]; then
                # If file exists, add its name to the list.
                FILE_LIST="$FILE_LIST $FILE_NAME"
        else
                # If file not exists, issue warning
                echo
                echo "$FILE_NAME, doesn't exist."
                echo "Obviously, I will not include it in this archive."
                echo "It is listed on line $FILE_NO of the config file."
                echo "Continuing to build archive list..."
                echo
        fi
#
        FILE_NO=$[$FILE_NO + 1]  # Increase Line/File number by one.
        read FILE_NAME           # Read next record.
done
#
###############################################
#
# Backup the files and Compress Archive
#
echo "Starting archive..."
echo
#
tar -czf $DESTINATION $FILE_LIST 2> /dev/null
#
echo "Archive completed"
echo "Resulting archive file is: $DESTINATION"
echo
#
exit

root@master:/archive# bash bin/Daily_Archive.sh 

Starting archive...

Archive completed
Resulting archive file is: /archive/archive20220814.tar.gz

2、删除系统用户

root@master:~/tmp/shell# cat Delete_User.sh 
#!/bin/bash
#
# Delete_User - Automates the 4 steps to remove an account
#
#############################################################
#
# Define Functions
#
#############################################################
#
function get_answer {
unset ANSWER
ASK_COUNT=0
#
while [ -z "$ANSWER" ]; do     # While no answer is given, keep asking.
    ASK_COUNT=$[ $ASK_COUNT + 1 ]
#
    case $ASK_COUNT in     # If user gives no answer in time allotted
    2)
            echo
            echo "Please answer the question."
            echo
    ;;
    3)
            echo
            echo "One last try...please answer the question."
            echo
    ;;
    4)
            echo
            echo "Since you refuse to answer the question..."
            echo "exiting program."
            echo
            #
            exit
    ;;
    esac
#
    echo
#
    if [ -n "$LINE2" ]; then
            echo $LINE1
            echo -e $LINE2"  \C"
    else
            echo -e $LINE1"  \c"
    fi
#
#    Allow 60 seconds to answer before time-out
    read -t 5 ANSWER
done
# Do a little variable clean-up
unset LINE1
unset LINE2
#
}  # End of get_answer function
#
###############################################
#
function process_answer {
#
case $ANSWER in
y|Y|yes|Yes)
        # If user answers "yes", do nothing.
        ;;
*)
        # If user answers anything but "yes", exit script
        echo
        echo $EXIT_LINE1
        echo $EXIT_LINE2
        echo
        exit
        ;;
esac
#
# Do a little variable clean-up
#
unset EXIT_LINE1
unset EXIT_LINE2
#
}  # End of process_answer function
#
##############################################
#
# End of Function Definitions
#
################## Main Script ##################
#
# Get name of User Account to check
#
echo "Step #1 - Determine User Account name to Delete "
echo
LINE1="Please anter the username of the user."
LINE2="account you wise to delete from system:"
get_answer
USER_ACCOUNT=$ANSWER
#
# Double check with script user that is the correct User Account
#
LINE1="Is $USER_ACCOUNT the user account "
LINE2="you wish to delete from the system? [y|n]"
get_answer
#
# Call process_answer function:
# if user answers anything but "yes", exit script
#
EXIT_LINE1="Because the account, $USER_ACCOUNT, is not "
EXIT_LINE2="the one you wish to delete, we are leaving the script..."
process_answer
#
#######################################################
#
# Check that USER_ACCOUNT is really an account on the system
#
USER_ACCOUNT_RECORD=$(cat /etc/passwd | grep -w $USER_ACCOUNT)
#
if [ $? -eq 1 ]; then
        echo
        echo "Account, $USER_ACCOUNT, not found."
        echo "Leaving the script..."
        echo
        exit
fi
#
echo
echo "I found this record:"
echo $USER_ACCOUNT_RECORD
#
LINE1="Is this the correct User Account? [y|n]?"
get_answer
#
#
# Call process_answer function:
# If user answers anything but "yes", exit script
#
EXIT_LINE1="Because the account, $USER_ACCOUNT, is not"
EXIT_LINE2="the one you wish to delete, we are leaving the script..."
process_answer
#
#########################################################
#
# Search for any running process that belong to the User Account
#
echo
echo "Step #2 - Find process on system belong to the user account"
echo
#
ps -u $USER_ACCOUNT > /dev/null  # Are user processes running?
#
case $? in
        1) # No processes running for this User Account
                #
                echo " There are no processes for this account currently running."
                echo
                ;;
        0) # Processes running for this User Account.
                # Ask Script User if wants us to kill the processes.
                #
                echo "$USER_ACCOUNT has the following process running: "
                echo
                ps -u $USER_ACCOUNT
                #
                LINE1="Would you like me to kill the process(es)? [y|n]"
                get_answer
                #
                case $ANSWER in
                        y|Y|yes|Yes)
                                #
                                echo
                                echo "killing off process(es)..."
                                #
                                # List user processes running code in variable, COMMAND_1
                                COMMAND_1="ps -u $USER_ACCOUNT --no-heading"
                                #
                                # Create command to kill process in variable, COMMAND_3
                                COMMAND_3="xargs -d \\n /user/bin/sudo /bin/kill -9"
                                #
                                # Kill processes via piping commands together
                                $COMMAND_1 | gawk '{print $1}' | $COMMAND_3
                                #
                                echo
                                echo "Process(es) killed."
                                ;;
                        *)  # If user answers anything but "yes", do not kill.
                                echo
                                echo "Will not kill the process(es)"
                                echo
                                ;;
                esac
                ;;
esac
###########################################################
#
# Create a report of all files owned by User Account
#
echo
echo "Step #3 - Find files on system belonging to user account"
echo
echo "Createing a report of all files owned by $USER_ACCOUNT隆."
echo
echo "It is recommanded that you backup/archive there files,"
echo "and then do one of two things:"
echo "  1) Detele the files."
echo "  2) Change the files' owership to a current user account."
echo
echo "Please wait. This may take a while..."
#
REPORT_DATE=$(date +%Y%m%d)
REPORT_FILE=$USER_ACCOUNT"_Files_"$REPORT_DATE".rpt"
#
find / -user $USER_ACCOUNT > $REPORT_FILE 2> /dev/null
#
echo
echo "Report is complete."
echo "Name of report:    $REPORT_FILE"
echo "Location of report: $(pwd)"
echo
#######################################
# Remove User Account
echo
echo "Step #4 - Remove user account"
echo
#
LINE1="Remove $USER_ACCOUNT's account from system? [y|n]?"
get_answer
#
# Call process_answer function:
# if user answer anything but "yes", exit script..."
process_answer
#
userdel $USER_ACCOUNT
echo
echo "User account, $USER_ACCOUNT, has been removed"
echo
#
exit

3、监测磁盘空间

#!/bin/bash
#
# Big_Users - Find big disk space user in various directories
#############################################################
# Parametes for script
#
CHECK_DIRECTORIES=" /var/log /home"
#
####################### Main Script #########################
#
DATE=$(date +%Y%m%d)
#
exec > disk_space_$DATE.rpt
#
echo "Top Ten Disk Space Usage"
echo "for $CHECK_DIRECTORIES Directories"
#
for DIR_CHECK in $CHECK_DIRECTORIES; do
        echo
        echo "The $DIR_CHECK Directory:"
 #
 # Create a listing of top ten dick space users in this dir
        du -S $DIR_CHECK 2> /dev/null |
        sort -rn |
        sed '{11,$D; =}' |
        sed 'N; s/\n/ /' |
        gawk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
#
done
#
exit

root@master:~/tmp/shell# cat disk_space_20220814.rpt 
Top Ten Disk Space Usage
for  /var/log /home Directories

The /var/log Directory:
1:      1048720 /var/log/journal/b89e764f7cc7468e9d5671be15153636
2:      9572    /var/log
3:      796     /var/log/calico/cni
4:      124     /var/log/containers
5:      116     /var/log/apt
6:      104     /var/log/unattended-upgrades
7:      20      /var/log/pods/kube-system_kube-scheduler-master_dfde75dd7f28a4a10a47fa93992cc6d2/kube-scheduler
8:      20      /var/log/pods/kube-system_etcd-master_f27ea89a828465854abe9cb0d3fd91d6/etcd
9:      12      /var/log/pods/tigera-operator_tigera-operator-b876f5799-kvt4m_15a68c7f-5cc5-4018-a57b-59d908cb9b85/tigera-operator
10:     12      /var/log/pods/kube-system_kube-proxy-zk7qx_e16a254d-b00d-42a8-95fd-dca631c7d1ac/kube-proxy

The /home Directory:
1:      230904  /home/ubuntu
2:      4       /home/ubuntu/.ssh
3:      4       /home/ubuntu/.cache
4:      4       /home

总结

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值