Mark blog

知行合一 划水归档

文件查找

在文件系统上查找符合条件的文件

文件查找:locate, find

​ 非实时查找(数据库查找):locate

​ 实时查找:find

locate

查询系统上预建的文件索引数据库

1
/var/lib/mlocate/mlocate.db

依赖于事先构建的索引

索引的构建是在系统较为空闲时自动进行(周期性任务),管理员手动更新数据库 (updatedb)

索引构建过程需要遍历整个根文件系统,极消耗资源

工作特点:

• 查找速度快

• 模糊查找

• 非实时查找

• 搜索的是文件的全路径,不仅仅是文件名

• 可能只搜索用户具备读取和执行权限的目录

locate 命令

locate KEYWORD

有用的选项

​ -i 不区分大小写的搜索

​ -n N 只列举前N个匹配项目

​ -r 使用正则表达式

示例

搜索名称或路径中带有”conf”的文件

1
locate conf

使用Regex来搜索以”.conf”结尾的文件

1
locate -r ‘\.conf$’

find

实时查找工具,通过遍历指定路径完成文件查找

工作特点:

​ • 查找速度略慢

​ • 精确查找

​ • 实时查找

​ • 可能只搜索用户具备读取和执行权限的目录

语法:

find [OPTION]… [查找路径][查找条件][处理动作]

查找路径:指定具体目标路径;

默认为当前目录 查找条件:指定的查找标准,可以文件名、大小、类型、权限等标准进行; 默认为找出指定路径下的所有文件

处理动作:对符合条件的文件做操作,默认输出至屏幕

查找条件

指搜索层级

​ -maxdepth level 最大搜索目录深度,指定目录为第1级

​ -mindepth level 最小搜索目录深度

根据文件名和inode查找:

​ -name “文件名称”:支持使用glob

​ *, ?, [], [^]

​ -iname “文件名称”:不区分字母大小写

​ -inum n 按inode号查找

​ -samefile name 相同inode号的文件

​ -links n 链接数为n的文件

​ -regex “PATTERN”:以PATTERN匹配整个文件路径字符串,而不仅仅是文 件名称

根据属主、属组查找:

​ -user USERNAME:查找属主为指定用户(UID)的文件

​ -group GRPNAME: 查找属组为指定组(GID)的文件

​ -uid UserID:查找属主为指定的UID号的文件

​ -gid GroupID:查找属组为指定的GID号的文件

​ -nouser:查找没有属主的文件

​ -nogroup:查找没有属组的文件

根据文件类型查找:

​ -type TYPE:

​ • f: 普通文件

​ • d: 目录文件

​ • l: 符号链接文件

​ • s:套接字文件

​ • b: 块设备文件

​ • c: 字符设备文件

​ • p: 管道文件

空文件或目录

​ -empty

​ find /app -type d -empty

组合条件:

​ 与:-a

​ 或:-o

​ 非:-not, !

德·摩根定律:

(非 A) 或 (非 B) = 非(A 且 B) 

(非 A) 且 (非 B) = 非(A 或 B) 

示例:

!A -a !B = !(A -o B) 

!A -o !B = !(A -a B) 

查找条件

根据文件大小来查找:

-size [+|-]#UNIT

常用单位:k, M, G,c(byte) 

#UNIT: (#-1, #]

如:6k 表示(5k,6k] 

-#UNIT:[0,#-1]

如:-6k 表示[0,5k] 

+#UNIT:(#,∞)

如:+6k 表示(6k,∞)     

根据时间戳:

以”天”为单位;

​ -atime [+|-]#,

​ #: [#,#+1)

​ +#: [#+1,∞]

​ -#: [0,#)

​ -mtime

​ -ctime

以”分钟”为单位:

​ -amin

​ -mmin

​ -cmin

根据权限查找:

-perm [/|-]MODE

MODE: 精确权限匹配

​ /MODE:任何一类(u,g,o)对象的权限中只要能一位匹配即可,或关系,+ 从centos7开始淘汰

​ -MODE:每一类对象都必须同时拥有指定权限,与关系 0 表示不关注

​ • find -perm 755 会匹配权限模式恰好是755的文件

​ • 只要当任意人有写权限时,find -perm +222就会匹配

​ • 只有当每个人都有写权限时,find -perm -222才会匹配

