总字符数: 56.14K

代码: 42.29K, 文本: 7.16K

预计阅读时间: 3.58 小时

Shell基础

Shell的简介

Shell的本意是“壳”的意思,其实已经很形象地说明了shell在Linux系统中的作用。Shell就是围绕在Linux内核之外的一个“壳”程序,用户在操作系统上完成的所有任务都是通过shell与Linux系统内核的交互来实现的 。

所以也可以认为Shell是用户和Linux操作系统之间的接口。Linux中有多种shell,其中缺省使用的是bash 。

Shell最重要的功能是命令解释,从这种意义上说,Shell是一个命令解释器。Linux系统中的所有可执行文件都可以作为Shell命令来执行。Linux系统上可执行文件的分类见下表。

description:Shell是如何完成命令解释的
description:Shell是如何完成命令解释的

当用户提交了一个命令后,Shell首先判断它是否为内置命令.

如果是就通过Shell内部的解释器将其解释为系统功能调用并转交给内核执行;

若是外部命令或实用程序就试图在硬盘中查找该命令并将其调入内存,再将其解释为系统功能调用并转交给内核执行。

用户登录系统后,如果登录字符界面,将出现shell命令提示符。

  • “#”表示登录的用户是系统超级用户
  • “$”表示登录到系统的是普通用户

​ Shell还是强大的解释行程序设计语言,它定义了各种选项和变量,几乎支持高级程序语言的所有结构,如变量、函数、表达式和循环等。

​ 利用shell可以编写shell脚本程序,类似于Windows/DOS下的批处理文件,但是shell功能更加完善,更加强大。

归纳

​ Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。

​ Shell还是一个功能相当强大的编程语言,易编写,易调试,灵活性较强。Shell是解释执行的脚本语言,在Shell中可以直接调用Linux系统命令。

​ Bash是作为用户的基本Shell(默认)。

命令学习

echo输出命令

echo [选项] [输出内容]
选项:-e 支持反斜线控制的字符转换

echo中间有空格需要加双引号,没有的话可以直接写,!在Linux中有特殊作用,如果非要加使用单引号

1
2
3
4
echo 123456
echo 'hello'
echo "hello world"
echo 'hello world!'
控制字符
控制字符 作用
\ 输出\本身
\a 输出警告音
\b 退格键,也就是向左删除键
\c 取消输出行末的换行符。和“-n”选项一致
\e ESCAPE键
\f 换页符
\n 换行符
\r 回车键
\t 制表符,也就是tab键
\v 垂直制表符
\0nnn 按照八进制ASCII码表输出字符。其中0为数字0,nnn为三位八进制数
\xhh 按照十六进制ASCII码表输出字符。其中hh是两位十六进制数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
echo -e "ab\bc"
# -e 使能解释转义字符。
# ab\bc 中的 \b 是一个退格符,它删除紧接在其前面的一个字符。
# 输出结果为 ac,因为 \b 删除了 b 左侧的 a。

echo -e "a\tb\tc\nd\te\tf"

# \t 表示一个制表符,用来在文本中添加水平空格。
# \n 表示换行符,用来开始一个新行。
# 输出结果是两行:第一行包含 a, b, c,每两个字符之间由制表符分隔;第二行包含 d, e, f,同样以制表符分隔。

echo -e "\x61\t\x62\t\x63\n\x64\t\x65\t\x66"

# \xHH 允许使用十六进制值 (HH) 来表示字符,其中 \x61 是 a,\x62 是 b,以此类推。
# 输出结果与前一个例子相同:两行 a b c 和 d e f,每对字符之间由制表符分隔。

echo -e "\e[1;31m abcd\e[0m"

# \e[1;31m 是一个ANSI转义序列,用于设置文本颜色。这里,\e[1;31m 设置文本为高亮显示的红色。
# \e[0m 用于重置文本格式,让之后的文本恢复默认格式。
# 输出结果是 abcd 以高亮的红色文本显示。

date时间命令

这个命令在shell脚本中使用很频繁,最常见的几个用法如下:

  • date +%Y :表示以四位数字格式打印年份
  • date +%y :表示以两位数字格式打印年份
  • date +%m :表示月份
  • date +%d :表示日期
  • date +%H :表示小时
  • date +%M :表示分钟
  • date +%S :表示秒
  • date +%w :表示星期,0~6,0表示星期天

参数说明

  • -d datestr : 显示 datestr 中所设定的时间 (非系统时间)
  • –help : 显示辅助讯息
  • -s datestr : 将系统时间设为 datestr 中所设定的时间
  • -u : 显示目前的格林威治时间
  • –version : 显示版本编号
1
2
3
4
5
6
7
8
[root@localhost ~]# date +"%Y-%m-%d %H:%M:%S"
2022-05-09 12:48:29
[root@localhost ~]# date -d "-1 day" +%d # -d设定时间,-1 day 表示当前 日期前一天,可以以此类推
08
[root@localhost ~]# date -d "-1 hour" +%H #-1 hour 表示当前时间前1小时 可以以此类推
11
[root@localhost ~]# date -d "-1 min" +%M #-1 min 表示当前时间前1分钟可 以以此类推
57

脚本执行方式

Shell脚本通常都以.sh作为后缀名,不是说不加.sh的脚本不能运行,只是大家的习惯,这样也方便辨识。

创建脚本

1
2
3
4
5
6
7
# 不是注释,是一个标志,标称写的内容为Bash,Shell脚本
#!/bin/bash
#The first program #一定要写完整的注释
#Author:kali #一定要有良好的注释
date
echo -e "Hello World!"
echo -e '\e[1;31mHello World!\e[0m'

上面,第一行以 #!/bin/bash 开头,表示该文件使用的是bash语法,不设置该行也可以执行,但不符合规范。

#表示注释,后面跟一些该脚本的相关注释内容,以及作者、创建日期或版本等。

注释可以省略,但不建议省略,因为我们的记忆力有限,没有注释我们自己都不知自己写的脚本是用来干什么的、是什么时候写的。建议从一开始就要养成写脚本的良好习惯,方便自己也方便他人。

执行脚本

1
2
3
4
5
6
7
8
9
#赋予执行权限
chmod 755 first.sh
chmod +x first.sh
#然后可以通过sh执行first.sh
#使用sh脚本命令执行脚本时,可以加 –x 选项来查看脚本的执行过程
sh first.sh
sh -x first.sh
bash first.sh
./ first.sh

Shell基本功能

Shell 元字符

在Shell中有一些具有特殊的意义字符,称为 Shell元字符(shell metacharacters)。若不以特殊方式指明,Shell并不会把它们当做普通文字符使用。

下表简单介绍了常用的Shell元字符的意义:

历史命令和命令补全

1
2
3
4
5
6
[root@localhost ~]#history [选项] [历史命令保存文件]
选项:
-c: 清空历史命令
-w: 把缓存中的历史命令写入历史命令保存文件
~/.bash_history #历史命令保存器
历史命令默认会保存1000条可以在环境变量配置文件/etc/profile中进行修改

历史命令的调用

  • 使用上、下箭头调用以前的历史命令
  • 使用!n重复执行第n条历史命令
  • 使用!!重复执行上一条命令
  • 使用!字串重复执行最后一条以该字串开头的命令

命令和文件补全

在Bash中,命令与文件补全是非常方便与常用的功能,我们只要在输入命令或文件时,按Tab键就会自动进行补全

命令别名与常用快捷键

1
2
[root@localhost ~]#alias 别名='原命令'   #设定命令别名
[root@localhost ~]#alias #查询命令别名

让别名永久有效

1
[root@localhost ~]#vi /root/.bashrc 

删除别名

1
[root@localhost ~]#unalias 别名 

命令执行时顺序

  1. 第一顺位执行用绝对路径或相对路径执行的命令
  2. 第二顺位执行别名
  3. 第三顺位执行Bash的内部命令
  4. 第四顺位执行按照$PATH环境变量定义的目录查找顺序找到的第一个命令

多命令顺序执行

多命令执行符 格式 作用
; 命令1;命令2 多个命令顺序执行,命令之间没有任何逻辑联系
&& 命令1&&命令2 逻辑与 当命令1正确执行,则命令2才会执行 当命令1执行不正确,则命令2不会执行
` `
1
[root@localhost ~]#ls /root;ls /root/kali
1
2
3
4
5
6
7
8
9
10
11
12
13
#dd命令是Linux磁盘复制或数据复制,能复制特殊命令,特殊文件,也能复制分区,甚至整个硬盘,可以当做磁盘对拷对待
[root@localhost ~]#dd if=输入文件 of=输出文件 bs=字节数 count=个数
选项:
if=输入文件 指定源文件或源设备
of=输出文件 指定目标文件或目标设备
bs=字节数 指定一次输入/输出多少字节,即把这些字节看做一个数据块
count=个数 指定输入/输出多少个数据块
[root@localhost ~]# date;dd if=/dev/zero of=/root/testfile bs=1k count=100000;date #创建100mb文件需要多长时间
[root@localhost ~]# ls && echo yes #第一条命令可以执行,输出yes
[root@localhost ~]# ls gdafa && echo yes #第一条命令出现错误,就不会输出
[root@localhost ~]# ls || echo no #当执行第一条命令,就不会输出
[root@localhost ~]# ls /kali && echo yes || echo no #当ls执行报错,第2个命令不执行,执行no
[root@localhost ~]# ls ./kali && echo yes || echo no #当ls执行第2个命令,不执行no

