文章目录
认识Shell编程
- 比较流行的shell程序都是bash
- shell是套在内核外面的一层外壳,向普通用户隐藏了许多关于系统内核的细节
- 在UNIX系统中,shell即使用户交互的界面,也是控制系统的脚本语言
- Bash是绝大多数Linux发行版的默认shell
- 当shell期待用户下一步输入时,shell提示符会从#变成>
- shell脚本以#!开头的一行代表它后面的那个参数时用来执行本文件的程序
- #!后面跟的是绝对路径,程序会搜索对应的路径,然后用创建对应的解释器进程来解释并执行当前脚本的语句
- 如果没有#! 我们也可以通过source来执行,这样就是在当前shell执行
- 我们给shell脚本传递参数,如果参数有空格,需要用""包起来
- 注释有两种,一种是#,另外一种是用用:配合here document来使用
- Linux每个命令都有一个退出状态码,成功是0,我们也可以用exit语句指定
Shell编程环境搭建
- bash的主要配置文件有5个,有每个用户的,也有全局的
- 为了简化命令,可以给命令设置别名,别名优先级要高于原始命令
变量和引用
- shell变量是弱类型,不需要显式声明数据类型
- shell变量会统一按照字符串存储,但是可以进行整数的加减等
- 如果一定要控制变量类型,可以使用declare来声明变量,这个属性也可以取消
- Shell语言中的三种引号非常重要
- 单引号
- 双引号
- 反括号 反括号在双引号中出现会被解释成命令
- 注意,在方法内部定义的变量也是全局变量,作用一直到Shell脚本结束或被显式的删除
- 使用local定义局部变量,方法结束就没了
- 取变量的值用$符号
- 如果我们想输出脚本名称,或者上个命令退出状态码,可以用系统变量
- 环境变量是所有Shell程序都可以使用的变量,可以使用set列出环境变量
- PATH 命令搜索路径
- HOME 用户主目录的路径名
- 引用
- 双引号 除了美元符号,单引号,反引号,反斜线外都保持字面意义
- 单引号 全都保持字面意义
- 反引号 里面解析成shell命令
条件测试和判断语句
- 条件为真,返回0
- 条件测试语法有test和[命令
- 使用方括号的时候一定要注意和参数保持一个空格
- 条件测试的运算符,比如=号两边一定要有空格,有空格才和JAVA的==一样
- 整数之间的比较,一定要用-eq这类运算符,不要用=号和!=,这俩是字符串比较的
- 条件测试也可以测试文件是否存在,可读可写等
- ! 是逻辑非 -a是逻辑与 -o是逻辑或
- if后面可以是条件表达式,也可以是Shell命令
- 空命令:一定返回0
- exit 语句还可以带一个可选的参数,用来指定程序退出时的状态码,我们可以通过这个状态码传递给父进程信息
- case语句注意事项
- 条件测试部分以右括号结束
- 一对分号相当于break
- 星号加右括号相当于default
- 运算符有
- expr
- $((…))和$[…] ,建议使用,因为它不需要对运算符和括号转义,可以采用松散或者紧凑的格式来书写表达式
- 使用 let 命令可以执行一个或者多个算术表达式,其中的变量名无需使用$符号
- shell也有自增和自减运算符,自增自减只能针对变量,不能对常量或表达式使用
循环结构
- FOR循环可以指定步长,可以用…来代表范围
for i in {1..100..2}
- FOR循环也可以使用字符串和命令
# 字符串
for day in {Mon Tue Wed Thu Fri Sat Sun}
# 命令
for file in $(ls)
- FOR循环如果没有IN,那么默认IN后面跟的是用户的调用参数
- 类C风格的FOR循环
for ((expression1;expression2;expression3))
for (( i=1;i<5;i++))
- 数组形式的FOR循环
array=(Monday Tuesday Wednesday Thursday Friday Saturday Sunday)
#通过 for 循环遍历数组元素
for day in ${array[*]}
- Until循环
until expression
do
...
done
- while 循环语句
while expression
do
...
done
- break N 表示跳出N层循环,默认是1层,continue也可以跟参数
函数
- 函数和SHELL脚本不同之处在于它不会创建新进程
- 函数可以有两种定义方式
function_name ()
function function_name ()
- 函数一定要在调用之前定义,调用时不用加(),函数调用函数也必须保证定义顺序
- 返回值很重要,return语句只能返回0-255的整数,如果不在这个范围,就会返回1
- 如果要突破return的限制,可以使用echo,echo的返回值并没有限制,通过它来写到标准输出
- 函数内部,如果没用local关键字,则也是全局变量
- SHELL脚本定义时,不需要指定参数,可以使用时直接传入,通过位置变量获取
- shift命令可以使脚本参数左移,左移之后左面的就被删除了,它会改变$#的值
- getopts可以处理复杂的入参
[hello@Git shell]$ bash test.sh -a hello
this is -a the arg is ! hello
[hello@Git shell]$ more test.sh
#!/bin/bash
# 这个opt保存了我们存到OPTARG里面的变量名
while getopts "a:" opt; do
case $opt in
a)
echo "this is -a the arg is ! $OPTARG"
;;
\?)
echo "Invalid option: -$OPTARG"
;;
esac
done
getopts一共有两个参数,第一个是-a这样的选项,第二个参数是 hello这样的参数。
选项之间可以通过冒号:进行分隔,也可以直接相连接,:表示选项后面必须带有参数,如果没有可以不加实际值进行传递
getopts ahfvc: option表明选项a、h、f、v可以不加实际值进行传递,而选项c必须取值,值保存在$OPTARG
- 变量名可以通过!间接应用
var=name
name=John
引用name的两种方式
${name}
${!var}
- SEHLL脚本本质上不支持数组作为函数参数,但是我们可以使用变通的方式
#! /bin/bash
#定义函数
func()
{
echo "number of elements is $#."
while test $# -gt 0
do
echo "$1"
shift
done
}
#定义数组
a=(a b "c d" e)
#调用函数,注意这里的引号,它将c和d看成一个
func "${a[@]}"
输出
number of elements is 4.
a
b
c d
e
- 函数库文件里面只有函数
- 载入库文件方法
# 注意,点号和filename之间有个空格,filename可以使绝对或相对路径
. filename
数组
- 定义数组有四种方式
# 直接使用下标,这种定义了1个,那么数组长度就是1
array[1]=one
# 使用declare -a
declare -a array
array[0]=1
# 通过元素值集合定义数组
array=(1 2 3 4 5 6 7 8)
# 通过键值对定义数组
array=([1]=one [4]=four)
- 当我们只有数组名访问数组的时候,返回的是下标为0的元素的值
array=(1 2 3 4 5)
# 下面返回1
echo "${array}"
- 数组可以使用截断某一部分,也可以截断某个元素的某一部分
- 可以用unset来删除数组某个元素或者整个数组,删除后某个元素数组长度减1
- 数组可以复制或者连接
# 复制
newarray=("${array[@]}")
# 连接
("$array1[@]}" "${array2[@]}")
- 数组可以加载某个文件,文件的每一行构成数组的一个元素
content=(`cat "demo.txt"`)
#通过循环输出数组内容
for s in "${content[@]}"
do
echo "$s"
done
正则表达式
- 正则表达式的核心是元字符
- 正则表达式有
- 基本正则表达式
- 扩展正则表达式
- Perl正则表达式
- POSIX字符集
基本文本处理
- echo -n 不输出行尾换行符,在默认情况下,echo 命令在输出文本的末尾会自动追加一个换行符
- echo中支持转义字符,我们可以通过转义字符来定义输出格式
- echo中输出变量需要 ${变量}括起来
- echo后面跟反引号括起来的shell命令,可以将shell结果输出
- 文本的格式化输出主要有三种
- 制表符,转义字符\t
- 使用 fold 命令格式化行,功能是将超过指定宽度的文本行进行折叠处理,使得超过指定宽度的字符转到下一行输出
- pr命令 转换成适合打印的格式
- fmt命令 格式化段落
- sort可以对文本行进行排序,可以指定字段,分隔符,如果对数字排序则使用-n
- 统计文本行数主要有两种方式
- grep -c
- wc 这个命令也可以统计单词书,字符数等等
- cut命令可以用来选取文本列
- paste可以用来拼接文本列
- join命令可以以指定列来连接文本列,包括左连接,右连接等
- tr命令用来替换文件内容
流编辑
- sed 命令会从文件或者标准输入中一次读取一行数据,将其复制到缓冲区,然后读取
命令行或者脚本的编辑子命令,对缓冲区中的文本行进行编辑。重复此过程,一直到所有
的文本行都处理完毕 - sed命令适合编辑一个非常大的文本文件
- sed命令不会影响原始文件,处理结果我们可以重定向到硬盘
- sed的核心option
- -n 这样sed命令就不会写到标准输出了
- sed命令的三种工作方式
- 直接在命令行使用
sed [options] commands inputfile
- 将sed命令写入sed脚本
sed [options] -f script inputfile
- 在脚本中直接指定解释器
./scrpt inputfile
然后脚本第一行如下
#! /bin/sed
- 位置参数
- 通过行号定位
- 通过正则表达式定位
- 子命令p将缓冲区中的文本行执行输出操作
- sed常用的命令
- 选择文本
sed -n '1,3p' students.txt
- 替换文本,s子命令
- 删除文本, d子命令
- 追加文本, a子命令
- 插入文本, i 子命令
- 组合命令 多个sed命令组合在一起来完成
- 使用-e选项
- 使用分号执行多个子命令
- 对一个地址使用多个子命令
address {
command1
command2
command3
…
}
- sed 提供了-f 选项,通过这个选项,sed 命令可以从指定的脚本文件中读取子命令,然后对每个文本行依次执行各个子命令,其语法如下
sed -f script
文本处理利器awk命令
- 在绝大部分的 Linux 发行版中,默认安装的是 gawk, 即 GNU awk
- awk 是 Linux 以及 UNIX 环境中现有的功能最强大的数据处理工具
- 在许多 Linux 发行版中,/bin/awk 命令是/bin/gawk 命令的符号链接
- awk命令的基本语法
# actions 前面的左大括号需与 pattern 位于同一行中
awk pattern { actions }
pattern 和 actions 都是可选的,但是两者必须保证至少有一个。如果省略匹配模式 pattern,
则表示对所有的文本行执行 actions 所表示的操作;如果省略 actions,则表示将匹配成功的
行输出到屏幕
- awk 会自动逐行读取数据文件的所有文本行
- 执行awk的三种方式
- 通过命令行执行awk
# 注意单引号,'program-text'是要执行的命令
awk 'program-text' datafile
- 执行awk脚本
# 注意开头
#! /bin/awk -f
# awk 脚本中不能含有除 awk 语句之外的其他命令或者语句,例如 Shell 命令等
awk -f program-file file ..`
- 可执行脚本文件
- awk匹配模式
- 关系表达式,例如大于>、小于<或者等于==等
#打印第 2 列的成绩超过 80 的行
result='awk '$2 > 80 { print }' scores.txt'
- 正则表达式
#输出以字符 T 开头的行
result='awk '/^T/ { print }' scores.txt'
- 混合模式 支持使用逻辑运算符 &&、||或者!将多个表达式组合起来作为一个模式
- 区间模式 就是两个行以及之间的内容
- BEGIN模式 就相当于awk的一个前置处理
- END模式 后置处理
- awk变量类型分为字符串和数值,awk会根据环境自动判断类型
- 系统内置变量
- $0 记录变量,表示当前正在处理的记录
- $n 字段变量,其中 n 为整数,且 n 大于 1。表示第 n 个字段的值
- FS 字段分隔字符,默认值是空格或者制表符,可以在BEGIN中指定
- RS 记录分隔符,默认值是换行符,可以在BEGIN中指定
- 运算符和表达式
- 算术运算符
- 赋值运算符
- 条件运算符
- 逻辑运算符
- 关系运算符
…等等
- awk对也支持很多对字符串的函数,也提供了很多比如平方根等算数函数
- awk支持对数组
- 数组下标不一定是整数
- 数组可以遍历,但是按顺序遍历的条件是下标识连续的整数
- 支持for,if,continue等流程控制语句
- 当遇到next时,awk 会继续读取下一行数据
- awk 程序的格式化输出主要有print,printf,sprintf()
- awk 的程序与 Shell 的交互
- 管道,awk的管道和unix管道不是一个意思
- system函数,这个局限性比较大
基本文本处理
-
命令输出文本echo
- 多用于显示提示信息或者程序产生的数据
- echo可以搭配-e和制表符来制作表格
- echo默认自带一个换行符
-
文本的格式化输出
- 可以对文本的输出格式更加细致的控制
- 制表符\t搭配echo
- fold命令可以指定换行的宽度
- fmt可以格式化段落,可以完全替代fold
- rev反转字符串
- pr格式化文本页,比如每页的行数等
-
文本排序的核心命令是sort
- sort 命令中,可以指定列排序
文件的操作
- 文件权限-代表没有权限
- 文件的类型分为
- 普通文件,通常位于存储设备上,分为文本和二进制,/usr/bin里面的是二进制文件
- 目录
- 伪文件,不占用磁盘空间,提供的是一种服务,比如proc,设备文件,命名管道等等
- find命令主要是-name(区分大小写),-iname(不区分大小写),需要模糊匹配需要搭配通配符
- 比较文件diff,它也可以比较目录
- 文件描述符是一个索引值,指向内核为每个进程所维护的打开文件的记录表
- LINUX内核规定每个进程最多打开文件数为1048576个
- 每个进程都与3个文件描述符关联
- 标准输入 0
- 标准输出 1
- 标准错误 2
- 输出重定向基本语法
# [n]可以省略,n表述文件描述符,默认是1,注意,1和>没空格
cmd [n]> file
cmd 表示 Shell 命令,大于号>为重定向操作符,file 表示重定向的目标文件(可存在也可不存在,如果追加用>>)
9. 我们可以将标准错误和标准输出同时重定向
ls -lz &> filelist
- 使用输出重定向我们可以快速清空或创建一个文件
[root@linux chapter12]# :> errmsg
- 一组命令输出重定向
{ cmd1;cmd2;...; } [n]> file
- 输入重定向基本语法,默认文件描述符是0
cmd < file
- 输入输出结合使用
grep Bae > demo.txt < students.txt
- 我们可以通过输入重定向,来生成当前文档
[root@linux ~]# cat << eof
> This is a test file.
> There are two lines.
> eof
This is a test file.
There are two lines.
这里的eof是代表结束,可以用其它的标志,但是不能还有空格和制表符
15. 重定向操作符只对当前命令有效,使用 exec 命令可以永久性地重定向,它可以让重定向对当前 Shell 进程中的所有命令有效
[mozhiyan@localhost ~]$ echo "重定向未发生"
重定向未发生
# 标准输出重定向到log.txt,那么以后的标准输出都到了log.txt
[mozhiyan@localhost ~]$ exec >log.txt
[mozhiyan@localhost ~]$ echo "c.biancheng.net"
[mozhiyan@localhost ~]$ echo "蔡彬的博客"
# 恢复标准输出到显示器,因为2也是到显示器,这样1也到显示器了,一切OK
[mozhiyan@localhost ~]$ exec 1>&2
[mozhiyan@localhost ~]$ echo "重定向已恢复"
重定向已恢复
[mozhiyan@localhost ~]$ cat log.txt
c.biancheng.net
蔡彬的博客
- 所有对文件描述符的操作方式 exec 都支持
子Shell与进程处理
- 在脚本被执行的时候,当前的 Shell 会启动另外一个 Shell 实例,这样,每个SHELL脚本都运行在父SHELL的一个子进程中,可以使用下面的命令显示SHELL的层次
echo $SHLVL
- 当用户登录Linux或者UNIX后,操作系统会根据用户/etcpasswd文件中的配置启动一个SHELL进程,如果某个用户不需要登录,则可以将该用户的默认的 Shell 程序设置为/sbin/nologin
- 一旦SHELL脚本执行完毕,该SHELL子进程随即结束,并且返回到父SHELL中
,这个过程不影响父SHELL中的环境,比如我们再脚本中修改工作目录,退出后父SHELL的工作目录并没有改变 - 如果用户想要在当前的SHELL中执行脚本,可以使用下面的方式
[root@linux chapter13]# . ./ex13-1.sh
这样在脚本里边就可以改变SHELL的路径了,使用圆点或者source命令使被调用脚本在当前SHELL进程中执行
- SHELL命令分为内部命令,保留字和外部命令
- 内部命令 包含在SHELL工具包中的命令,例如cd
- 保留字就比如break这种
- 外部命令以磁盘文件的形式存在于磁盘中,用户登录时并不加载到内存,而是在需要的时候才被调进内存,绝大部分的SHELL命令都是外部命令,比如ls,外部命令一般位于/usr/bin及/usr/sbin等目录中,其中/usr/sbin中的命令通常与系统管理有关
- 用户在执行内部命令时不创建子SHELL,执行外部命令创建子SHELL,完成后返回父SHELL
- 在子SHELL中执行命令,可以在圆括号后面加上&放到后台执行
- 圆括号结构
(command1;command2;command3;...)
- 子 Shell 中的代码可以访问父 Shell 中的变量的值。并且,当变量的值在子 Shell 中修改之后,在父 Shell 中可以获得变化之后的值
- 父SHELL无法获得子SHELL变量的值,但是我们可以通过临时文件或者命名管道的方式获取
- 一个作业可以有多个进程,比如下面有三个进程
[root@linux chapter13]# man ls | grep long | more
- Ctrl+Z可以将前台作业挂起,然后用fg切换回来,可以用jobs命令查看后台列表,+号的作业是fg默认的,可以用disown命令或者kill删除
- 信号在 Linux 系统中是非常重要的一种通信机制。信号在软件层次上模拟了硬件中断机制。因此,简单地讲,信号即软件中断,比如KILL命令就是一种信号
- 用户可以对信号设定专门的处理函数,使用trap命令
trap [[arg] sigspec ...]
Shell 脚本调试技术
- if [ $x == “exit” ] 中括号两边一定要有空格
- 变量一定要用$引用
- 调试技术
- echo,就像js中的alert
- trap
在 Shell 脚本执行的时候,会产生 3 个所谓的伪信号,分别为 EXIT、ERR 以及 DEBUG。其中,EXIT 信号在退出某个函数或者某个脚本执行完成时触发,ERR 信号在某条命令返 回非 0 状态时触发,DEBUG 信号在脚本的每一条命令执行之前触发,为这三个信号是由 Shell 产生的,而其他的信号是由操
作系统产生的
trap 'command' signal
我们可以使用它来在命令出错,或者函数返回非0时,结合ERR定义一个函数来输出错误
-
tee 主要是用在管道调试,tee 命令会从标准输入读取数据,将其内容输出到标准输出设备,同时又可将内容保存成文件
-
调试钩子
就是一个全局变量,来控制调试开关