​ • 只有当其它人(other)有写权限时,find -perm -002才会匹配

处理动作

-print:默认的处理动作,显示至屏幕

-ls:类似于对查找到的文件执行”ls -l”命令

-delete:删除查找到的文件

-fls file:查找到的所有文件的长格式信息保存至指定文件中

-ok COMMAND {} ;

​ 对查找到的每个文件执行由COMMAND指定的命令,对于 每个文件执行命令之前,都会交互式要求用户确认

-exec COMMAND {} ;

​ 对查找到的每个文件执行由COMMAND指定的命令

{}: 用于引用查找到的文件名称自身

find传递查找到的文件至后面指定的命令时,查找到所有符合条件的文件一次性 传递给后面的命令

参数替换xargs

由于很多命令不支持管道|来传递参数,而日常工作中有这个必要,所以就有了 xargs命令

xargs用于产生某个命令的参数,xargs 可以读入 stdin 的数据,并且以空格符 或回车符将 stdin 的数据分隔成为arguments

注意:文件名或者是其他意义的名词内含有空格符的情况

有些命令不能接受过多参数,命令执行可能会失败,xargs可以解决

示例:

​ ls f* |xargs rm

​ find /sbin -perm +700 |ls -l 这个命令是错误的

​ find /sbin -perm +700 | xargs ls –l

find和xargs格式:find | xargs COMMAND

压缩,解压缩及归档工具

file-roller

compress/uncompress: .Z

gzip/gunzip: .gz

bzip2/bunzip2: .bz2

xz/unxz: .xz

zip/unzip

tar

cpio

compress/uncompress

compress [-dfvcVr]/[-b maxbits]/[file …]

​ -d: 解压缩,相当于uncompress

​ -c: 结果输出至标准输出,不删除原文件

​ -v: 显示详情

uncompress 解压缩

zcat file.Z >file

gzip/gunzip

gzip [OPTION]… FILE …

​ -d: 解压缩,相当于gunzip

​ -c: 将压缩或解压缩的结果输出至标准输出

​ -#:1-9,指定压缩比,值越大压缩比越大

zcat:不显式解压缩的前提下查看文本文件内容

实例:

​ gzip -c messages >messages.gz

​ gzip -c -d messages.gz > messages

​ zcat messages.gz > messages

bzip2/bunzip/bzcat

bzip2 [OPTION]… FILE …

​ -k: keep, 保留原文件

​ -d:解压缩

​ -#:1-9,压缩比,默认为9

bzcat:不显式解压缩的前提下查看文本文件内容

xz/unxz/xzcat

xz [OPTION]… FILE …

​ -k: keep, 保留原文件

​ -d:解压缩

​ -#:1-9,压缩比,默认为6

xzcat: 不显式解压缩的前提下查看文本文件内容

zip/unzip

打包压缩

1
zip –r /testdir/sysconfig /etc/sysconfig/

解包解压缩

1
2
3
4
5
unzip sysconfig.zip 

cat /var/log/messages | zip messages -

unzip -p message > message

tar工具

tar(Tape ARchive,磁带归档的缩写)

tar [OPTION]…

(1) 创建归档

1
tar -cpvf /PATH/TO/SOMEFILE.tar FILE...

(2) 追加文件至归档: 注:不支持对压缩文件追加

1
tar -r -f /PATH/TO/SOMEFILE.tar FILE...

(3) 查看归档文件中的文件列表

1
tar -t -f /PATH/TO/SOMEFILE.tar

(4) 展开归档

1
2
3
tar -x -f /PATH/TO/SOMEFILE.tar 

tar -x -f /PATH/TO/SOMEFILE.tar -C /PATH/

(5) 结合压缩工具实现:归档并压缩

1
-j: bzip2, -z: gzip, -J: xz

-T选项指定输入文件,-X选项指定包含要排除的文件列表

1
tar zcvf mybackup.tgz -T /root/includefilelist -X /root/excludefilelist

分割大的 tar 文件为多份小文件:

1
2
3
4
5
split –b Size –d tar-file-name prefix-name 

split -b 1M –d mybackup.tgz mybackup-parts

split -b 1M mybackup.tgz mybackup-parts

合并:

1
cat mybackup-parts* > mybackup.tar.gz

cpio

功能:复制文件从或到归档