Bash常用快捷键

Shell的重定向

Linux下系统打开3个文件,即标准输入、标准输出和标准错误输出。

用户的shell将键盘设为默认的标准输入,默认的标准输出和标准错误输出为屏幕。

也就是,用户从键盘输入命令,然后将结果和错误消息输出到屏幕。

所谓的重定向,就是不使用系统默认的标准输入/输出,而是重新指定,因此重定向分为输入重定向、输出重定向和错误输出重定向要实现重定向就需要了解重定向操作符,shell就是根据重定向操作符来决定重定向操作的。

输入重定向

1
2
3
4
5
[root@localhost ~]#wc [选项] [文件名]
选项:
-c 统计字节数
-w 统计单词数
-l 统计行数

输入重定向用于改变命令的输入源,利用输入重定向,就可以将一个文件的内容作为命令的输入,而不从键盘输入。(CTRL+D)

用于输入重定向的操作符有<<<。例如:

1
2
[root@localhost ~]#wc </etc/shadow
#这里用wc命令统计输入给它的文件/etc/inittab的行数、单词数和字符数。

输出重定向

输出重定向不是将命令的输出结果在屏幕上输出,而是输出到一个指定文件中。

在Linux下输出重定向用得很多。例如,某个命令的输出很长,一个屏幕无法显示完毕,这时可以将命令的输出指定到一个文件,然后用more命令查看这个文件,从而得到命令输出的完整信息。

类型 符号 作用
标准输出重定向 命令 >文件 命令 >>文件 以覆盖的方式,把命令的正确输出输出到指定的文件或设备当中 以追加的方式,把命令的正确输出输出到指定的文件或设备当中
标准错误输出重定向 错误命令 2>文件 错误命令 2>>文件 以覆盖的方式,把命令的错误输出输出到指定的文件或设备当中 以追加的方式,把命令的错误输出输出到指定的文件或设备当中

用于输出重定向的操作符有>>>。例如:

1
2
3
4
5
6
7
ps -ef >ps.txt
#这条命令将ps -ef输出的系统运行进程信息全部输入到了ps.txt文件,而不是输出到屏幕,可以用more命令查看ps.txt文件中系统运行的进程信息。
more file1 file2 file3 >file
#其中,more命令用于查看文件的内容,上面的命令是将file1/file2/file3的内容全部输出到file文件中,类似于文件内容的合并。
如果在“>”后面指定的文件不存在,shell就会自动重建一个;如果文件存在,那么这个文件原有的内容将被覆盖;如果不想覆盖存在的文件,可以使用“>>”操作符。例如
ls -al /etc/* >>/root/install.log
#这条命令 w将/etc目录及其子目录下的所有文件信息追加到/root/install.log文件的后面,/root/install.log文件原来的内容仍然存在。

错误重定向

实际工作中,正确输出和错误输出同时保存

错误重定向和标准输出重定向一样,可以使用操作符2>2>>实现对错误输出的重定向

命令 > 文件 2>&1 以覆盖的方式,把正确输出和错误输出都保存到同一个文件当中
命令 >> 文件 2>&1 以追加的方式,把正确输出和错误输出都保存到同一个文件当中
命令 &>文件 以覆盖的方式,把正确输出和错误输出都保存到同一个文件当中
命令 &>>文件 以追加的方式,把正确输出和错误输出都保存到同一个文件当中
命令 >>文件1 2>>文件2 把正确的输出追加到文件1中,把错误的输出追加到文件2中
1
2
3
[root@localhost ~]# tar zxvf text.tar.gz 2>error.txt
#其中,tar是打包命令,可以在屏幕上看到tar的解压过程。如果“text.tar.gz”是个损坏的压缩包,就会把错误信息输出到error.txt文件。
[root@localhost ~]# ls &>/dev/null #不保存任何错误,执行就行

Shell的管道

管道可以把很多命令连接起来,可以把第1个命令的输出当作第2个命令的输入,第2个命令的输出当作第3个命令的输入,依次类推。因此,管道的作用就是把一个命令的输出当作下一个命令的输入,而不经过任何中间文件。

通过管道符“|”可以建立管道连接

示例
1
2
3
4
5
[root@localhost ~]# ls  -al /etc/*  |more
#这条命令表示将/etc目录以及子目录下的所有文件分屏显示。
[root@localhost ~]# ps -ef| grep httpd|wc -l
#这个命令用户查看系统中正在运行的httpd进程,并统计httpd的进程数。
[root@localhost ~]# pstree|grep bash

Shell的通配符

通配符主要是为了方便用户对文件或目录的描述,例如,当用户仅仅需要以.sh结尾的文件时,使用通配符就能很方便地实现。各个版本的shell都有通配符,这些通配符是一些特殊字符,用户可以在命令行的参数中使用这些字符,进行文件或者路径名的匹配。Shell将把与命令行中指定的匹配规则符合的所有文件名或者路径作为命令的参数,然后执行这个命令。

Bash中常用的通配符有* ? []

*匹配任意一个或多个字符

1
2
3
4
5
6
7
8
[root@localhost ~]# ls  *.txt
# 这条命令列出当前目录中所有以“.txt”结尾的文件(除去以“.”开头的文件)。

[root@localhost ~]# cp doc/* /opt
# 这条命令表示将doc目录下的所有文件(除去以“.”开头的文件)复制到/opt目录下。

[root@localhost ~]# ls -al /etc/*/*.conf
# 这条命令列出/etc目录的子目录下所有以“.conf”结尾的文件。在/etc目录下以“.conf”结尾的文件不会列出

?匹配任意单一字符

1
2
3
4
5
[root@localhost ~]# ls  ab?.txt
# 这条命令列出当前目录下以ab开头,随后一个字母是任意字符,接着以“.txt”结尾的文件。

[root@localhost ~]# ls ab??.txt
# 这条命令列出当前目录下以ab开头,随后两个字母是任意字符,接着以“.txt”结尾的文件。

[]匹配任何包含在方括号内的单子符

  • [] 匹配中括号中任意一个字符。例如[abc]代表一定匹配一个字符,或者是a,或者是b,或者是c
  • [-] 匹配中括号中任意一个字符,-代表一个范围。例如:[a-z]代表匹配一个小写字母
  • [^] 逻辑非,表示匹配不是中括号内的一个字符。例如:[^0-9]代表匹配一个不是数字的字符
1
2
3
4
5
6
7
[root@localhost ~]# ls /dev/sda[12345]
/dev/sda1 /dev/sda2 /dev/sda3 /dev/sda4 /dev/sda5
# 上面的命令列出了在/dev目录下以sda开头,第4个字符是1/2/3/4/5的所有文件。
[root@localhost kali]# ls [Yy]*
YASUO yasuser yasuser.bz2 yasuser.zip
[root@localhost ~]# ls /dev/sda[1-5]
# 在方括号“1-5”给出了匹配的范围,与上面一条命令完全等效。

通配符的组合使用

1
2
3
4
5
[root@localhost ~]# ls [0-9]?.conf
# 这条命令列出当前目录下以数字开头,随后一个是任意字符,接着以“.conf”结尾的所有文件。

[root@localhost ~]# ls [xyz]*.txt
# 这条命令列出当前目录下以x/y/z开头,最后以“.txt”结尾的文件。

Shell中其他特殊符号

在bash中有很多特殊字符,这些字符本身就具有特殊含义。如果在shell的参数中使用它们,就会出现问题。Linux中使用了“引用”技术来忽略这些字符的特殊含义,引用技术就是通知shell将这些特殊字符当作普通字符处理。Shell中用于引用的字符有转义字符\、单引号''、双引号""

转义字符

如果将\放到特殊字符前面,shell就会忽略这些特殊字符的原有含义,把它们当作普通字符对待.

示例
1
2
3
4
5
[root@localhost ~]# ls
[root@localhost ~]# mv abc\?\* abc
[root@localhost ~]# mv C\:\\backup backup
# C:\backup
# 上面是将abc?*重名名为abc,将C:\backup重命名为backup。因为文件名中包含特殊字符,所以都使用了转义字符“\”。

单引号

如果将字符串放到一对单引号之间,那么字符串中所有字符的特殊含义将被忽略.

示例
1
2
3
[root@localhost ~]# mv C\:\\backup backup
[root@localhost ~]# mv 'C:\backup' backup
#上面两条命令完全等效。

双引号

双引号的引用与单引号基本相同,包含在双引号内的大部分特殊字符可以当作普通字符处理,但是仍有一些特殊字符即使用双引号括起来,也仍然保留自己的特殊含义,比如$\,

