1. 认识 Shell Scripts
- 学习 Shell 的疑惑
- 如何启动命令行以及接下来做什么?
- 如何使用 shell脚本来自动处理系统管理任务,包括从检测系统统计数据和数据文件到为你的老板生成报表?
- Shell 简介
- Shell 是一个用 C 语言编写的程序,Shell 既是一种命令语言,又是一种程序设计语言。
- Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
- Shell 类型
- /bin/bash
- /bin/tcsh
- /bin/dash
- /bin/csh
- /bin/sh
- /bin/zsh
执行 Shell 脚本
// 新增可执行权限 chmod +x test.sh
// 作为可执行程序 ./test.sh
// 作为解释器参数 /bin/sh test.sh
- 常见 Shell 操作终端
- Linux 控制终端
- Terminal
- GNOME Terminal
- Konsole Terminal
- Terminus
- Xterm
- XShell
理解 Shell 的父子关系
$ ps -f
$ bash $ ps -f 输入命令之后,一个子 shell 就出现了。第二个 ps -f 是在子 shell 中执行的。可以从显示结果中看到两个 bash shell 程序在运行。
$ bash $ bash $ bash $ ps --forest 在上面例子中,bash 命令被输入了三次。实际上创建了三个子 shell。ps --forest 命令展示了这些子 shell 间的嵌套结构。可以使用 exit 命令退出子 shell $ exit
进程列表
$ pwd;ls;cd /etc;pwd 在命令之间加入“;,指定要依次执行的一系列命令 $ (pwd;ls;cd /etc;pwd) 使用括号包含命令,成为进程列表
查看是否生成了子 shell,使用: $ echo $BASH_SUBSHELL
子shell用法
// 在后台睡眠10s $ sleep 10& // 查看后台进程 $ ps -f or $ jobs -l
//将进程列表置入后台 $ (sleep 2;echo $BASH_SUBSHELL;sleep 2)& // 创建备份 $ (tar -cf Rich.rar /home/rich;tar -cf My.tar /home/christine)&
//协程:在后台生成一个子shell,同时在这个子shell中执行命令。 // 进行协程处理,使用 coproc 命令 $ coproc sleep 10 $ coproc My_Job{sleep 10;
- 理解 shell 的内建命令
- 外部命令
也被称为文件系统命令,是存在于bash shell之外的程序。ps 就是一个外部命令,可以使用 which 和 type 命令找到
$ which ps $ type -a ps
当外部命令执行时,会创建一个子进程,这种操作叫做衍生(forking)。
- 内建命令
内建命令和外部命令的区别在于前者不需要使用子进程来执行。它们已经和 shell 编译成一体,作为 shell 工具的组成部分存在。可以利用 type 命令来了解某个命令是否是内建的。
$ type cd cd is a shell builtin
要注意,有些命令有多种实现。既有内建命令也有外部命令。
$ type -a echo echo is a shell builtin echo is /bin/echo $ type -a pwd pwd is a shell builtin pwd is /bin/pwd
2. Shell 基础
2.1. Hello World
#!/bin/bash ################ # Hello World # ################ # This script displays the date and who's #脚本用途说明及作者等信息描述 echo "This's is a shell script." #显示消息 echo -n "The time and date are: " #n表示在一行显示 echo "Hello,World!" # print "Hello,World!" date # print date. echo "User info for userid: $USER" #环境变量,用set命令可以查看一份完整的当前环境变量列表。 echo UID: $UID echo HOME: $HOME echo "The cost of the item is \$15." #美元需要使用\转义 days= 10 #用户自定义变量 echo $days # 有两种方法可以将命令赋给变量 test= `date` #用一对反引号把整个命令围起来 test= $(date) #使用$()格式 today= $(date+%y%m%d) #today变量被赋予格式化后的date命令的输出。
2.2. 变量
############ # 定义变量 # ############ your_name="qinjx" echo $your_name echo ${your_name} # 变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,例如下面的情况: for skill in Ada Coffe Action Java; do echo "I am good at ${skill}Script" done # 只读变量 myUrl="https://www.google.com" readonly myUrl myUrl="https://www.runoob.com" :<<EOF 运行脚本,结果如下: /bin/sh: NAME: This variable is read only. EOF ############ # 删除变量 # ############ unset variable_name
2.3. 字符串
################ # Shell 字符串 # ################ str='this is a string' your_name='zrg' str2="Hello, I know you are \"$your_name\"! \n" echo -e $str :<<EOF 输出结果为: Hello, I know you are "runoob"! EOF # 拼接字符串 # 使用双引号拼接 greeting="hello, "$your_name" !" greeting_1="hello, ${your_name} !" # 使用单引号拼接 greeting_2='hello, '$your_name' !' greeting_3='hello, ${your_name} !' # 获取字符串长度 string="abcd" echo ${#string} #输出 4 # 提取子字符串 string="runoob is a great site" echo ${string:1:4} # 输出 unoo # 查找子字符串 # 查找字符 i 或 o 的位置(哪个字母先出现就计算哪个): string="runoob is a great site" echo `expr index "$string" io` # 输出 4
2.4. 数组
# 定义 array_name=(value0 value1 value2 value3) # 读取数组 value=${array_name[n]} # 使用 @ 符号可以获取数组中的所有元素 echo ${array_name[@]} # 获取数组的长度 # 取得数组元素的个数 length=${#array_name[@]} # 或者 length=${#array_name[*]} # 取得数组单个元素的长度 lengthn=${#array_name[n]}
2.5. 注释
- 单行注释:以 # 开头的行就是注释
多行注释:
:<<EOF 注释内容... EOF # 或者是 :<<' 注释内容... ' :<<! 注释内容... !
2.6. 环境变量(Environment Parameter)
- 概念:环境变量(environment variable),用来存储有关 shell 会话和工作环境的信息。
全局环境变量和局部环境变量:
// 查看全局变量 $ env or $ printenv
// 查看某个全局环境变量 $ env HOME or $ echo $HOME
// set 命令会显示为某个特定进程设置的所有环境变量,包括全局变量、局部变量以及用户自定义变量。 $ set
设置用户自定义变量
$ my_variable=Hello
注意:所有环境变量名均使用大写字母,这是 bash shell 的标准惯例。自己创建的局部变量或是 shell 脚本,请使用小写字母。变量名区分大小写。
$ my_variable="Hello World"
// 设置全局变量 $ export my_variable="I am Global now"
// 删除环境变量 $ unset my_variable
注意:如果要用到变量,使用\(;如果要操作变量,不使用\)。
PATH、PS1 环境变量
// 全局环境变量 $ PATH=$PATH:/opt/test/scripts
// 自定义用户命令行的字符显示
PS1 默认提示符变量,如动态显示当前目录:
$ export PS1="[\u@\h \w]"
Table 1: PS1 变量可使用的参数值 \d 代表日期,格式为weekday month date,例如:"Mon Aug 1" \H 完整的主机名称。例如:我的机器名称为:fc4.linux,则这个名称就是fc4.linux \h 仅取主机的第一个名字,如上例,则为fc4,.linux则被省略 \t 显示时间为24小时格式,如:HH:MM:SS \T 显示时间为12小时格式 \A 显示时间为24小时格式:HH:MM \u 当前用户的账号名称 \v BASH的版本信息 \w 完整的工作目录名称。家目录会以 ~代替 \W 利用basename取得工作目录名称,所以只会列出最后一个目录 \# 下达的第几个命令 \$ 提示字符,如果是root时,提示符为:# ,普通用户则为:$ \[ 字符"[" \] 字符"]" \! 命令行动态统计历史命令次数 PS2 是副提示符变量,默认值是''> ''。PS2一般使用于命令行里较长命令的换行提示信息。可自定义设置如下:
$ export PS2="PS2 => "
另外,还有 PS3 和 PS4,因为这两个环境变量可能用得不多,所以在这就不介绍了,感兴趣的小伙伴可自行研究。
- 定位系统环境变量
登录时作为默认登录 shell
登录 shell 会从5个不同的启动文件里读取命令,其中 /etc/profile 是默认的 bash shell 主启动文件。$HOME/.bash_profile $HOME/.bashrc $HOME/.bash_login $HOME/.profile
- 作为非登录 shell 的交互式 shell
作为非登录 shell 的交互式启动的,它不会访问 /etc/profile 文件,只会检查 HOME 目录中的 .bashrc 文件。
.bashrc 文件有两个作用:一是查看/etc目录下通用的 bashrc 文件;二是为用户提供一个定制自己的命名别名和私有脚本函数的地方。 - 作为运行脚本的非交互式shell
系统执行 shell 脚本时使用,不同的地方在于它没有命令提示符。bash shell 提供了 BASH_ENV 环境变量,当 shell 启动一个非交互式 shell 进程时,它会检查这个环境变量来查看要执行的启动文件。
在大多数发行版中,存储个人用户永久性 bash shell 变量的地方是 $HOME/.bashrc 文件。但如果设置了 BASH_ENV 变量,那么记住,除非它指向的是 $HOME/.bashrc,否则应该将非交互式 shell 的用户变量放在别的地方。
数组变量
// 环境变量作为数组使用 $ mytest=(one two three four five) $ echo ${mytest[2]} three $ echo ${mytest[*]} one two three four five
//改变某个索引的值 $ mytest[2] = seven
//删除某个索引的值和删除整个数组 $ unset mytest[2] $ unset mytest
- 环境变量配置文件
- /etc/profile
- /etc/profile.d/*.sh
- ~/.bash_profile
- ~/.bashrc
- /etc/bashrc
- ~/.bash_logout
- ~/.bash_history
- 本地终端欢迎信息
- 登录后的欢迎信息
2.7. 重定向(Redirect)输入和输出
0 | 标准输入 |
---|---|
1 | 标准输出 |
2 | 标准错误输出 |
> | 默认为标准输出重定向,与 >1 相同 |
2>&1 | 把标准输出重定向到标准输出 |
&>file | 把标准输出和标准错误输出都重定向到 file 中 |
/dev/null | 是一个特殊文件,所有重定向到它的东西都丢弃掉 |
输出重定向
// 标准输出重定向 $ date > test $ date >> test
// 标准错误输出重定向 $ date 2>test $ date 2>>test
// 正确输出和错误输出同时保存 $ date > test 2>&1 $ date >> test 2>&1 $ date &>test $ date &>>test $ date >>test1 2>>test2
输入重定向
// 输入重定向 $ wc < test //wc 命令,默认情况下,会输出3个值:
- 文本的行数
- 文本的词数
文本的字节数
// 内联输入重定向(inline input redirection) $ wc << EOF
shell 会用PS2环境变量中定义的次提示符来提示输入数据
2.8. 通配符(Wildcard Character)
shell通配符(wildcard)
Table 3: shell 常见通配符 字符 含义 实例 ∗ 匹配 0 或多个字符 a*b a与b之间可以有任意长度的任意字符, 也可以一个也没有, 如aabcb, axyzb, a012b, ab。 ? 匹配任意一个字符 a?b a与b之间必须也只能有一个字符, 可以是任意字符, 如aab, abb, acb, a0b。 [list] 匹配 list 中的任意单一字符 a[xyz]b a与b之间必须也只能有一个字符, 但只能是 x 或 y 或 z, 如: axb, ayb, azb。 [!list] 匹配 除list 中的任意单一字符 a[!0-9]b a与b之间必须也只能有一个字符, 但不能是阿拉伯数字, 如axb, aab, a-b。 [c1-c2] 匹配 c1-c2 中的任意单一字符 如:[0-9] [a-z] a[0-9]b 0与9之间必须也只能有一个字符 如a0b, a1b… a9b。 {string1,string2,…} 匹配 sring1 或 string2 (或更多)其一字符串 a{abc,xyz,123}b a与b之间只能是abc或xyz或123这三个字符串之一。 shell 特殊字符 shell 除了有通配符之外,由shell 负责预先先解析后,将处理结果传给命令行之外,shell还有一系列自己的其他特殊字符。
Table 4: shell 特殊字符 字符 说明 IFS 由 <space> 或 <tab> 或 <enter> 三者之一组成(我们常用 space )。 CR 由 <enter> 产生。 = 设定变量。 $ 作变量或运算替换(请不要与 shell prompt 搞混了)。 > 重导向 stdout。 * < 重导向 stdin。 * 命令管线。 * & 重导向 file descriptor ,或将命令置于背境执行。 * ( ) 将其内的命令置于 nested subshell 执行,或用于运算或命令替换。 * { } 将其内的命令置于 non-named function 中执行,或用在变量替换的界定范围。 ; 在前一个命令结束时,而忽略其返回值,继续执行下一个命令。 * && 在前一个命令结束时,若返回值为 true,继续执行下一个命令。 * 两个竖线 在前一个命令结束时,若返回值为 false,继续执行下一个命令。 * ¡ 执行 history 列表中的命令。* shell 转义符
Table 5: shell 转义符号 字符 说明 ‘’(单引号) 又叫硬转义,其内部所有的shell 元字符、通配符都会被关掉。注意,硬转义中不允许出现’(单引号)。 “”(双引号) 又叫软转义,其内部只允许出现特定的shell 元字符:$用于参数代换 `用于命令代替 \(反斜杠) 又叫转义,去除其后紧跟的元字符或通配符的特殊意义。 shell 解析脚本过程
2.9. 位置参数(Positional Parameter)
特殊变量
Table 6: 位置参数 位置参数变量 说明 $n n为自然数。0代表命令本身,0代表命令本身,1到9代表第1到第9个参数(参数的值是执行该命令时,从9代表第1到第9个参数(参数的值是执行该命令时,从1开始依次输入的),十以上的参数要用大括号包含,如${10}。 $* 这个变量代表命令行中所有的参数(不包括$0),它把所有的参数当做一个整体对待。对其进行for循环遍历时,只会循环一次。 $@ 这个变量也代表命令行中所有的参数(不包括$0),它把所有的参数当做独立的个体对待。对其进行for循环遍历时,可循环多次。 $# 这个变量代表命令行中所有参数的个数(不包括$0)。 $$ 脚本运行的当前进程ID号 $! 后台运行的最后一个进程的ID号 $- 显示Shell使用的当前选项,与set命令功能相同。 $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 $ 与 $@ 区别:*
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。
读取参数
#!/bin/bash # using one command line parameter echo "执行的文件名:$0"; echo "第一个参数为:$1"; echo "第二个参数为:$2"; factorial=1 for ((number=1; number<=$1; number++)) do factorial=$[$factorial * $number] done echo "The factorial of $1 is $factorial"
2.10. 运算符(Operational Character)
方法1:declare
$ declare -i c=$a+$b $ echo $c
方法2:expr 或 let 运算工具
$ c=$(expr $a +$b) $ echo c
方法3:$((表达式)) 或 $[表达式]
$ var1=$((1+5)) $ var2=$[$var1*2] // 使用 $ 和 [] 将数学表达式围起来
注意:bash shell数学运算符支持整数运算。z shell(zsh)提供了完整的浮点数算术操作。
浮点运算解决方案
使用内建的bash计算器:bc $ bc 3.44 / 5 0 scale = 4 // 浮点运算由scale控制,默认值为0
注意:-q 选项可以不显示冗长的欢迎信息
$ bc -q
#!/bin/bash var1 = $(echo "scale=4;3.44 / 5" | bc) echo The answer is $var1
#!/bin/bash var1= 10.46 var2= 43.67 var3= 33.2 var4= 71 var5= $(bc << EOF scale= 4 a1= ($var1*$var2) a2= ($var3*var4) a1+b1 EOF ) echo "The final answer for this mess is $var5"
- 运算符
算术运算符
Table 7: 算术运算符 运算符 说明 举例 + 加法 `expr $a + $b` 结果为 30。 - 减法 `expr $a - $b` 结果为 -10。 ∗ 乘法 `expr $a \* $b` 结果为 200。 / 除法 `expr $b / $a` 结果为 2。 % 取余 `expr $b % $a` 结果为 0。 = 赋值 a=$b 将把变量 b 的值赋给 a。 == 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。 != 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。 注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]。
关系运算符
Table 8: 关系运算符 运算符 说明 举例 -eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。 -ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。 -gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。 -lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。 -ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。 -le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。 布尔运算符
Table 9: 布尔运算符 运算符 说明 举例 ¡ 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。 -o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。 -a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。 逻辑运算符
Table 10: 逻辑运算符 运算符 说明 举例 && 逻辑的 AND [ $a -lt 100 && $b -gt 100 ] 返回 false || 逻辑的 OR [ $a -lt 100 || $b -gt 100 ] 返回 true 注意:“|”,可通过 M-x org-entities-help <RET> 查看,Other > Misc
字符串运算符
Table 11: 字符串运算符 运算符 说明 举例 = 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。 != 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。 -z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。 -n 检测字符串长度是否为0,不为0返回 true。 [ -n "$a" ] 返回 true。 $ 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。 文件测试运算符
Table 12: 文件测试运算符 操作符 说明 举例 -b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。 -c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。 -d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。 -f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。 -g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。 -k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。 -p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。 -u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。 -r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。 -w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。 -x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。 -s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。 -e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。 -S 判断某文件是否 socket。 -L 检测文件是否存在并且是一个符号链接。
2.11. 变量测试
变量测试主要在 Shell 中使用,其它绝大多数语言是没有这个概念的,通用度不高。而且变量测试比较复杂,在实际写脚本的过程中完全可以用其它方式来取代变量测试。
变量置换方式 | y 没有设置 | y 为空 | y 设置值 |
---|---|---|---|
x=${y-变量} | x=newValue | x为空 | x=$y |
x=${y:-变量} | x=newValue | x=newValue | x=$y |
x=${y+变量} | x为空 | x=newValue | x=newValue |
x=${y:-变量} | x为空 | x为空 | x=newValue |
x=${y=变量} | x=newValue | x为空 | x=$y |
y=newValue | y值不变 | y值不变 | |
x=${y:=变量} | x=newValue | x=newValue | x=$y |
y=newValue | y=newValue | y值不变 | |
x=${y?变量} | newValue 输出到标准错误输出 | x为空 | x=$y |
x=${y:?变量} | newValue 输出到标准错误输出 | newValue 输出到标准错误输出 | x=$y |
x=${y-4} // 表示如果y不存在,那么x=4;如果y为空值,那么x为空值;如果y有值,那么x被赋y的值。
2.12. 退出
退出状态码
Table 14: 退出状态码 状态码 描述 0 命令成功结束 1 一般性未知错误 2 不适合的shell命令 126 命令不可执行 127 没找到命令 128 无效的退出参数 128+x 与Linux信号x相关的严重错误 130 通过Ctrl+C终止的命令 255 正常范围之外的退出状态码 $ echo $? 0
exit
echo 'Hello, World' exit 5
$ ./test Hello, World $ echo $? 5
#+end_src
2.13. 管道(Pipe)命令
选取命令: cut,grep 排序命令: sort,wc,uniq 双向重定向:tee 划分命令: tr,col,join,paste,expand 参数代换: split,xargs
2.14. 正则表达式(Regular Expression)
正则表达式 | 描述 | 示例 |
---|---|---|
\ | 转义符,将特殊字符进行转义,忽略其特殊意义 | a\.b匹配a.b,但不能匹配ajb,.被转义为特殊意义 |
^ | 匹配行首,awk中,^则是匹配字符串的开始 | ^tux匹配以tux开头的行 |
$ | 匹配行尾,awk中,$则是匹配字符串的结尾 | tux$匹配以tux结尾的行 |
. | 匹配除换行符\n之外的任意单个字符,awk则中可以 | ab.匹配abc或bad,不可匹配abcd或abde,只能匹配单字符 |
[] | 匹配包含在[字符]之中的任意一个字符 | coo[kl]可以匹配cook或cool |
[^] | 匹配[^字符]之外的任意一个字符 | 123[^45]不可以匹配1234或1235,1236、1237都可以 |
[-] | 匹配[]中指定范围内的任意一个字符,要写成递增 | [0-9]可以匹配1、2或3等其中任意一个数字 |
? | 匹配之前的项1次或者0次 | colou?r可以匹配color或者colour,不能匹配colouur不支持 |
+ | 匹配之前的项1次或者多次 | sa-6+匹配sa-6、sa-666,不能匹配sa-不支持 |
∗ | 匹配之前的项0次或者多次 | co*l匹配cl、col、cool、coool等 |
() | 匹配表达式,创建一个用于匹配的子串 | ma(tri)?匹配max或maxtrix不支持()()() |
{n} | 匹配之前的项n次,n是可以为0的正整数 | [0-9]{3}匹配任意一个三位数,可以扩展为[0-9][0-9][0-9]不支持 |
{n,} | 之前的项至少需要匹配n次 | [0-9]{2,}匹配任意一个两位数或更多位数不支持 |
{n,m} | 指定之前的项至少匹配n次,最多匹配m次,n<=m | [0-9]{2,5}匹配从两位数到五位数之间的任意一个数字不支持 |
| | 交替匹配 | 两边的任意一项ab(c | d)匹配abc或abd不支持 |
2.15. 流程控制
2.15.1. if-then
#!/bin/bash testuser = zrg # if grep $testuser /etc/passwd then echo "The bash files for user $testuser are:" ls -a /home/$testuser/.b* echo elif ls -d /home/$testuser then echo "The user $testuser has a directory" else echo "The user $testuser does not exist on this system." echo fi #test命令提供了在if-then语句中测试不同条件的途径。 #test命令可以判断三类条件:数值比较;字符串比较;文件比较
if-then 的高级特性
#!/bin/bash # (( expression )) expression 可以是任意的数学赋值或比较表达式。 var1=10 if(( $var1 ** 2 > 90)) then (( $var2 = $var1 ** 2)) echo "The square of $var1 is $var2." fi
#!/bin/bash # [[ expression ]] if[[ $USER == r* ]] then echo "Hello $USER" else echo "Sorry, I do not know you." fi
复合条件
格式:
[condition1] && [condition2] [condition1] || [condition2]
#!/bin/bash # testing compound comparisons # if [-d $HOME] && [-w $HOME/testing] then echo "The file exists and you can write to it." else echo "I cannot write to the file." fi
2.15.2. test
格式:
if test condition then commands fi
如果 test 命令中列出的条件成立,退出并返回退出状态码0;如果条件不成立,退出并返回非零的退出状态码。
#!/bin/bash $var = 10 if [$var -eq 5] then echo "The value $var are equal." else echo "The value $var are different." fi $var1 = baduser if [$USER != $var1] then echo "This is not $var1" else echo "Welcome $var" fi $var2 = baseall $var3 = hockey if [$var2 \> $var3] #>符号需要转义,防止解释成输出重定向 then echo "$var2 is greater than $var3" else echo "$var2 is less than $var3" fi
特别说明: 1.test命令和测试表达式使用标准的数学比较符号来表示字符串比较,而用文本代码来表示数值比较。 2.比较测试时,大写字母被认为是小于小写字母,但sort命令恰好相反。
#!/bin/bash var1 = testing var2 ='' if [-n $var1] then echo "The string '$var1' is not empty." else echo "The string '$var1' is empty." fi if [-z $var2] then echo "The string '$var2' is empty." else echo "The string '$var2' is not empty." fi
#!/bin/bash jump_directory=/home/arthur if [-d $jump_directory] then echo "The $jump_directory directory exists." else echo "The $jump_directory directory does not exists." fi
比较 | 描述 |
---|---|
-d file | 检查file是否存在并是一个目录 |
-e file | 检查file是否存在 |
-f file | 检查file是否存在并是一个文件 |
-r file | 检查file是否存在并可读 |
-s file | 检查file是否存在并非空 |
-w file | 检查file是否存在并可写 |
-x file | 检查file是否存在并可执行 |
-O file | 检查file是否存在并属当前用户所有 |
-G file | 检查file是否存在并且默认组与当前用户相同 |
file1 -nt file2 | 检查file是否比file2 |
file1 -ot file2 | 检查file是否比file2旧 |
2.15.3. case
格式:
case $变量名 in 模式1) 命令序列1;; 模式2) 命令序列2;; *) 默认执行的命令序列;; esac
#!/bin/bash case $action in start | begin) echo "start something" echo "begin something";; stop | end) echo "stop something" echo "end something";; *) echo "Ignorant.";; esac
2.15.4. for
格式:
for var in list do commands done
#!/bin/bash # # basic for command for country in China America India Japen do echo "The next state is $country" done # another example of how not to use the for command # 1.使用转义字符(反斜线) # 2.使用双引号 for test in I don\'t know if "this'll" work do echo "word:$test" done # using a variable to hold the list list="China America India Japen" list=$list" Connecticut" for country in $list do echo "Welcome to $country" done # reading values from a file file="states" # 修改IFS环境变量的值,使其只能识别换行符 IFS=$'\n' for state in $(cat $file) do echo "Visit beautiful $state" done # iterate through all the files in a directory for file in $HOME/* /etc/nginx/* do if [-d "$file"] then echo "$file is a directory." elif [-f "$file"] then echo "$file is a file." fi done # C-style for loop # for (( i=1; i <= 10; i++)) do echo "The next number is $i" done # multiple variable for (( a=1; b=10;a <= 10; a++, b++)) do echo "$a - $b" done
处理循环的输出
可以对循环的输出使用管道或进行重定向,通过在 done 命令之后添加一个处理命令来实现:
for file in /home/zrg/* do ... done > output.txt
2.15.5. while
格式:
while test command do other commands done
# while command test var1=10 while [ $var1 -gt 0 ] do echo $var1 var1=$[ $var1 - 1 ] done
2.15.6. until
until 命令和 while 命令完全相反。 格式:
until test command do other commands done
1: #!/bin/bash 2: # using the until command 3: var1=100 4: until [ $var1 -eq 0 ] 5: do 6: echo $var1 7: var1=$[ $var1 -25 ] 8: done
循环处理文件数据-处理
1: #!/bin/bash 2: # changing the IFS value 3: IFS.OLD=$IFS 4: IFS=$'\n' 5: for entry in $(cat /etc/passwd) 6: do 7: echo "Values in $entry -" 8: IFS=: 9: for value in $entry 10: do 11: echo "$value" 12: done 13: done 14: # 该脚本使用了两个不同的 IFS 的值来解析数据,第一个 IFS 值解析出 /etc/passwd 文件中的单独的行,内部 for 循环接着将 IFS 的值修改为冒号,允许你从 /etc/passwd 的行中解析出单独的值。
2.15.7. break
1: #!/bin/bash 2: # -------------------------------- 3: # 跳出单个循环 4: # 1.breaking out of a for loop 5: for var1 in 1 2 3 4 5 6: do 7: if [ $var1 -eq 5] 8: then 9: break 10: fi 11: echo "Iteration number: $var1" 12: done 13: echo "The for loop is completed" 14: # 2.breaking out of a while loop 15: var1=1 16: while [ $var1 -lt 10 ] 17: do 18: if [ $var1 -eq 5] 19: then 20: break 21: fi 22: echo "Iteration number: $var1" 23: done 24: echo "The while loop is completed" 25: # -------------------------------- 26: # 跳出内部循环 27: # 3.breaking out of an inner loop 28: for(( a = 1; a<4; a++)) 29: do 30: echo "Outer loop: $a" 31: for((b = 1; b<100; b++)) 32: do 33: if [ $var1 -eq 5] 34: then 35: break 36: fi 37: echo "Inner loop: $b" 38: done 39: done 40: # --------------------------------- 41: # 跳出外部循环 42: # 4.breaking out of an outer loop 43: for(( a = 1; a<4; a++)) 44: do 45: echo "Outer loop: $a" 46: for((b = 1; b<100; b++)) 47: do 48: if [ $var1 -eq 5] 49: then 50: break 2 51: fi 52: echo "Inner loop: $b" 53: done 54: done
2.15.8. continue
1: # 1.using the continue command 2: for((var1 = 1; var1<15; var1++)) 3: do 4: if [$var1 -gt 5] && [$var1 -lt 10] 5: then 6: continue 7: fi 8: echo "Iteration number: $var1" 9: done 10: # 2.improperly using the continue command in a while loop 11: var1=1 12: while echo "while iteration: $var1" 13: [ $var1 -lt 15 ] 14: do 15: if [ $var1 -gt 5] && [$var1 -lt 10] 16: then 17: continue 18: fi 19: echo "Inside iteration number: $var1" 20: var1 = $[$var1 +1] 21: done 22: # 3.continuing an outer loop 23: for(( a = 1; a<5; a++)) 24: do 25: echo "Interation : $a" 26: for((b = 1; b<3; b++)) 27: do 28: if [ $b -gt 2] && [$a -lt 4] 29: then 30: continue 2 31: fi 32: var3=$[$a+$b] 33: echo "The result of $a * $b is $var3" 34: done 35: done
2.16. 处理用户输入和数据呈现
2.16.1. 命令行参数
2.16.2. 数据呈现
2.17. 控制脚本
3. Shell 高级
3.1. 函数
3.2. 图形化桌面的脚本编程
3.2.1. 创建文本菜单
3.2.2. 制作窗口
3.3. 其它 Shell
4. 实用的脚本收集
4.1. 查找可执行文件
#!/bin/bash # finding files in the PATH IF=: for folder in $PATH do echo "$folder:" for file in $folder/* do if [-x $file] then echo "$file" fi done done
#!/bin/bash # process new user accounts input = "users.csv" while IFS=',' read -r userid name do echo "adding $userid" useradd -c "$name" -m $userid done < "$input"
4.2. 编写简单的脚本实用工具
4.2.1. 归档
4.2.2. 管理用户账户
4.2.3. 检测磁盘空间
4.3. 创建与数据库、Web及E-Mail相关的脚本
4.4. 发送消息
4.5. 获取格言
4.6. 编造借口
4.7. 在当前目录及指定子目录深度下创建.gitignore文件
#!/bin/sh for dir in `find ./ -mindepth 2 -maxdepth 4 -type d` do echo $dir `touch $dir/.gitignore` echo "*">$dir/.gitignore done
4.8. 解决 dpkg: warning: files list file for package 'x' missing
for package in $(sudo apt install catdoc 2&1 |grep "warning: files list file for package'" |grep -Po "[^'\n ]+'" |grep -Po "[^']+"); do sudo apt install --reinstall "$package" done
4.9. 删除大文件的前n行
tail -n +10 old_file>new_file mv new_file old_file
4.10. 打包文件并上传阿里云及下载
#!/bin/bash version="$1" serviceName="$2" if [ ! -n "${version}" ] || [ ! -n "${serviceName}" ]; then echo "参数错误:接收2个参数,参数1:文件名称,如1.1.5,参数2:服务名称,如cpms" exit fi filename='' if [ "${serviceName}" == 'cpms' ]; then filename='pms' fi # Check pom.xml and target directory exist. pomFilePath="./pom.xml" targetPath="./target" if [ ! -f "${pomFilePath}" ] || [ ! -d "${targetPath}" ]; then echo "请将执行文件移动或复制到到服务代码根目录下运行!!!" exit fi # Check pom.xml file "<version>${filename}</version>" pomFileVersion=$(cat ${pomFilePath} | grep version | grep ${version}) if [ -z ${pomFileVersion} ]; then echo "pom.xml 版本与所传入版本(version)不一致" exit fi filename="${filename}-${version}.jar" sourcePath="./target/${filename}" dest="out-road-park-version/${serviceName}/${filename}" # Check file exist. echo "${sourcePath}" if [ ! -f ${sourcePath} ]; then # ## 1. Build and package .jar file mvn clean install -DskipTests fi ## 2. Upload to Aliyun OSS. Host="oss-cn-hangzhou.aliyuncs.com" Bucket="park-version" Id="Access ID" Key="Secret Key" ossHost=$Bucket.$Host resource="/${Bucket}/${dest}" contentType=$(file -ib ${sourcePath} | awk -F ";" '{print $1}') dateValue=$(TZ=GMT env LANG=en_US.UTF-8 date +'%a, %d %b %Y %H:%M:%S GMT') stringToSign="PUT\n\n${contentType}\n${dateValue}\n${resource}" signature=$(echo -en $stringToSign | openssl sha1 -hmac ${Key} -binary | base64) url=http://${ossHost}/${dest} echo "upload ${sourcePath} to ${url}" curl -i -q -X PUT -T "${sourcePath}" \ -H "Host: ${ossHost}" \ -H "Date: ${dateValue}" \ -H "Content-Type: ${contentType}" \ -H "Authorization: OSS ${Id}:${signature}" \ ${url}
#!/bin/bash host="oss-cn-shanghai.aliyuncs.com" bucket="bucket名" Id="AccessKey ID" Key="Access Key Secret" osshost=$bucket.$host source="objecetename" dest="localfilename" resource="/${bucket}/${source}" contentType="" dateValue="`TZ=GMT env LANG=en_US.UTF-8 date +'%a, %d %b %Y %H:%M:%S GMT'`" stringToSign="GET\n\n${contentType}\n${dateValue}\n${resource}" signature=`echo -en $stringToSign | openssl sha1 -hmac ${Key} -binary | base64` url=http://${osshost}/${source} echo "download ${url} to ${dest}" curl --create-dirs \ -H "Host: ${osshost}" \ -H "Date: ${dateValue}" \ -H "Content-Type: ${contentType}" \ -H "Authorization: OSS ${Id}:${signature}" \ ${url} -o ${dest}
4.11. 读取Excel文件
原理:将excel转换为csv格式,再读取
安装 "libreoffice-common" 和 "unoconv"
sudo apt-get update sudo apt-get install libreoffice-common unoconv
将 Excel 文件转换为 CSV 格式:
unoconv -f csv input.xlsx
读取 CSV 文件
#!/bin/bash #unoconv -f csv ~/Downloads/cpms-version.xlsx while IFS=',' read -r col1 col2 col3 do # 在这里处理读取的数据 echo "Column 1: $col1" echo "Column 2: $col2" echo "Column 3: $col3" done < cpms-version.csv
4.12. 根据参数执行指定 PHP 脚本(消息队列rabbitmq)
消息队列:启用消费者
#!/bin/bash route_category="$1" # 路由组名称 number="$2" # 消费者数量 expect_number="$2" # 预计启用消费者数量 routes=(user store system) # Check routes exists if [ ! -n "$route_category" ] || [ ! -n "$number" ]; then echo "错误:接收两个参数,参数1:路由组名称,参数2:运行消费者数量" exit fi # Check correctness of route category if [[ ! "${routes[@]}" =~ "$route_category" ]]; then echo "错误:非法的路由组名称" exit fi # If number less than 1, then let $number equal 1 if [ $number -lt 1 ]; then echo "警告:第二个参数值不能小于1" number=1 fi # Start execute cumstomer command echo "即将启用消费者队列......" actual_number=0 # 实际启用消费者数量 while (($number > 0)); do php /www/test/think rabbit_receive "$route_category" & let actual_number=actual_number+1 echo "第 $actual_number 个 $route_category 消费者队列已启用" let number=number-1 done # Get queue total_number=`ps -ef |grep rabbit_receive\ $route_category | wc -l` let total_number=total_number-1 echo "----------------------------------" echo "完成 $route_category 消费者队列启用" echo "预计启用数量:$expect_number" echo "实际启用数量:$actual_number" echo "当前 $route_category 消息队列总计启用数量:$total_number" echo "----------------------------------"
消息队列:Kill 启用消费者
#!/bin/bash route_category="$1" # 路由组名称 kill_number="$2" # 要杀死的消费者队列数量(可选),不传表示杀死全部 routes=(user store system) # Check routes exists if [ ! -n "$route_category" ]; then echo "错误:接收两个参数,参数1:路由组名称,参数2(可选):要杀死的消费者队列数量" exit fi # Check correctness of route category if [[ ! "${routes[@]}" =~ "$route_category" ]]; then echo "错误:非法的路由组名称" exit fi # ps -efw 查看所有进程的命令 # grep -w rabbit_receive\ $route_category 强制 PATTERN 仅完全匹配字词 # grep -v grep 在列出的进程中去除含有关键字“grep”的进程 # cut -c 9-15 截取输入行的第9个字符到第15个字符,而这正好是进程号PID # head -n $kill_number 指定列出要kill的PID # xargs kill -9 xargs命令是用来把前面命令的输出结果(PID)作为“kill -9”命令的参数,并执行该命令 echo "----------------------------------" if [ -n "$kill_number" ] && [ $kill_number -gt 0 ]; then ps -efw | grep -w rabbit_receive\ $route_category | grep -v grep | cut -c 9-15 | head -n $kill_number | xargs kill -9 echo "已 Kill $kill_number 个消费者队列" last_number=$(ps -efw | grep -w rabbit_receive\ $route_category | grep -v grep | cut -c 9-15 | wc -l) echo "剩余 $last_number 个 $route_category 消费者队列" else ps -efw | grep -w rabbit_receive\ $route_category | grep -v grep | cut -c 9-15 | xargs kill -9 all_kill_number=$(ps -efw | grep -w rabbit_receive\ $route_category | grep -v grep | cut -c 9-15 | wc -l) echo "已Kill $all_kill_number 个 $route_category 消费者队列,所有 $route_category 消费队列全部Kill完成" fi echo "----------------------------------"