cpio命令是通过重定向的方式将文件进行打包备份,还原恢复的工具,它可以 解压以”.cpio”或者”.tar”结尾的文件

cpio [选项] > 文件名或者设备名

cpio [选项] < 文件名或者设备名

选项

​ -o 将文件拷贝打包成文件或者将文件输出到设备上

​ -i 解包,将打包文件解压或将设备上的备份还原到系统

​ -t 预览,查看文件内容或者输出到设备上的文件内容

​ -v 显示打包过程中的文件名称。

​ -d 解包生成目录,在cpio还原时,自动的建立目录

​ -c 一种较新的存储方式

需求分析

学习 shell 的最好方法就是去实践,网上有很多的源码可供参考

实现过程

源码分析:

先从游戏主程序开始分析:

1
2
3
4
5
6
7
8
9
10
11
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
Usage
elif [[ "$1" == "--version" ]]; then
echo "$APP_NAME $APP_VERSION"
elif [[ "$1" == "--show" ]]; then
#当发现具有参数--show时,运行显示函数
RunAsDisplayer
else
bash $0 --show& #以参数--show将本程序再运行一遍
RunAsKeyReceiver $! #以上一行产生的进程的进程号作为参数
fi

里面出现了两个变量,查看定义后发现

APP_NAME : 打印了脚本名

APP_NAME="${0##*[\\/]}"

APP_VERSION : 自己定义了版本号

APP_VERSION="1.0"

其中出现了3个函数

Usage :显示脚本用法的函数

RunAsDisplayer :处理显示和游戏流程的主函数

RunAsKeyReceiver :接收输入的进程的主函数

我们从头开始分析这些函数的实现

1
2
3
4
5
6
7
8
9
10
function Usage
{
cat << EOF
Usage: $APP_NAME
Start tetris game.

-h, --help display this help and exit
--version output version information and exit
EOF
}

可以看出 Usage 函数就是打印了一些信息,主要是用户提示.

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
function RunAsDisplayer()
{
local sigThis #定义了局部变量 sigThis
InitDraw

#挂载各种信号的处理函数
trap "sig=$sigRotate;" $sigRotate
trap "sig=$sigLeft;" $sigLeft
trap "sig=$sigRight;" $sigRight
trap "sig=$sigDown;" $sigDown
trap "sig=$sigAllDown;" $sigAllDown
trap "ShowExit;" $sigExit

while :
do
#根据当前的速度级iLevel不同,设定相应的循环的次数
for ((i = 0; i < 21 - iLevel; i++))
do
sleep 0.02
sigThis=$sig
sig=0

#根据sig变量判断是否接受到相应的信号
if ((sigThis == sigRotate)); then BoxRotate; #旋转
elif ((sigThis == sigLeft)); then BoxLeft; #左移一列
elif ((sigThis == sigRight)); then BoxRight; #右移一列
elif ((sigThis == sigDown)); then BoxDown; #下落一行
elif ((sigThis == sigAllDown)); then BoxAllDown; #下落到底
fi
done
#kill -$sigDown $$
BoxDown #下落一行
done
}

暂时先不管 RunAsDisplayer 中调用的 InitDraw 函数,先看一下这个函数的功能.

从流程上分析这个函数实现了按键信号的捕捉,然后在一个死循环中以操作时间为0.02秒,不断的执行对方块的移动操作,然后执行下落一行的操作.

接下来对其中出现的参数和函数进行分析:

trap 这个命令实现了shell中脚本信号的获取,可以在脚本运行中执行额外的操作,比如获取键盘的输入用来控制方块的运动轨迹.

iLevel 这个参数是定义的速度级,这个稍后会提到.