1
2
3
4
5
6
7
[root@localhost ~]# str="The \$SHELL Current shell is $SHELL"
[root@localhost ~]# str1="\$$SHELL"
[root@localhost ~]# echo $str
The $SHELL Current shell is /bin/bash
[root@localhost ~]# echo $str1
$/bin/bash
# 从上面的输出可以看出,“$”和“\”在双引号内仍然保留了特殊含义。
1
2
3
4
[root@localhost ~]# str="This hostname is 'hostname'"
[root@localhost ~]# echo $str
This hostname is 'hostname'
# 上面的输出中,字符“’”在双引号中也保留了自己的特殊含义。
1
2
3
4
5
6
7
8
9
[root@localhost ~]# name=kali # 声明变量
[root@localhost ~]# echo $name # 输出变量
kali
[root@localhost ~]# echo '$name' # 输出内容
$name
[root@localhost ~]# echo "$name" # 输出变量
kali
[root@localhost ~]# echo $(date) # 传输变量时间
2021年 12月 24日 星期五 22:08:23 CS

Bash变量

什么是变量

变量是计算机内存的单元,其中存放的值可以改变。当Shell脚本需要保存一些信息时,如一个文件名或是一个数字,就把它存放在一个变量中。每个变量有一个名字,所以很容易应用它。使用变量可以保存有用信息,使系统获知用户相关设置,变量也可以用于保存暂时信息。

变量设置规则

  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头。如果变量名是2name则是错误的。
  • 在Bash中,变量的默认类型都是字符型,如果要进行数值运算,则必须指定变量类型为数值型。
  • 变量用等号连接值,等号左右两侧不能有空格。
  • 变量的值如果有空格,需要使用单引号或双引号包括。
  • 在变量的值中,可以使用\转义符。
  • 如果需要增加变量的值,那么可以进行变量值的叠加。不过变量需要用双引号包含$变量名或用${变量名}包含。
  • 如果是把命令的结果作为变量值赋予变量,则需要使用反引号或$()包含命令。
  • 环境变量名建议大写,便于区分。

变量分类

  • 用户自定义变量(最常用的)
  • 环境变量:这种变量中主要保存的是和系统操作环境相关的数据
  • 位置参数变量:这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的
  • 预定义变量:是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的。

用户自定义变量(本地变量)

1
2
[root@localhost ~]# name="kali"    
# 变量定义,有空格用双引号或单引号括起来

变量叠加

1
2
3
4
5
6
7
8
[root@localhost ~]# aa=123
# 变量叠加的一种方法
[root@localhost ~]# aa="$aa"456
# 变量叠加的一种方法
[root@localhost ~]# aa=${aa}789
# 输出变量
[root@localhost ~]# echo $aa
123456789

变量调用

在脚本中应用变量时需要加上符号$

1
2
# 调用变量  
[root@localhost ~]# echo $name

变量查看

1
2
3
# 查看系统的所有变量
[root@localhost ~]# set
[root@localhost ~]# set | grep "name"

变量删除

1
2
# 删除变量 
[root@localhost ~]# unset $aa
其他示例
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
# 写入下面的内容
vim variable.sh
#! /bin/bash
## In this script we will user variable.
## Writen by kali 2022-09-15

d=`date +%H:%M:%S`
echo "The script begin at $d."
echo "NOW We'll sleep 5 seconds."
sleep 5
d1=`date +%H:%M:%S`
echo "The script end at $d1."


# 数字运算
# 写入下面内容
vim sum.sh

#! /bin/bash
## For get the sum of two number.
## Writen by kali 2022-09-15
a=1
b=2
sum=$[$a+$b]
echo "$a+$b=$sum"
# 数字计算要用[]括起来,并且前面要加上符号$

# 和用户交互
vim read.sh
#! /bin/bash
## Using 'read' in shell script.
## Writen by kali 2022-09-15
read -p "Please input a number:" x
read -p "Please input a number:" y
sum=$[$x+$y]
echo "the sum of two numbers is :$sum"

环境变量

环境变量是什么

用户自定义变量只在当前的Shell中生效,而环境变量会在当前Shell和这个Shell的所有子Shell当中生效。如果把环境变量写入相应的配置文件,那么这个环境变量就会在所有Shell中生效。

设置环境变量

1
2
3
export 变量名=变量值   # 申明全局变量
env # 专门查询环境变量 重点看PATH
unset 变量名 # 删除变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 有一个概念需要了解父shell和子shell
# 进入一个子shell
[root@localhost ~]# bash
# 查看树形结构进程,可以查看子shell
[root@localhost ~]# pstree
# 退出子shell
[root@localhost ~]# exit
# 本地变量
[root@localhost ~]# name=kali
# 全局变量
[root@localhost ~]# export age=18
# 本地变量
[root@localhost ~]# sex=nan
# 定义全局变量
[root@localhost ~]# export sex
# 查看变量
[root@localhost ~]# set |grep sex
# 进入子shell
[root@localhost ~]# bash
[root@localhost ~]# set

系统常见环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# PATH:系统查找命令的路径
# 环境变量
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost sh]# cp ./hello.sh /root/bin
[root@localhost ~]# hello.sh
Hello World!

# PATH变量叠加
PAHT="$PATH":/root/sh
[root@localhost ~]# PATH="$PATH":/root/sh
[root@localhost ~]# hello.sh
Hello World!
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/sh

PS1:定义系统提示符的变量

转义序列 描述
\d 显示日期,格式为“星期 月 日”
\h 显示简写主机名。如默认主机名“localhost”
\t 显示24小时制时间,格式为“HH:MM:SS”
\T 显示12小时制时间,格式为“HH:MM:SS”
\A 显示24小时制时间,格式为“HH:MM”
\u 显示当前用户名
\w 显示当前所在目录的完整名称
\W 显示当前所在目录的最后一个目录
\# 显示这是当前会话中执行的第几个命令
\$ 显示提示符。如果是root用户会显示“#”,如果是普通用户会显示“$”
1
2
3
4
5
6
7
8
[root@localhost ~]# echo $PS1
[\u@\h \W]\$
[root@localhost ~]# PS1='[\u@\t\w]\$ '
[root@10:56:03~]# cd /usr/local/src
[root@10:56:49/usr/local/src]# PS1='[\u@\@ \h \#\W]\$ '
[root@10:57 上午 localhost 31src]# PS1='[\u@\h \W]\$ '

# 临时的修改,重启或者重新登录就修改回去了

环境变量配置文件

source命令
1
2
3
4
5
6
7
8
9
10
# 在Linux中,source 命令或者点命令(.)被用来读取并执行指定文件中的命令。
# 通常这类文件包含了环境变量的赋值。
# 使用 source 或者点命令执行文件后,文件中定义的环境变量将会在当前会话中即时生效,而无需重新启动或退出当前会话。
# 这通常用于修改环境变量配置文件,如 ~/.bashrc、~/.profile 或者是其他自定义的脚本文件。

[root@localhost ~]# source /path/to/configuration_file

# 或者

[root@localhost ~]# . /path/to/configuration_file
环境变量配置文件简介

环境变量配置文件中主要是定义对系统的操作环境生效的系统默认环境变量,比如PATH(环境变量)/HISTSIZE(历史命令)/PSI(提示符)/HOSTNAME(系统名)等默认环境变量

环境变量主要有:

1
2
3
4
5
/etc/profile   
/etc/profile.d/*.sh
~/.bash_profile
~/.bashrc
/etc/bashrc

etc中对所有用户都生效,~只对家目录生效

环境变量配置文件作用
description:配置文件的优先级
description:配置文件的优先级
/etc/profile的作用
1
2
3
4
5
6
7
8
9
USER变量:
LOGNAME变量:
MAIL变量:
PATH变量:
HOSTNAME变量:
HISTSIZE变量:
umask

# 注意:写在后面的环境变量会覆盖前面的变量,除非使用变量叠加
其他配置文件和登录信息
注销时生效的环境变量配置文件
1
~/.bash_logout   #可以把命令写到这个文件中,系统在注销的时候就会执行这些命令
其他配置文件
1
~/.bash_history   #历史命令,排错使用
Shell登录信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
本地终端欢迎信息:/etc/issue
转义符 作用
\d 显示当前系统日期
\s 显示操作系统名称
\l 显示登录的终端号,这个比较常用
\m 显示硬件体系结构,如i386,i686等
\n 显示主机名
\o 显示域名
\r 显示内核版本
\t 显示当前系统时间
\u 显示当前登录用户的序列号

远程终端欢迎信息:/etc/issue.net(不认上面的转义符)
转义符在/etc/issue.net文件中不能使用
是否显示此欢迎信息,由ssh的配置文件/etc/ssh/sshd_config决定,加入“Banner /etc/issue.net”行才能显示(记得重启ssh服务)

重启服务:service sshd restart #重启sshd服务

登陆后欢迎信息:/etc/motd
#不管是本地登陆,还是远程登陆,都可以显示此欢迎信息

位置参数变量

位置参数变量 作用
$n n为数字,$0代表命令本身,$1-$9代表第一到第九个参数,十以上的参数需要用大括号包含,如${10}
$* 这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体
$@ 这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待
$# 这个变量代表命令行中所有参数的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@192 ~]# cd sh
[root@192 sh]# ls
hello.sh h.sh square.sh
[root@192 sh]# vi parameters.sh
#!/bin/bash
echo $0
echo $1
echo $2
echo $3
# 修改脚本增加执行权限
[root@192 sh]# chmod +x parameters.sh
[root@192 sh]# sh parameters.sh
parameters.sh
[root@192 sh]# sh parameters.sh 123 123 456
parameters.sh
123
123
456

关于环境变量的脚本

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
# 示例1(求和):sum.sh

#!/bin/bash
# author:kali
num1=$1
num2=$2
sum=$(($num1+$num2))
# 变量sum的和是num1加num2
echo $sum
# 打印变量sum的值

# 另一种写法
#!/bin/bash
sum=$(($1+$2))
echo "sum is :$sum"
[root@192 sh]# ./sum.sh 33 45
78

# 示例2:
#!/bin/bash
echo "A total of $# parameters" #使用$#代表所有参数的个数
echo "The parameter is: $*" #使用$*代表所有的参数
echo "The parameter is: $@" #使用$@也代表所有参数

# 实际我按下面的写
[root@192 sh]# vi parameters1.sh
#!/bin/bash
echo $#
echo $*
echo $@
[root@192 sh]# parameters1.sh
-bash: /root/sh/parameters1.sh: 权限不够
[root@192 sh]# chmod 755 parameters1.sh
[root@192 sh]# parameters1.sh
0
[root@192 sh]# parameters1.sh 11 22 33 44 55 66 #查看参数有几个
6
11 22 33 44 55 66
11 22 33 44 55 66

# 示例3:$*与$@的区别
[root@192 sh]# vi parameters2.sh
[root@192 sh]# chmod 755 parameters2.sh
[root@192 sh]# parameters2.sh 1 2 3 4
The parameters is : 1 2 3 4
The parameter1 is:1
The parameter2 is:2
The parameter3 is:3
The parameter4 is:4

[root@192 sh]# cat parameters2.sh
#!/bin/bash
for i in "$*"
#$*中的所有参数看成是一个整体,所以这个for循环只会循环一次
do
echo "The parameters is : $i"
done
x=1
for y in "$@"
#$@中的每个参数都看成是独立的,所以"$@"中有几个参数,就会循环几次
do
echo "The parameter$x is:$y"
x=$(($x+1))
done

预定义变量

预定义变量 作用
$? 最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体哪个数,由命令自己来决定),则证明上一个命令执行不正确。
$$ 当前进程的进程号(PID)
$! 后台运行的最后一个进程的进程号(PID)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@192 sh]# ls 123
ls: 无法访问123: 没有那个文件或目录
[root@192 sh]# echo $?
2

# 示例:
[root@192 sh]# vi variable.sh
[root@192 sh]# chmod 755 variable.sh
[root@192 sh]# variable.sh
The current process is 62038
The last one Daemon process is 62039
[root@192 sh]# /root/sh/hello.sh

#!/bin/bash
#Author byking
echo "The current process is $$"
#输出当前进程的PID
#这个PID就是variable.sh这个脚本执行时,生成的进程的PID
find /root -name hello.sh &
#使用find命令在root目录下查找hello.sh文件
#符号&的意思是把命令放入后台执行
echo "The last one Daemon process is $!"

接收键盘输入

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
[root@localhost ~]# read [选项] [变量名]
选项:
-p "提示信息":在等待read输入时,输出提示信息,read命令会一直等待用户输入,使用此选项可以指定等待时间
-n 秒数: read命令只接受指定的字符数,就会执行
-s: 隐藏输入的数据,适用于机密信息的输入

#!/bin/bash
# Author:布衣king
read -t 30 -p "Please input your name:" name
echo "Name is $name"
read -s -t 30 -p "Please enter your age:" age
#年龄是隐私,所以我们用“-s”选项隐藏输入
echo -e "\n"
echo "Age is $age"

read -n 1 -t 30 -p "Please select your gender[M/F]:" sex
#使用“-n 1”选项只接收一个输入字符就会执行(都不用输入回车)
echo -e "\n"
echo "Sex is $sex"


#另一种
#!/bin/bash
# Author:布衣king
read -t 30 -p "Please input your name:" name
read -s -t 30 -p "Please enter your age:" age
#年龄是隐私,所以我们用“-s”选项隐藏输入
read -n 1 -t 30 -p "Please select your gender[M/F]:" sex
#使用“-n 1”选项只接收一个输入字符就会执行(都不用输入回车)
echo "Name is $name"
echo -e "\n"
echo "Age is $age"
echo -e "\n"
echo "Sex is $sex"

Bash的数值运算和运算符

数值运算

1
2
3
4
5
6
[root@192 sh]# aa=11                                                                     
[root@192 sh]# bb=22
[root@192 sh]# cc=$aa+$bb
[root@192 sh]# echo $cc
11+22
#变量默认数据类型是字符串

declare声明变量类型

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# declare [+/-][选项] 变量名
选项:
-: 给变量设置类型属性
+: 取消变量的类型属性
-i: 将变量声明为整数型(integer
-x: 将变量声明为环境变量
-p: 显示指定变量的被声明的类型
[root@192 sh]# declare -p aa
declare -- aa="11"
[root@192 sh]# export aa
[root@192 sh]# declare -p aa
declare -x aa="11"< /FONT>

数值运算——方法1

1
2
3
4
5
6
[root@192 sh]# aa=11
[root@192 sh]# bb=22
#给变量aa和bb赋值
[root@192 sh]# declare -i cc=$aa+$bb
[root@192 sh]# declare -p cc
declare -i cc="33"

方法2:expr或let数值运算工具

1
2
3
4
5
6
7
8
9
10
11
# expr英文:简单计算器
[root@192 sh]$ aa=11
[root@192 sh]$ bb=22
#给变量aa和bb赋值
[root@192 sh]$ dd= $(expr $aa+ $bb) #dd的值是aa和bb的和。注意“+”号左右两侧必须有空格,错误结果
[root@192 sh]$ dd=$(expr $aa + $bb) #加空格后的结果 33
[root@192 sh]$ echo $dd
33
[root@192 sh]$ dd=$(expr $aa+$bb) #没有加空格的结果 11+22
[root@192 sh]$ echo $dd
11+22

方法3:$((运算式))$[运算式]

1
2
3
4
5
6
[root@192 sh]# aa=11
[root@192 sh]# bb=22
[root@192 sh]# ff=$(($aa+$bb))
[root@192 sh]# gg=$[$aa+$bb]
[root@192 sh]# echo $ff $gg
33 33

运算符(数值越大优先级越高)

优先级 运算符 说明
13 -,+ 单目负、单目正
12 !,~ 逻辑非、按位取反或补码
11 *,/,% 乘、除、取模
10 +,- 加,减
9 <<,>> 按位左移,按位右移
8 <=,>=,<,> 小于或等于,大于或等于,小于,大于
7 ==,!= 等于、不等于
6 & 按位与
5 ^ 按位异或
4 | 按位或
3 && 逻辑与
2 || 逻辑或
1 =,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>= 赋值、运算且赋值
1
2
3
4
5
6
7
8
[root@localhost ~]$ aa=$(((11+3)*3/2))
#虽然乘和除的优先级高于加,但是通过小括号可以调整运算优先级
[root@localhost ~]$ bb=$((14%3)) #14不能被3整除,余数是2
[root@localhost ~]$ cc=$((1&&0))
#逻辑与运算只有两边都是1,与的结果才是1,否则与的结果是0
[yangmi@localhost ~]$ cc=$((2&&2))
[yangmi@localhost ~]$ echo $cc
1

Shell编程

Shell脚本是在Linux shell中执行的命令集合,用于自动化执行复杂或重复的任务。将命令写入脚本文件可简化操作并便于维护;建议将自定义脚本存放在/user/local/sbin/目录下以便管理和定时执行任务。

正则表达式

正则表达式与通配符

正则表达式用来在文件中匹配符合条件的字符串,正则是包含匹配。grepawksed等命令支持正则表达式。

通配符用来匹配符合条件的文件名,通配符是完全匹配。ls、find、cp这些命令不支持正则表达式,所以只能使用shell自己的通配符来进行匹配(* ? [])。

基础正则表达式

元字符 作用
* 前一个字符匹配0次或任意多次
. 匹配除了换行符外任意一个字符
^ 匹配行首。例如:^hello会匹配以hello开头的行
$ 匹配行尾。例如:hello$会匹配以hello结尾的行
[ ] 匹配中括号中指定的任意一个字符,只匹配一个字符。例如:[aoeiu]匹配任意一个元音字母,[0-9]匹配任意一位数字,[a-z][0-9]匹配小写字和一位数字构成的两位字符
[^] 匹配除中括号的字符以外的任意一个字符。例如:[^0-]
\ 转义符。用于取消将特殊符号的含义取消
\{n\} 表示其前面的字符恰好出现几次。例如:[0-9]{4}匹配4位数字,[1][3-8][0-9]\{9\}匹配手机号码
\{n,\} 表示其前面的字符出现不小于n次。例如:[0-9]\{2,\}表示两位以上的数字
\{n,m\} 表示其前面的字符至少出现n次,最多出现m次。例如:[a-z]\{6,8\}匹配6到8位的小写字母
test_rule.txt
1
2
3
4
5
6
7
8
9
10
11
Mr. Li Ming said:
he was the honest man in Lampbrother.
123despise him.

But since Mr. shen Chao came,
he never saaaid those words.
5555nice!

because,actuaaaally,
Mr. Shen Chao is the most honest man!
Later,Mr. Li ming soid his hot body.

"*"

前一个字符匹配0次或任意多次

1
2
3
4
5
6
7
8
9
10
11
# 匹配所有内容,包括空白行
[root@localhost kali]# grep "a*" test_rule.txt

# 匹配至少包含有一个a的行
[root@localhost kali]# grep "aa*" test_rule.txt

# 匹配最少包含两个连续a的字符串
[root@localhost kali]# grep "aaa*" test_rule.txt

# 匹配最少包含四个连续a的字符串
[root@localhost kali]# grep "aaaaa*" test_rule.txt

.

匹配除了换行符外任意一个字符

1
2
3
4
5
6
# "s..d"会匹配在s和d这两个字母之间一定 有两个字符的单词
[root@localhost kali]# grep "s..d" test_rule.txt
# 匹配在s和d字母之间的任意字符
[root@localhost kali]# grep "s.*d" test_rule.txt
# 匹配所有内容
[root@localhost kali]# grep ".*" test_rule.txt

匹配行首/行尾

^匹配行首,$匹配行尾

1
2
3
4
5
6
7
# 匹配以大写M开头的行
[root@localhost kali]# grep "^M" test_rule.txt

# 匹配以小写n结尾的行
[root@localhost kali]# grep "n$" test_rule.txt
# 匹配空白行
[root@localhost kali]# grep "^$" test_rule.txt

[]

[ ]匹配中括号中指定的任意一个字符,只匹配一个字符

1
2
3
4
5
6
7
8
# 匹配s和i字母中,要不是a,要不是o
[root@localhost kali]# grep "s[ao]id" test_rule.txt

# 匹配任意一个数字
[root@localhost kali]# grep "[0-9]" test_rule.txt

# 匹配用小写字母开头的行
[root@localhost kali]# grep "^[a-z]" test_rule.txt

[^]

[^]匹配除中括号的字符以外的任意一个字符

1
2
3
4
# 匹配不用小写字母开头的行
[root@localhost kali]# grep "^[^a-z]" test_rule.txt
# 匹配不用字母开头的行
[root@localhost kali]# grep "^[^a-z A-Z]" test_rule.txt

\转义符

1
2
# 匹配使用“.”结尾的行
[root@localhost kali]# grep "\.$" test_rule.txt

{n}

{n}表示其前面的字符恰好出现几次

1
2
3
4
5
# 匹配a字母连续出现三次的字符串
[root@localhost kali]$ grep "a\{3\}" test_rule.txt
# 匹配a字母连续出现三次的字符串
# 匹配包含连续的三个数字的字符串
[root@localhost kali]$ grep "[0-9]\{3\}" test_rule.txt

{n,}

{n,}表示其前面的字符出现不小于n次

1
2
# 匹配最少用连续3个数字开头的行
[root@localhost kali]# grep "^[0-9]\{3,\}[a-z]" test_rule.txt

{n,m}

{n,m}匹配其前面的字符至少出现n次,最多出现m次

1
[root@localhost kali]# grep "sa\{1,3\}i" test_rule.txt

字符截取命令

cut字段提取命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@localhost~]# cut [选项] 文件名
选项:
-f 列号: 提取第几列
-d分隔符: 按照指定分隔符分割列

#示例:
[root@localhost~]# vi student.txt
ID Name PHP LINUX MYSQL AVERAGE
1 liming 82 95 86 87.66
2 sc 74 96 87 85.66
3 gao 99 83 93 91.66
[root@localhost~]# cut -f 2 student.txt
[root@localhost~]# cut -f 2,4 student.txt #提取第2列和第4列
[root@localhost~]# cut -d ":" -f 1,3 /etc/passwd
[root@localhost~]# cat /etc/passwd | grep /bin/bash | grep -v root | cut -d ":" -f 1

#cut命令的局限(不是制表符,是空格符,无法分辨)

[root@localhost~]# df -h | grep "sda5" #查看分区使用状况
[root@localhost~]# df -h | cut -d " " -f 1,3 #查看分区使用状况

printf命令(awk中使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
printf '输出类型输出格式' 输出内容
输出类型:
%ns: 输出字符串。n是数字指代输出几个字符
%ni: 输出整数。n是数字指代输出几个数字
%m.nf: 输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位是小数,6位是整数
输出格式:
\a: 输出警告声音
\b: 输出退格键,也就是Backspace键
\f: 清除屏幕
\n: 换行
\r: 回车,也就是Enter键
\t: 水平输出退格键,也就是Tab键
\v: 垂直输出退格键,也就是Tab键

#示例:
[root@localhost~]# printf %s 1 2 3 4 5 6
[root@localhost~]# printf %s %s %s 1 2 3 4 5 6
[root@localhost~]# printf '%s %s %s' 1 2 3 4 5 6
[root@localhost~]# printf '%s %s %s\n' 1 2 3 4 5 6
[root@localhost~]#cat student.txt | printf '%s' #没有结果输出
[root@localhost~]#printf '%s' $(cat student.txt) #正确输出,变量赋值输出
[root@localhost~]#printf '%s\t %s\t %s\t %s\t %s\t %s\n' $(cat student.txt) #调整格式输出

在awk命令的输出中支持print和printf命令

  • print:print会在每个输出之后自动加入一个换行符(Linux默认没有print命令)
  • printf:printf是标准格式输出命令,并不会自动加入换行符,如果需要换行,需要手工加入换行符

awk命令

比cut强大,awk是复杂的命令,可以经常编程,流程控制等

1
2
3
4
5
6
7
8
9
# awk '条件1{动作1} 条件2{动作2}...' 文件名
条件 (Pattern):
一般使用关系表达式作为条件
x>10 判断变量x是否大于10
x>=10 大于等于10
x<=10 小于等于10
动作 (Action):
格式化输出
流程控制语句

注意:awk先读入第一行在开始处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#示例:
[root@localhost~]# awk '{printf $2 "\t" $6 "\n"}' student.txt #不做任何条件执行动作,显示第2和6列
[root@localhost~]# df -h | awk '{print $1 "\t" $3 "\t" $5}' #可以识别空格
[root@localhost~]# df -h | grep sda5 | awk '{print $5}'| cut -d "%" -f 1 #提取使用了多少空间

BEGIN #放到开头
[root@localhost~]# awk 'BEGIN {printf "This is a transcript \n"} {printf $2 "\t" $6 "\n"}' student.txt
#BEGIN是条件,开始之前

END #放到结尾
[root@localhost~]# awk 'END {printf "This END \n"} {printf $2 "\t" $6 "\n"}' student.txt

FS内置变量
[root@localhost~]# awk '{FS=":"}' {printf $1 "\t" $3 "\n"} /etc/passwd
[root@localhost~]# awk 'BEGIN{FS=":"}' {printf $1 "\t" $3 "\n"} /etc/passwd
[root@localhost~]# cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN { FS=":"} {printf $1 "\t" $3 "\n"}'

关系运算符
[root@localhost~]# cat student.txt | grep -v Name | awk '$6>=87 {printf $2 "\n"}'

sed命令

sed是一种几乎包括在所有UNIX平台(包括Linux)的轻量级流编辑器。sed主要是用来将数据进行选取、替换、删除、新增的命令。

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost~]# sed [选项] '[动作]' 文件名
选项:
-n: 一般sed命令会把所有数据都输出到屏幕,如果加入此选项,则只会把经过sed命令处理的行输出到屏幕。
-e: 允许对输入数据应用多条sed命令编辑
-i: 用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出
动作:
a\: 追加,在当前行后添加一行或多行。添加多行时,除最后一行外,每行末尾需要用"\"代表数据未完结。
c\: 行替换,用c后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用"\"代表数据未完结。
i\: 插入,在当期行前插入一行或多行。插入多行时,除最后一行外,每行末尾需要用"\"代表数据未完结。
d: 删除,删除指定的行。
p: 打印,输出指定的行。
s: 字串替换,用一个字符串替换另外一个字符串。格式位"行范围s/旧字串/新字串/g"(和vim中的替换格式类似)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
示例:sed输出都不影响文件本身,只是影响屏幕输出
行数据操作:
[root@localhost~]# sed '2p' student.txt #查看文件的第2行
[root@localhost~]# sed -n '2p' student.txt #查看文件的第2行
[root@localhost~]# sed '2,4d' student.txt #删除第二行到第四行的数据,但不修改文件本身
[root@localhost~]# df -h | sed -n '2p'
[root@localhost~]# sed '2a hello' student.txt #在第二行后追加hello
[root@localhost~]# sed '2i hello \ world' student.txt #在第二行前插入两行数据
[root@localhost~]# sed '2c No such person' student.txt #数据替换

字符串替换:
[root@localhost~]# sed 's/旧字串/新字串/g' 文件名
[root@localhost~]# sed '3s/74/99/g' student.txt #在第三行中,把74换成99
[root@localhost~]# sed -i '3s/74/99/g' student.txt #sed操作的数据直接写入文件
#[root@localhost~]# sed ie 's/Liming//g;s/Gao//g' student.txt #同时把Liming和Gao替换为空

字符处理命令

排序命令sort

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]#sort [选项] 文件名
选项:
-f: 忽略大小写
-n: 以数值型进行排序,默认使用字符串型排序
-r: 反向排序
-t: 指定分隔符,默认分隔符是制表符
-k n[,m] 按照指定的字段范围排序。从第n字段开始,m字段结束(默认到行尾)

[root@localhost ~]#sort /etc/passwd #排序用户信息文件
[root@localhost ~]#sort -r /etc/passwd #反向排序
[root@localhost ~]#sort -t ":" -k 3,3 /etc/passwd #指定分隔符是“:”,用第三字段开头,第三字段结尾排序,就是只用第三字段排序。
[root@localhost ~]#sort -n -t ";" -k 3,3 /etc/passwd

统计命令wc

1
2
3
4
5
6
7
8
[root@localhost ~]#wc [选项] 文件名
选项:
-l: 只统计行数
-w: 只统计单词数
-m: 只统计字符数

[root@localhost ~]# wc /etc/passwd
43 87 2262 /etc/passwd

条件判断

安装文件类型进行判断

测试选项 作用
-b 文件 判断该文件是否存在,并且是否为块设备文件(是块设备文件为真)
-c 文件 判断该文件是否存在,并且是否为字符设备文件(是字符设备文件为真)
-d 文件 判断该文件是否存在,并且是否为目录文件(是目录为真)
-e 文件 判断该文件是否存在(存在为真)
-f 文件 判断该文件是否存在,并且是否为普通文件(是普通文件为真)
-L 文件 判断该文件是否存在,并且是否为符号链接文件(是符号链接文件为真)
-p 文件 判断该文件是否存在,并且是否为管道文件(是管道文件为真)
-s 文件 判断该文件是否存在,并且是否为非空(非空为真)
-s 文件 判断该文件是否存在,并且是否为套接字文件(是套接字文件为真)
1
2
3
4
5
6
7
8
9
10
11
# 两种判断格式(给程序看的)
[root@localhost ~]# test -e /root/install.log
# 前面和后面一定要有空格
[root@localhost ~]# [ -e /root/kali/test_rule.txt ]

# $?查看上一条命令是否正确,返回值是0代表正确,非0代表错误
[root@localhost ~]# echo $?
0
# 命令执行顺序||
[root@localhost ~]# [ -d /root ]&& echo "yes"||echo "no"
yes

按照文件权限进行判断

测试选项 作用
-r 文件 判断该文件是否存在,并且是否该文件拥有读权限(有读权限为真)
-w 文件 判断该文件是否存在,并且是否该文件拥有写权限(有写权限为真)
-x 文件 判断该文件是否存在,并且是否该文件拥有执行权限(有执行权限为真)
-u 文件 判断该文件是否存在,并且是否该文件拥有SUID权限(有SUID权限为真)
-g 文件 判断该文件是否存在,并且是否该文件拥有SGID权限(有SGID权限为真)
-k 文件 判断该文件是否存在,并且是否该文件拥有SBit权限(有SBit权限为真)
1
2
3
[root@localhost ~]# [ -w /root/kali/test_rule.txt ]&& echo "yes"||echo "no"
[root@localhost ~]# [ -w /root/kali/test_rule.txt ]&& echo yes||echo no
yes

两个文件之间进行比较

测试选项 作用
文件1 -nt 文件2 判断文件1的修改时间是否比文件2的新(如果新则为真)
文件1 -ot 文件2 判断文件1的修改时间是否比文件2的旧(如果旧则为真)
文件1 -ef 文件2 判断文件1是否和文件2的inode号一致,可以理解为两个文件是否为同一个文件,这个判断用于判断硬链接是很好的方法

两个整数之间比较(针对程序脚本的)

测试选项 作用
整数1 -eq 整数2 判断整数1是否和整数2相等(相等为真)
整数1 -ne 整数2 判断整数1是否和整数2不相等(不相等为真)
整数1 -gt 整数2 判断整数1是否大于整数2(大于为真)
整数1 -lt 整数2 判断整数1是否小于整数2(小于为真)
整数1 -ge 整数2 判断整数1是否大于等于整数2(大于等于为真)
整数1 -le 整数2 判断整数1是否小于等于整数2(小于等于为真)
1
2
3
4
5
6
7
8
9
# 判断整数23大于整数22
[root@localhost ~]# [ 23 -gt 22 ]&& echo yes ||echo no
yes

# 判断23是否大于等于22
[root@localhost ~]# [ 23 -ge 22 ]&& echo yes ||echo no

# 判断23是否小于等于22
[root@localhost ~]# [ 23 -le 22 ]&& echo yes ||echo no

字符串的判断(常用的)

测试选项 作用
-z 字符串 判断字符串是否为空(为空返回真)
-n 字符串 判断字符串是否为非空(非空返回真)
字串1==字串2 判断字符串1是否和字符串2相等(相等返回真)
字串1!=字串2 判断字符串1是否和字符串2不相等(不相等返回真)
1
2
3
4
5
6
7
8
9
# 给name变量赋值
[root@localhost ~]# name=sc
# 判断name是否为空,因为不为空,所以返回no(写程序用的上)
[root@localhost ~]# [ -z "$name" ]&& echo "yes"||echo "no"
[root@localhost ~]# aa=11
[root@localhost ~]# bb=22
# 判断两个变量的值是否相等,明显不相等,所以返回no
[root@localhost ~]# [ "$aa" == "$bb" ]&& echo "yes" ||echo "no"
no

多重条件判断

测试选项 作用
判断1 -a 判断2 逻辑与,判断1和判断2都成立,最终的结果为真
判断1 -o 判断2 逻辑或,判断1和判断2有一个成立,最终的结果就为真
!判断 逻辑非,使原始的判断式取反
1
2
3
4
5
[root@localhost ~]# aa=11
[root@localhost ~]# [ -n "$aa" -a "$aa" -gt 23 ]&& echo yes ||echo no
no
# 判断变量aa是否有值,同时判断变量aa的是否大于23
# 因为变量aa的值不大于23,所以虽然第一个判断值为真,返回的结果也是假

Shell流程控制

IF语句

单分支if条件语句

1
2
3
4
5
6
7
8
if  [条件判断式] ;then
程序
fi
或者
if [条件判断式]
then
程序
fi

单分支条件语句需要注意几个点:

  • if语句使用fi结尾,和一般语言使用大括号结尾不同
  • [条件判断式]就是使用test命令判断,所以中括号和条件判断式之间必须有空格
  • then后面跟符合条件之后执行的程序,可以放在[ ]之后,用“;”分割。也可以换行写入,就不需要“;”了
    判断分区使用率
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/bin/bash
    # 统计根分区使用率
    # Author: kali

    rate=$(df -h | grep "/dev/sda3" | awk '{print $5}' | cut -d "%" -f1) # 关键
    # 把根分区使用率作为变量值赋予变量rate

    if [$rate -ge 80]
    then
    echo "Warning! /dev/sda3 is full!!"
    fi

    # 此程序的写法
    # 这条命令可以执行就可以作为变量使用
    df -h | grep "/dev/sda3" | awk '{print $5}' | cut -d "%" -f1

双分支if条件语句

1
2
3
4
5
6
if [条件判断式]
then
条件成立时,执行的程序
else
条件不成立时,执行的另一个程序
fi
备份mysql数据库
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
#! /bin/bash
# 备份mysql数据库
# Author: kali

ntpdate asia.pool.ntp.org &>/dev/null
# 同步系统时间
date=$(date +%y%m%d)
# 把当前系统时间按照“年月日”格式赋予变量date
size=$(du -sh /var/lib/mysql)
# 统计mysql数据库的大小,并把大小赋予size变量

if [ -d /tmp/dbbak]
then
echo "Date :$date!" > /tmp/dbbak/dbinfo.txt
echo "Date size :$size" >> /tmp/dbbak/dbinfo.txt
cd /tmp/dbbak
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &>dev/null #/var/lib/mysql目录可以直接换一个做测试
rm -rf /tmp/dbbak/dbinfo.txt
else
mkdir /tmp/dbbak
echo "Date :$date!" > /tmp/dbbak/dbinfo.txt
echo "Date size :$size" >> /tmp/dbbak/dbinfo.txt
cd /tmp/dbbak
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &>dev/null
rm -rf /tmp/dbbak/dbinfo.txt
fi
判断apache是否启动
1
2
3
4
5
6
7
8
9
10
11
12
13
#! /bin/bash
# 判断apache是否启动
# Author: kali

port=$(nmap -sT 192.168.1.156 | grep tcp | grep http | awk '{print $2}')
#使用nmap命令扫描服务器,并截取apache服务的状态,赋予变量port
if ["$port"=="port"]
then
echo "$(date) httpd is ok!" >>/tmp /autostart-acc.log
else
/etc/rc.d/init.d/httpd start &>/dev/null
echo "$(date) restart httpd !!" >>/tmp/autostart-err.log
fi

多分支if条件语句

1
2
3
4
5
6
7
8
9
10
if [条件判断式1]
then
当条件判断式1成立时,执行程序1
elif [条件判断式2]
then
但条件判断式2成立时,执行程序2
...省略更多条件...
else
当所有条件都不成立时,最后执行此程序
fi
判断用户输入的是什么文件
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
#!/bin/bash
# 判断用户输入的是什么文件
# Author: kali

read -p "Please input a filename: " file
# 接收键盘的输入,并赋予变量file

if [ -z "$file"]
# 判断file变量是否为空
then
echo "Error ,pleae input a filename"
exit 1
elif [ ! -e "$file"]
# 判断file的值是否存在
then
echo "Your input is not a file!"
exit 2
elif [ -f "$file"]
# 判断file的值是否为普通文件
then
echo "$file is a regulare file!"
elif [ -d "$file"]
# 判断file的值是否为目录文件
then
echo "$file is a directory!"
else
echo "$file is an other file! "
fi

CASE语句

多分支case条件语句(判断)

case语句和if…elif…else语句一样都是多分支条件语句,不过和if多分支条件语句不同的是,case语句只能判断一种条件关系,而if语句可以判断多种条件关系。

1
2
3
4
5
6
7
8
9
10
11
12
case $变量名 in 
"值1"
如果变量的值等于值1,则执行程序1
;;
"值2")
如果变量的值等于值2,则执行程序2
;;
...省略其他分支...
*)
如果变量的值都不是以上的值,则执行此程序
;;
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
示例1:
#!/bin/bash
# 判断用户输入
# AuThor:kali
read -p "Please choose yes/no" -t 30 cho
case $cho in
"yes")
echo "YOUR choose is yes!"
;;
"no")
echo "YOUR choose is no!"
;;
*)
echo "Your choose is error!"
;;
esac

示例2:
#!/bin/bash
# 输入123选择机票
# AuThor:kali

echo 'you want to shanghai,plseas input "1"'
echo 'you want to guangzhou,plseas input "2"'
echo 'you want to chengdu,plseas input "3"'


read -t 30 -p "Please input your chooise: " cho
case $cho in
"1")
echo "上海的机票"
;;
"2")
echo "广州的机票!"
;;
"3")
echo "成都的机票!"
;;
"*")
echo "错误,输入1、2、3"
esac

FOR循环

语法一
1
2
3
4
for  变量  in 值1 值2 值3...
do
程序
done
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
#!/bin/bash
# 打印时间
# AuThor:kali
for time in morning noon afternoon evening
do
echo "This time is $time!"
done


#!/bin/bash
# 循环1到6
# AuThor:kali
for i in 1 2 3 4 5 6 # 可以用变量替代
do
echo $i
done


#!/bin/bash
# 批量解压缩脚本
# AuThor:kali
cd /lamp
ls *.tar.gz>ls.log
for i in $(cat ls.log)
do
tar -zxf $i &>/dev/null
done
rm -rf /lamp/ls.log
语法二:适用是否知道循环
1
2
3
4
for ((初始值;循环控制条件;变量变化))
do
程序
done
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
#!/bin/bash
# 从1加到100
# AuThor:kali

s=0
for ((i=1; i<=100; i=i+1))
do
s= $(($s+$i)) #只有用双小括号数值才能运算
done
echo "The sum of 1+2+3...+100 is :$s"



#!/bin/bash
# 批量添加指定数量的用户
# AuThor:kali

read -p "Please input user name: " -t 30 name
read -p "Please input the number of users: " -t 30 num
read -p "Please input the password of users: " -t 30 pass
if[ ! -z "$name" -a ! -z "$num" -a ! -z "$pass"]
then
y=$(echo $num | sed's/^[0-9]*$'//g)
if[ -z "$y"]
then
for((i=1;i<=$num;i=i+1))
do
/usr/sbin/useradd $name$i &>/dev/null
echo $pass | /usr/bin/passwd --stdin $name$i &>/dev/null #| passwd --stdin "$name$i"另一种写法
done
fi
fi

WHILE循环和UNTIL循环

while循环

while循环是不定循环,也称作条件循环。只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。这就是和for的固定循环不太一样了。

语法
1
2
3
4
while [ 条件判断式 ]
do
程序
done
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# 从1加到100
# AuThor:kali

i=1
s=0
while [$i -le 100]
# 如果变量i的值小于等于100,则执行循环
do
s=$(($s+$i))
i=$(($i+1))
done
echo "The sum is : "$s"

until循环

until循环,和while循环相反,until循环时只要条件判断式不成立则进行循环,并执行循环程序。一旦循环条件成立,则终止循环。

语法
1
2
3
4
until [ 条件判断式 ]
do
程序
done
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# 从1加到100
# AuThor:kali

i=1
s=0
until [$i -gt 100]
# 循环直到变量i的值大于100 就停止循环
do
s=$(($s+$i))
i=$(($i+1))
done
echo "The sum is : "$s"

Shell编程案例分析

移动目录

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# 编写shell脚本,把/root/目录下的所有目录(只需要一级)拷贝到/tmp/目录下;
cd /root/
for file in `ls`
do
if [ -d $file ]
then
mkdir /tmp/$file
else
continue
fi
done

输入整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
# 编写shell脚本,要求输入一个正整数,然后计算出从1到输入数字的和,要求如果输入的数字小于1,则重新输入,直到输入正确的数字为止;
while :
do
read -p "请输入一个正整数: " n
if echo $n |grep -q '[^0-9]'
then
echo "你没有输入一个正整数!"
continue
fi
if [ $n -lt 1 ]
then
echo "你没有输入大于1的数!"
continue
fi
for i in `seq 1 $n` #1循环到$n
do
j=$[$j+$i]
done
echo $j
exit
done

批量建立用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# 批量建立用户user_00,user_01,...user_100并且所有用户同属于users组;
for i in `seq 0 1 100`
do
if [ $i -lt 10 ]
then
useradd -g 100 user_0$i
elif [ $i == 100 ]
then
useradd -g 100 user_100
else
useradd -g 100 user_$i
fi
done

计算1-100的和

1
2
3
4
5
6
#!/bin/bash
for i in `seq 1 100`
do
j=$[$j+$i]
done
echo $j

获取随机数

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
#!/bin/bash
# RANDOM随机函数,100取余就可以获得1-100的随机整数
n=$[$RANDOM%100]
while :
do
read -p "请输入一个1-100间的整数:" n1
n2=`echo $n1|sed 's/[0-9]//g'`
if [ ! -z $n2 ]
then
echo "你输入的不是1-100的整数!"
continue
fi
if [ $n1 -gt 100 ] || [ $n1 == 0 ]
then
echo "请输入1-100的整数!"
continue
fi
if [ $n1 == $n ]
then
echo "你猜对了!"
break
elif [ $n1 -gt $n ]
then
echo "你输入的数字太大了!"
continue
else
echo "你输入的数字太小了!"
continue
fi
done

乘法口诀表

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
for i in `seq 1 9`
do
for j in `seq 1 $i`
do
k=$[$i*$j]
# -n选项,不换行,-e使用制表符
echo -ne "$j""X""$i=$k\t"
done
echo
done

俄罗斯方块

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
#!/bin/bash

# Tetris Game
# 10.21.2003 xhchen<[email]xhchen@winbond.com.tw[/email]>

#APP declaration
APP_NAME="${0##*[\\/]}"
APP_VERSION="1.0"


# 颜色定义
cRed=1
cGreen=2
cYellow=3
cBlue=4
cFuchsia=5
cCyan=6
cWhite=7
colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)

# 位置和大小
iLeft=3
iTop=2
((iTrayLeft = iLeft + 2))
((iTrayTop = iTop + 1))
((iTrayWidth = 10))
((iTrayHeight = 15))

# 颜色设置
cBorder=$cGreen
cScore=$cFuchsia
cScoreValue=$cCyan

# 控制信号
# 改游戏使用两个进程,一个用于接收输入,一个用于游戏流程和显示界面;
# 当前者接收到上下左右等按键时,通过向后者发送signal的方式通知后者。
sigRotate=25
sigLeft=26
sigRight=27
sigDown=28
sigAllDown=29
sigExit=30

# 七中不同的方块的定义
# 通过旋转,每种方块的显示的样式可能有几种
box0=(0 0 0 1 1 0 1 1)
box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
box4=(0 1 0 2 1 1 2 1 1 0 1 1 1 2 2 2 0 1 1 1 2 0 2 1 0 0 1 0 1 1 1 2)
box5=(0 1 1 1 2 1 2 2 1 0 1 1 1 2 2 0 0 0 0 1 1 1 2 1 0 2 1 0 1 1 1 2)
box6=(0 1 1 1 1 2 2 1 1 0 1 1 1 2 2 1 0 1 1 0 1 1 2 1 0 1 1 0 1 1 1 2)
# 所有其中方块的定义都放到box变量中
box=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
# 各种方块旋转后可能的样式数目
countBox=(1 2 2 2 4 4 4)
# 各种方块再box数组中的偏移
offsetBox=(0 1 3 5 7 11 15)

# 每提高一个速度级需要积累的分数
iScoreEachLevel=50 # be greater than 7

# 运行时数据
sig=0 # 接收到的signal
iScore=0 # 总分
iLevel=0 # 速度级
boxNew=() # 新下落的方块的位置定义
cBoxNew=0 # 新下落的方块的颜色
iBoxNewType=0 # 新下落的方块的种类
iBoxNewRotate=0 # 新下落的方块的旋转角度
boxCur=() # 当前方块的位置定义
cBoxCur=0 # 当前方块的颜色
iBoxCurType=0 # 当前方块的种类
iBoxCurRotate=0 # 当前方块的旋转角度
boxCurX=-1 # 当前方块的x坐标位置
boxCurY=-1 # 当前方块的y坐标位置
iMap=() # 背景方块图表

# 初始化所有背景方块为-1, 表示没有方块
for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done

# 接收输入的进程的主函数
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
}

# 退出前的恢复
function MyExitNoSub()
{
local y

# 恢复终端属性
stty $sTTY
((y = iTop + iTrayHeight + 4))

# 显示光标
echo -e "\033[?25h\033[${y};0H"
exit
}


function MyExit()
{
# 通知显示进程需要退出
kill -$sigExit $pidDisplayer

MyExitNoSub
}


# 处理显示和游戏流程的主函数
function RunAsDisplayer()
{
local 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
}


# BoxMove(y, x), 测试是否可以把移动中的方块移到(x, y)的位置, 返回0则可以, 1不可以
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;
}


# 将当前移动中的方块放到背景方块中去,
# 并计算新的分数和速度级。(即一次方块落到底部)
function Box2Map()
{
local j i x y xp yp line

# 将当前移动中的方块放到背景方块中去
for ((j = 0; j < 8; j += 2))
do
((i = j + 1))
((y = ${boxCur[$j]} + boxCurY))
((x = ${boxCur[$i]} + boxCurX))
((i = y * iTrayWidth + x))
iMap[$i]=$cBoxCur
done

# 消去可被消去的行
line=0
for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
do
for ((i = j + iTrayWidth - 1; i >= j; i--))
do
if ((${iMap[$i]} == -1)); then break; fi
done
if ((i >= j)); then continue; fi

((line++))
for ((i = j - 1; i >= 0; i--))
do
((x = i + iTrayWidth))
iMap[$x]=${iMap[$i]}
done
for ((i = 0; i < iTrayWidth; i++))
do
iMap[$i]=-1
done
done

if ((line == 0)); then return; fi

# 根据消去的行数line计算分数和速度级
((x = iLeft + iTrayWidth * 2 + 7))
((y = iTop + 11))
((iScore += line * 2 - 1))
# 显示新的分数
echo -ne "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore} "
if ((iScore % iScoreEachLevel < line * 2 - 1))
then
if ((iLevel < 20))
then
((iLevel++))
((y = iTop + 14))
# 显示新的速度级
echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel} "
fi
fi
echo -ne "\033[0m"


# 重新显示背景方块
for ((y = 0; y < iTrayHeight; y++))
do
((yp = y + iTrayTop + 1))
((xp = iTrayLeft + 1))
((i = y * iTrayWidth))
echo -ne "\033[${yp};${xp}H"
for ((x = 0; x < iTrayWidth; x++))
do
((j = i + x))
if ((${iMap[$j]} == -1))
then
echo -ne " "
else
echo -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"
fi
done
done
}


# 下落一行
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
}

# 左移一列
function BoxLeft()
{
local x s
((x = boxCurX - 1))
if BoxMove $boxCurY $x
then
s=`DrawCurBox 0`
((boxCurX = x))
s=$s`DrawCurBox 1`
echo -ne $s
fi
}

# 右移一列
function BoxRight()
{
local x s
((x = boxCurX + 1))
if BoxMove $boxCurY $x
then
s=`DrawCurBox 0`
((boxCurX = x))
s=$s`DrawCurBox 1`
echo -ne $s
fi
}


# 下落到底
function BoxAllDown()
{
local k j i x y iDown s
iDown=$iTrayHeight

# 计算一共需要下落多少行
for ((j = 0; j < 8; j += 2))
do
((i = j + 1))
((y = ${boxCur[$j]} + boxCurY))
((x = ${boxCur[$i]} + boxCurX))
for ((k = y + 1; k < iTrayHeight; k++))
do
((i = k * iTrayWidth + x))
if (( ${iMap[$i]} != -1)); then break; fi
done
((k -= y + 1))
if (( $iDown > $k )); then iDown=$k; fi
done

s=`DrawCurBox 0` # 将旧的方块抹去
((boxCurY += iDown))
s=$s`DrawCurBox 1` # 显示新的下落后的方块
echo -ne $s
Box2Map # 将当前移动中的方块贴到背景方块中
RandomBox # 产生新的方块
}


# 旋转方块
function BoxRotate()
{
local iCount iTestRotate boxTest j i s
iCount=${countBox[$iBoxCurType]} # 当前的方块经旋转可以产生的样式的数目

# 计算旋转后的新的样式
((iTestRotate = iBoxCurRotate + 1))
if ((iTestRotate >= iCount))
then
((iTestRotate = 0))
fi

# 更新到新的样式, 保存老的样式(但不显示)
for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
do
boxTest[$j]=${boxCur[$j]}
boxCur[$j]=${box[$i]}
done

if BoxMove $boxCurY $boxCurX # 测试旋转后是否有空间放的下
then
# 抹去旧的方块
for ((j = 0; j < 8; j++))
do
boxCur[$j]=${boxTest[$j]}
done
s=`DrawCurBox 0`

# 画上新的方块
for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
do
boxCur[$j]=${box[$i]}
done
s=$s`DrawCurBox 1`
echo -ne $s
iBoxCurRotate=$iTestRotate
else
# 不能旋转,还是继续使用老的样式
for ((j = 0; j < 8; j++))
do
boxCur[$j]=${boxTest[$j]}
done
fi
}


# DrawCurBox(bDraw), 绘制当前移动中的方块, bDraw为1, 画上, bDraw为0, 抹去方块。
function DrawCurBox()
{
local i j t bDraw sBox s
bDraw=$1

s=""
if (( bDraw == 0 ))
then
sBox="\040\040"
else
sBox="[]"
s=$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"
fi

for ((j = 0; j < 8; j += 2))
do
((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))
# \033[y;xH, 光标到(x, y)处
s=$s"\033[${i};${t}H${sBox}"
done
s=$s"\033[0m"
echo -n $s
}


# 更新新的方块
function RandomBox()
{
local i j t

# 更新当前移动的方块
iBoxCurType=${iBoxNewType}
iBoxCurRotate=${iBoxNewRotate}
cBoxCur=${cBoxNew}
for ((j = 0; j < ${#boxNew[@]}; j++))
do
boxCur[$j]=${boxNew[$j]}
done


# 显示当前移动的方块
if (( ${#boxCur[@]} == 8 ))
then
# 计算当前方块该从顶端哪一行"冒"出来
for ((j = 0, t = 4; j < 8; j += 2))
do
if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
done
((boxCurY = -t))
for ((j = 1, i = -4, t = 20; j < 8; j += 2))
do
if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
done
((boxCurX = (iTrayWidth - 1 - i - t) / 2))

# 显示当前移动的方块
echo -ne `DrawCurBox 1`

# 如果方块一出来就没处放,Game over!
if ! BoxMove $boxCurY $boxCurX
then
kill -$sigExit ${PPID}
ShowExit
fi
fi



# 清除右边预显示的方块
for ((j = 0; j < 4; j++))
do
((i = iTop + 1 + j))
((t = iLeft + 2 * iTrayWidth + 7))
echo -ne "\033[${i};${t}H "
done

# 随机产生新的方块
((iBoxNewType = RANDOM % ${#offsetBox[@]}))
((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
do
boxNew[$j]=${box[$i]};
done

((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))

# 显示右边预显示的方块
echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"
for ((j = 0; j < 8; j += 2))
do
((i = iTop + 1 + ${boxNew[$j]}))
((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
echo -ne "\033[${i};${t}H[]"
done
echo -ne "\033[0m"
}


# 初始绘制
function InitDraw()
{
clear
RandomBox # 随机产生方块,这时右边预显示窗口中有方快了
RandomBox # 再随机产生方块,右边预显示窗口中的方块被更新,原先的方块将开始下落
local i t1 t2 t3

# 显示边框
echo -ne "\033[1m"
echo -ne "\033[3${cBorder}m\033[4${cBorder}m"

((t2 = iLeft + 1))
((t3 = iLeft + iTrayWidth * 2 + 3))
for ((i = 0; i < iTrayHeight; i++))
do
((t1 = i + iTop + 2))
echo -ne "\033[${t1};${t2}H||"
echo -ne "\033[${t1};${t3}H||"
done

((t2 = iTop + iTrayHeight + 2))
for ((i = 0; i < iTrayWidth + 2; i++))
do
((t1 = i * 2 + iLeft + 1))
echo -ne "\033[${iTrayTop};${t1}H=="
echo -ne "\033[${t2};${t1}H=="
done
echo -ne "\033[0m"


# 显示"Score"和"Level"字样
echo -ne "\033[1m"
((t1 = iLeft + iTrayWidth * 2 + 7))
((t2 = iTop + 10))
echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"
((t2 = iTop + 11))
echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"
((t2 = iTop + 13))
echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"
((t2 = iTop + 14))
echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"
echo -ne "\033[0m"
}


# 退出时显示GameOVer!
function ShowExit()
{
local y
((y = iTrayHeight + iTrayTop + 3))
echo -e "\033[${y};0HGameOver!\033[0m"
exit
}


# 显示用法.
function Usage
{
cat << EOF
Usage: $APP_NAME
Start tetris game.

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


# 游戏主程序在这儿开始.
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