BoxDown : 控制方块下落的函数

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
function RunAsKeyReceiver()
{
local pidDisplayer key aKey sig cESC sTTY

pidDisplayer=$1
aKey=(0 0 0)

cESC=`echo -ne "\033"`
cSpace=`echo -ne "\040"`

#保存终端属性。在read -s读取终端键时,终端的属性会被暂时改变。
#如果在read -s时程序被不幸杀掉,可能会导致终端混乱,
#需要在程序退出时恢复终端属性。
sTTY=`stty -g`

#捕捉退出信号
trap "MyExit;" INT TERM
trap "MyExitNoSub;" $sigExit

#隐藏光标
echo -ne "\033[?25l"


while :
do
#读取输入。注-s不回显,-n读到一个字符立即返回
read -s -n 1 key

aKey[0]=${aKey[1]}
aKey[1]=${aKey[2]}
aKey[2]=$key
sig=0

#判断输入了何种键
if [[ $key == $cESC && ${aKey[1]} == $cESC ]]
then
#ESC键
MyExit
elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]]
then
if [[ $key == "A" ]]; then sig=$sigRotate #<向上键>
elif [[ $key == "B" ]]; then sig=$sigDown #<向下键>
elif [[ $key == "D" ]]; then sig=$sigLeft #<向左键>
elif [[ $key == "C" ]]; then sig=$sigRight #<向右键>
fi
elif [[ $key == "W" || $key == "w" ]]; then sig=$sigRotate #W, w
elif [[ $key == "S" || $key == "s" ]]; then sig=$sigDown #S, s
elif [[ $key == "A" || $key == "a" ]]; then sig=$sigLeft #A, a
elif [[ $key == "D" || $key == "d" ]]; then sig=$sigRight #D, d
elif [[ "[$key]" == "[]" ]]; then sig=$sigAllDown #空格键
elif [[ $key == "Q" || $key == "q" ]] #Q, q
then
MyExit
fi

if [[ $sig != 0 ]]
then
#向另一进程发送消息
kill -$sig $pidDisplayer
fi
done
}

这个函数是接收输入的进程的主程序,然后判断用户的输入,在这个脚本中,可以使用方向键和wasd来控制方块的运行轨迹,然后不断显示直到游戏结束.

里面调用了一个MyExit的函数.

接下来分析一下 RunAsDisplayer中BoxDown 这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function BoxDown()
{
local y s
((y = boxCurY + 1)) #新的y坐标
if BoxMove $y $boxCurX #测试是否可以下落一行
then
s="`DrawCurBox 0`" #将旧的方块抹去
((boxCurY = y))
s="$s`DrawCurBox 1`" #显示新的下落后方块
echo -ne $s
else
#走到这儿, 如果不能下落了
Box2Map #将当前移动中的方块贴到背景方块中
RandomBox #产生新的方块
fi
}

这个函数主要是定义了控制方块下落的功能,首先会判断是否可也下落,如果可以消除的话,先把可以消除的方块抹去,然后显示新的下落后的方块,如果不能下落了就将当前移动中的方块贴到背景方块中,然后继续产生新的方块.

里面调用了四个函数

BoxMove : 测试是否可以下落一行

DrawCurBox : 显示方块

Box2Map : 将当前移动中的方块贴到背景方块中

RandomBox : 产生新的方块

查看一下BoxMove这个函数,看看其中的功能是怎么实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function BoxMove()
{
local j i x y xTest yTest
yTest=$1
xTest=$2
for ((j = 0; j < 8; j += 2))
do
((i = j + 1))
((y = ${boxCur[$j]} + yTest))
((x = ${boxCur[$i]} + xTest))
if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
then
#撞到墙壁了
return 1
fi
if ((${iMap[y * iTrayWidth + x]} != -1 ))
then
#撞到其他已经存在的方块了
return 1
fi
done
return 0;
}

里面定义了方块移动的相关信息,通过判断方块的位置来确定是否可以进行移动

Shell 编程中的有很多特殊的变量可以打印出来很多有用的信息.在网上检索之后做个笔记.希望也可以帮助更多有需要的人

$$

Shell本身的PID(ProcessID)

$!

Shell最后运行的后台Process的PID

$?

最后运行的命令的结束代码(返回值)

$-

使用Set命令设定的Flag一览 $

S*

所有参数列表。如”$*”用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。

$@

所有参数列表。如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。

$#

添加到Shell的参数个数

$0

Shell本身的文件名 $1~$n 添加到Shell的各参数值。$1是第1参数、$2是第2参数…。


我们先写一个简单的脚本,执行以后再解释各个变量的意义

# touch variable

# vi variable

脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh 

echo "number:$#"

echo "scname:$0"

echo "first :$1"

echo "second:$2"

echo "argume:$@"

保存退出 赋予脚本执行权限

# chmod +x variable

执行脚本

# ./variable aa bb

执行结果

1
2
3
4
5
number:2 
scname:./variable
first: aa
second:bb
argume:aa bb

通过显示结果可以看到:

$# 是传给脚本的参数个数

$0 是脚本本身的名字

$1是传递给该shell脚本的第一个参数

$2是传递给该shell脚本的第二个参数

$@ 是传给脚本的所有参数的列表

高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的
declare [选项] 变量名
​ -r 声明或显示只读变量
​ -i 将变量定义为整型数
​ -a 将变量定义为数组
​ -A 将变量定义为关联数组
​ -f 显示已定义的所有函数名及其内容
​ -F 仅显示已定义的所有函数名
​ -x 声明或显示环境变量和函数
​ -l 声明变量为小写字母 declare –l var=UPPER
​ -u 声明变量为大写字母 declare –u var=lower

eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令.该命令适用于那些一次扫描无法实现其功能的变量.该命令对变量进行两次扫描
示例:

1
2
3
4
5
6
7
8
9
10
[root@server ~]# CMD=whoami
[root@server ~]# echo $CMD
whoami
[root@server ~]# eval $CMD
root
[root@server ~]# n=10
[root@server ~]# echo {0..$n}
{0..10}
[root@server ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10

间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为
​ variable1=variable2
​ variable2=value

bash Shell提供了两种格式实现间接变量引用
​ eval tempvar=$$variable1
​ tempvar=${!variable1}
示例:

1
2
3
4
5
6
7
8
[root@server ~]# N=NAME
[root@server ~]# NAME=mark
[root@server ~]# N1=${!N}
[root@server ~]# echo $N1
mark
[root@server ~]# eval N2=$$N
[root@server ~]# echo $N2
mark

创建临时文件

mktemp命令:创建并显示临时文件,可避免冲突
mktemp [OPTION]… [TEMPLATE]
​ TEMPLATE: filenameXXX
​ X至少要出现三个
OPTION:
​ -d: 创建临时目录
​ -p DIR或–tmpdir=DIR:指明临时文件所存放目录位置
示例:

1
2
3
4
5
mktemp /tmp/testXXX

tmpdir=`mktemp –d /tmp/testdirXXX`

mktemp --tmpdir=/testdir testXXXXXX

安装复制文件

install命令:
​ install [OPTION]… [-T] SOURCE DEST 单文件

1
2
3
4
5
install [OPTION]... SOURCE... DIRECTORY

install [OPTION]... -t DIRECTORY SOURCE...

install [OPTION]... -d DIRECTORY...创建空目录

选项:
​ -m MODE,默认755

1
2
3
-o OWNER

-g GROUP

示例:
​ install -m 700 -o mark -g admins srcfile desfile

1
install –m 770 –d /testdir/installdir

expect介绍

expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成.尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率

expect命令

expect 语法:
expect [选项][ -c cmds ] [ [ -[f|b] ] cmdfile ][ args ]
选项
​ -c:从命令行执行expect脚本,默认expect是交互地执行的
​ 示例:expect -c ‘expect “\n” {send “pressed enter\n”}
​ -d:可以输出输出调试信息
​ 示例:expect -d ssh.exp
expect中相关命令
​ spawn:启动新的进程
​ send:用于向进程发送字符串
​ expect:从进程接收字符串
​ interact:允许用户交互
​ exp_continue 匹配多个字符串在执行动作后加此命令

expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:
​ expect “hi” {send “You said hi\n”}
​ 匹配到hi后,会输出“you said hi”,并换行
多分支模式语法:

1
2
3
expect "hi" { send "You said hi\n" } \
"hehe" { send “Hehe yourself\n" } \
"bye" { send “Good bye\n" }

匹配hi,hello,bye任意字符串时,执行相应输出.等同如下:

1
2
3
4
5
expect {
"hi" { send "You said hi\n"}
"hehe" { send "Hehe yourself\n"}
"bye" { send “Good bye\n"}
}

expect例程

1
2
3
4
5
6
7
#!/usr/bin/expect
spawn scp /etc/fstab 192.168.8.100:/app
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send “mark\n" }
}
expect eof

例程:变量

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/expect
set ip 192.168.8.100
set user root
set password mark
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact

例程:位置参数

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
#./ssh3.exp 192.168.8.100 root mark

例程:执行多个命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo mark |passwd --stdin haha\n" }
send "exit\n"
expect eof
#./ssh4.exp 192.168.8.100 root mark

例程:shell脚本调用expect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }
expect "]#" { send "echo mark |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
#./ssh5.sh 192.168.8.100 root mark

信号捕捉trap

trap ‘触发指令’ 信号
​ 自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作
trap ‘’ 信号
​ 忽略信号的操作
trap ‘-‘ 信号
​ 恢复原信号的操作
trap -p
​ 列出自定义信号操作

trap例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
trap 'echo “signal:SIGINT"' int
trap -p
for((i=0;i<=10;i++))
do
sleep 1
echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i++))
do
sleep 1
echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i++))
do
sleep 1
echo $i
done

数组

变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
​ 索引:编号从0开始,属于数值索引
​ 注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
​ bash的数组支持稀疏格式(索引不连续)
声明数组:
​ declare -a ARRAY_NAME
​ declare -A ARRAY_NAME: 关联数组
​ 注意:两者不可相互转换

数组赋值

数组元素的赋值
​ (1) 一次只赋值一个元素
​ ARRAY_NAME[INDEX]=VALUE
​ weekdays[0]=”Sunday”
​ weekdays[4]=”Thursday”
​ (2) 一次赋值全部元素
​ ARRAY_NAME=(“VAL1” “VAL2” “VAL3” …)
​ (3) 只赋值特定元素
​ ARRAY_NAME=([0]=”VAL1” [3]=”VAL2” …)
​ (4) 交互式数组值对赋值
​ read -a ARRAY
显示所有数组:declare -a

引用数组

引用数组元素:

1
2
3
`${ARRAY_NAME[INDEX]}`

注意:省略[INDEX]表示引用下标为0的元素

引用数组所有元素:

1
2
3
`${ARRAY_NAME[\*]}`

`${ARRAY_NAME[@]}`

数组的长度(数组中元素的个数):

1
2
3
`${#ARRAY_NAME[\*]}`

`${#ARRAY_NAME[@]}`

删除数组中的某元素:导致稀疏格式

1
`unset ARRAY[INDEX]`

删除整个数组:
unset ARRAY

数组数据处理

引用数组中的元素:
​ 数组切片:

1
2
3
4
5
6
7
8
9
`${ARRAY[@]:offset:number}`

offset: 要跳过的元素个数

number: 要取出的元素个数

取偏移量之后的所有元素

`${ARRAY[@]:offset}`

向数组中追加元素:

1
`ARRAY[${#ARRAY[*]}]=value`

关联数组:
declare -A ARRAY_NAME

1
2
3
`ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)`

注意:关联数组必须先声明再调用

数组例程:

生成10个随机数保存于数组中,并找出其最大值和最小值

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[$i]} && max=${nums[$i]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo “All numbers are ${nums[*]}”
echo Max is $max
echo Min is $min

编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
fi
done
echo "Lines: $lines."

shell中的字符串

字符串切片

1
2
3
4
5
6
7
8
9
10
11
12
${#var}:返回字符串变量var的长度
${var:offset}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字
符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset:number}:返回字符串变量var中从第offset个字符后(不包括第offset个
字符)的字符开始,长度为number的部分
${var: -length}:取字符串的最右侧几个字符
注意:冒号后必须有一空白字符
${var:offset:-length}:从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字
符之前的内容
${var: -length:-offset}:先从最右侧向左取到length个字符开始,再向右取到距离最
右侧offset个字符之间的内容
注意:-length前空格

字符串处理

基于模式取子串
​ ${var#*word}:其中word可以是指定的任意字符

功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符

${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容

示例:

1
2
3
4
5
file=“var/log/messages”

${file#*/}: log/messages

${file##*/}: messages

${var%word\*}:其中word可以是指定的任意字符
​ 功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符之间的所有字符
file="/var/log/messages"
${file%/\*}: /var/log
${var%%word\*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;
示例:

1
2
3
url=http://www.google.com:80
${url##*:} 80
${url%%:*} http

查找替换
​ ${var/pattern/substr}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
​ ${var//pattern/substr}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
​ ${var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
​ ${var/%pattern/substr}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之

查找并删除
​ ${var/pattern}:删除var表示的字符串中第一次被pattern匹配到的字符串
​ ${var//pattern}:删除var表示的字符串中所有被pattern匹配到的字符串
​ ${var/#pattern}:删除var表示的字符串中所有以pattern为行首匹配到的字符串
​ ${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串

字符大小写转换
​ ${var^^}:把var中的所有小写字母转换为大写
​ ${var,,}:把var中的所有大写字母转换为小写

变量赋值

shell中的函数

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于:
Shell程序在子Shell中运行
而Shell函数在当前Shell中运行.因此在当前Shell中,函数可以对shell中变量进行修改

定义函数

函数由两部分组成:函数名和函数体
help function
语法一:

1
2
3
f_name (){
...函数体...
}

语法二:

1
2
3
function f_name {
...函数体...
}

语法三:

1
2
3
function f_name () {
...函数体...
}

函数使用

函数的定义和使用:
​ 可在交互式环境下定义函数
​ 可将函数放在脚本文件中作为它的一部分
​ 可放在只包含函数的单独文件中
调用:函数只有被调用才会执行
​ 调用:给定函数名
​ 函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止

函数返回值

函数有两种返回值:
函数的执行结果返回值:
​ (1) 使用echo等命令进行输出
​ (2) 函数体中调用命令的输出结果
函数的退出状态码:
​ (1) 默认取决于函数中执行的最后一条命令的退出状态码
​ (2) 自定义退出状态码,其格式为:
​ return 从函数中返回,用最后状态命令决定返回值
​ return 0 无错误返回.
​ return 1-255 有错误返回

交互式环境下定义和使用函数

示例:

1
2
3
dir() {
> ls -l
> }

定义该函数后,若在$后面键入dir,其显示结果同ls -l的作用相同
$ dir

该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令
unset dir

在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它
后才能使用
调用函数仅使用其函数名即可
示例:

1
2
3
4
5
6
7
8
9
10
cat func1
#!/bin/bash
# func1
hello()
{
echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello"
hello
echo “back from the function”

使用函数文件

可以将经常使用的函数存入函数文件,然后将函数文件载入shell
文件名可任意选取,但最好与相关任务有某种联系.例如:functions.main
一旦函数文件载入shell,就可以在命令行或脚本中调用函数.可以使用set命
令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数.改动完毕后,再重新载
入此文件

创建函数文件

函数文件示例:

1
2
3
4
5
6
7
8
9
10
11
cat functions.main
#!/bin/bash
#functions.main
findit()
{
if [ $# -lt 1 ] ; then
echo "Usage:findit file"
return 1
fi
find / -name $1 –print
}

载入函数

函数文件已创建好后,要将它载入shell
定位函数文件并载入shell的格式
. filenamesource filename
注意:此即<点> <空格> <文件名>这里的文件名要带正确路径
示例:
​ 上例中的函数,可使用如下命令:
. functions.main

检查载入函数

使用set命令检查函数是否已载入.set命令将在shell中显示所有的载入函数
示例:

1
2
3
4
5
6
7
8
9
10
set
findit=( )
{
if [ $# -lt 1 ]; then
echo "usage :findit file";
return 1
fi
find / -name $1 -print
}

执行shell函数

要执行函数,简单地键入函数名即可
示例:

1
2
3
findit groups
/usr/bin/groups
/usr/local/backups/groups.bak

删除shell函数

现在对函数做一些改动后,需要先删除函数,使其对shell不可用.使用unset命
令完成删除函数
命令格式为:
unset function_name
示例:
unset findit
​ 再键入set命令,函数将不再显示
环境函数
使子进程也可使用
声明:export -f function_name
查看:export -f 或 declare -xf

函数参数

函数可以接受参数:
​ 传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfunc arg1 arg2 …”
​ 在函数体中当中,可使用$1, $2, …调用这些参数;还可以使用$@, $*, $#等特殊变量

函数变量

变量作用域:
​ 环境变量:当前shell和子shell有效
​ 本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
​ 局部变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有局部变量,如果其名称同本地变量,使用局部变量
在函数中定义局部变量的方法
local NAME=VALUE

函数递归示例

函数递归:
​ 函数直接或间接调用自身
​ 注意递归层数
递归实例:
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!
​ n!=1×2×3×…×n
​ 阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
​ n!=n(n-1)(n-2)…1
​ n(n-1)! = n(n-1)(n-2)!

例程:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
#
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1

fork炸弹

fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序.由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
函数实现
:(){ :|:& };:

bomb() { bomb | bomb & }; bomb

脚本实现

1
2
3
cat Bomb.sh
#!/bin/bash
./$0|./$0&

Shell 脚本也是学习Linux中必须要掌握的基础之一,掌握了 Shell 脚本之后可以轻松控制自动化及重复的任务,大大减少重复的劳动.

流程控制

过程式编程语言:

顺序执行 

​ 选择执行

循环执行 

条件选择 if 语句

选择执行:

注意:if语句可嵌套

单分支

if 判断条件;then 

    条件为真的分支代码 

fi 

双分支

if 判断条件; then 

    条件为真的分支代码 

else 

    条件为假的分支代码 

fi 
if语句

多分支

if 判断条件1; then 

    条件1为真的分支代码 

elif 判断条件2; then 

    条件2为真的分支代码 

​ elif 判断条件3; then

    条件3为真的分支代码 

else 

    以上条件都为假的分支代码 

fi 

逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句

if示例

根据命令的退出状态来执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ping -c1 -W2 station1 &> /dev/null; then 

echo 'Station1 is UP'

elif grep "station1" ~/maintenance.txt &> /dev/null; then

echo 'Station1 is undergoing maintenance‘

else

echo 'Station1 is unexpectedly DOWN!'

exit 1

fi

条件判断 case 语句

case 变量引用 in

PAT1)

分支1 

;; 

PAT2)

分支2 

;; 

*)

默认分支 

;; 

esac

case支持glob风格的通配符:

*: 任意长度任意字符 

​ ?: 任意单个字符

[]:指定范围内的任意单个字符 

a|b: a或b 

循环

循环执行

将某代码段重复运行多次 

重复运行多少次 

    循环次数事先已知 

    循环次数事先未知 

有进入条件和退出条件 

for, while, until

for循环

for 变量名 in 列表;do

循环体 

done

执行机制:

依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直 到列表中的元素耗尽,循环结束 

列表生成方式:

(1) 直接给出列表 

(2) 整数列表: 

    (a) {start..end} 

    (b) $(seq [start [step]] end) 

(3) 返回列表的命令 

    $(COMMAND) 

(4) 使用glob,如:*.sh

​ (5) 变量引用;

​ $@, $*

while循环

while CONDITION; do
​ 循环体
done
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环
因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为true
退出条件:CONDITION为false

until循环

until CONDITION; do
​ 循环体
done
进入条件: CONDITION 为false
退出条件: CONDITION 为true

循环控制语句continue

用于循环体中
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层

1
2
3
4
5
6
7
8
9
while CONDTIITON1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done
循环控制语句break

用于循环体中
break [N]:提前结束第N层循环,最内层为第1层

1
2
3
4
5
6
7
8
9
while CONDTIITON1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done
循环控制shift命令

shift [n]
用于将参量列表 list 左移指定次数,缺省为左移一次.
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除.while 循环遍
历位置参量列表时,常用到 shift
./doit.sh a b c d e f g h

./shfit.sh a b c d e f g h

命令示例

doit.sh

1
2
3
4
5
6
7
8
9
#!/bin/bash
# Name: doit.sh
# Purpose: shift through command line arguments
# Usage: doit.sh [args]
while [ $# -gt 0 ] # or (( $# > 0 ))
do
echo $*
shift
done

shift.sh

1
2
3
4
5
6
7
8
#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
echo "$1"
shift
done
echo
创建无限循环
1
2
3
while true; do
循环体
done
1
2
3
until false; do
循环体
Done
循环的特殊用法

while循环的特殊用法(遍历文件的每一行):

1
2
3
while read line; do
循环体
done < /PATH/FROM/SOMEFILE

依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

双小括号方法,即((…))格式,也可以用于算术运算
双小括号方法也可以使bash Shell实现C语言风格的变量操作
I=10
((I++))

for循环的特殊格式:

1
2
3
4
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done

控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

select循环与菜单
1
2
3
4
select variable in list
do
循环体命令
done

select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在
标准错误上,并显示 PS3 提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入被保存在内置变量 REPLY 中

select与case

select 是个无限循环,因此要记住用 break 命令退出循环,或用
exit 命令终止脚本.也可以按 ctrl+c 退出循环
select 经常和 case 联合使用
与 for 循环类似,可以省略 in list,此时使用位置参量