Shell

脚本的执行方式

echo

  1. echo "hello world" 输出helloworld
  2. -n 不换行显示
  3. -e 支持控制字符

Shell脚本基础 - 图1

shell脚本的执行

  1. # vim hello.sh #创建脚本文件
  2. #!/bin/bash #第一行写入该字段,才算是shell脚本
  3. echo "hello world"
  4. # chmod +x hello.sh #添加执行权限
  5. #./hello.sh #执行脚本

Bash的基本功能

history 历史命令

查看历史命令

  1. history -c 清空历史命令
  2. history -w 将缓存的历史命令,记录到历史命令文件中

注意:如果用户不退出,历史命令是不会被计入到历史文件中的。

历史命令的调用

上下 调用之前的历史命令

!n 重复第n条命令

!! 重复执行上一条命令

!字串 重复执行最后一条以该字串开头的命令

!$ 重复上一条命令的最后一个参数

alias 命令别名

  1. alias 别名 = ‘原命令’
  2. 想要永久保存,需要写在 .bashrc

Bash常用快捷键

Shell脚本基础 - 图2

Shell脚本基础 - 图3

命令行开头|命令行尾|终止|清屏|删除/剪切|删除剪切之后|粘贴剪切内容|搜索|退出终端|暂停放入后台|暂停屏幕输出|恢复屏幕输出

输出重定向

Shell脚本基础 - 图4

wc 统计

  1. wc 123.txt #统计行数——字数——字节数——文件名
  2. wc -c 123.txt #只统计文件字节数

-c 统计字节数

-w 统计单词数

-l 统计行数

管道符

grep 行提取命令

-A 数字 列出符合条件的行,并且列出后续的n行

-B 数字 列出符合条件的行,并且列出前面的n行

-c 统计找到符合条件的字符串的次数

-i 忽略大小写

-n 输出行号

-v 反向查找

—color=auto 搜出的关键字用颜色显示

  1. grep "/bin/bash" /etc/passwd
  2. grep -A 3 "root" /etc/passwd #查找包含有“root”的行,并列出后续的 3 行
  3. grep -n "/bin/bash" /etc/passwd #查找可以登录的用户,并显示行号

bash中特殊符号

Shell脚本基础 - 图5

  1. 单引号,双引号和反引号

单引号:单引号里写了啥,就输出啥。

双引号:其中的变量,引用等操作都能被显示出来。

反引号:如果是命令,则会执行命令

  1. 小括号,中括号和大括号

说括号之前,先明白父shell和子shell。每次bash都会创建了bash,可以通过pstree查看

()执行一串命令时,需要重新开一个子 shell 进行执行
{}执行一串命令时,是在当前 shell 执行;
()和{}都是把一串的命令放在括号里面,并且命令之间用;号隔开;
()最后一个命令可以不用分号
{}最后一个命令要用分号;
{}的第一个命令和左括号之间必须要有一个空格;
()里的各命令不必和括号有空格;
()和{}中括号里面的某个命令的重定向只影响该命令,但括号外的重定向则影响到括号里的
所有命令。

bash变量

  1. # test=123
  2. # test="$test"456
  3. # echo $test
  4. 123456
  5. #叠加变量 test,变量值变成了 123456
  6. # test=$(date)
  7. # echo $test
  8. 2018 10 21 星期一 20:27:50 CST

变量的分类 ★

  1. 用户自定义变量
  2. 环境变量:有用户自定义的,也有系统自带的。总的来说就是对整个系统生效。环境变量建议大写。
  3. 位置参数变量:这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的。
  4. 预定义变量:是 Bash 中已经定义好的变量,变量名不能自定义,变量作用也是固定的。

Shell脚本基础 - 图6

用户定义变量

错误的定义方法:

  1. test = liao
  2. test=li ao
  3. 1test=liao

自定义变量的查看与删除

set 查看自定义变量

unset 变量名

环境变量

  1. export age = "18" #定义变量
  2. env #查看环境变量
  3. unset age #删除变量

PATH

叠加path内容:

  1. echo "$PATH":/root/sh

PS1变量

  1. PS1='[\u@\h:\A \W]\$ '
  2. 临时生效,配置文件在:/etc/bashrc

Shell脚本基础 - 图7

LANG语言变量

  1. echo $LANG #输出语系环境
  2. locale #直接查询当前语系
  3. vim /etc/sysconfig/i18n #修改linux语系
  4. vim /etc/locale.conf #centos7中的语言配置文件

位置参数变量

Shell脚本基础 - 图8

$n

$1 $2 代表执行脚本后面的参数,例如:

./test.sh 123 100

在下面代码里就是相加的意思

Shell脚本基础 - 图9

$@

类似循环显示,将输入的数字,一个一个输入进来。

  1. vim xunhuan.sh
  2. #!/bin/bash
  3. x=$@
  4. echo x
  5. # bash xunhuan.sh 10 11 12 13
  6. # 10 11 12 13

$*

将所有的数字参数,看成一个整体

$

所有数字参数的数量。

预定义变量

事先把名称。作用定义好,内容能自定义的变量。

$?

最后一次执行的命令的返回状态,如果这个变量的值为0,证明上一个命令正确执行。如果这个变量的值为非0,证明上一个命令执行不正确。

  1. ls #查看文件
  2. echo $? #输出 0
  3. sdkasdlksad #随便输入
  4. echo $? #输出非0的随机数

$$

当前进程的PID号

$!

后台运行最后一个进程的PID进程号

read 键盘接收

用来向脚本传送值,类似input,scanner。非常适合给其他用户使用

  1. read -t 10 -p "qwq:" test
  2. -p 提示信息: 在等待read输入时,输出提示信息
  3. -t 秒数 : read命令会一直等待用户输入,使用此选项可以指定等待时间
  4. -n 字符数: read命令只接受指定的字符数,就会执行
  5. -s 隐藏输入的数据:适用于输入机密信息
  6. #!/bin/bash
  7. read -t 30 -p "请输入数字1:" num1
  8. read -t 30 -p "请输入数字2:" num2
  9. sum=$((num1+num2))
  10. echo $sum

shell运算符

declare声明变量类型

Shell脚本基础 - 图10

  1. declare [+/-] [选项] 变量名
  2. declare -i c=$a+$b #声明变量是整数型,并且是a+b的和
  3. declare -r test=123 #设置为只读属性,不能添加删除,修改。
  4. declare -e JAVA_HOME=/root/url #设置环境变量

expr和let数值运算工具

  1. a=11
  2. b=22
  3. d=$(expr $a + $b) # + 两边必须有空格 不然会变成字符型
  4. 33
  5. let e=$a+$b
  6. 33

使用$((运算符))$[运算符](不推荐)

  1. a=11
  2. b=22
  3. f=$(($a+b$)) #此时内部有没有空格都无所谓
  4. 33

简易的计算机

  1. #!/bin/bash
  2. read -t 10 -p "请输入第一个数字: " num1 #10秒内,读取键盘的输入,根据提示信息
  3. read -t 10 -p "请输入第二个数字: " num2
  4. read -t 10 -p "请输入运算符: " ope #获取输入的运算符
  5. #如果ope为指定运算符,然后打印计算结果,并且停止程序
  6. [ "$ope" == "+" ] && echo "$(($num1 + $num2))" && exit
  7. [ "$ope" == "-" ] && echo "$(($num1 - $num2))" && exit
  8. [ "$ope" == "*" ] && echo "$(($num1 * $num2))" && exit
  9. [ "$ope" == "/" ] && echo "$(($num1 / $num2))" && exit
  10. echo "请输入正确的运算符" #如果运算符有问题,则输出该命令

变量的测试与内容的更换

Shell脚本基础 - 图11

环境变量配置文件

  1. source 配置文件 #刷新配置文件

Shell脚本基础 - 图12

1,2,5个是全局的,3,4个是当前用户的。

~/.bash_logout

该文件默认不会写入任何内容,但是有时候我们希望在退出时执行一些操作,就可以在该文件写入命令,比如备份数据,保存数据等。

shell登录信息

/etc/issue

本地终端登录时,提示的欢迎界面,我们可以自行修改。建议额外添加一个\t和\l即可。

Shell脚本基础 - 图13

Shell脚本基础 - 图14

/etc/issue.net

上面那个是本地终端的登录提示,我们常常用ssh登录,所以需要在这个文件中修改才能看到提示。并且还需要在/etc/ssh/sshd_config中加入如下内容才能提示:

  1. cat /etc/ssh/sshd_config
  2. #Banner none
  3. Banner /etc/issue.net #将前面的注释去掉,并且添加路径即可。

/etc/motd

这个文件就没有任何限制了,设置了后本地和远程终端都能使用。一般用来填写欢迎信息等提示信息的。

shell正则表达式

Shell脚本基础 - 图15

首先给grep命令添加匹配颜色

  1. vi /root/.bashrc
  2. alias grep = 'grep --color=auto'

常见简单的匹配原则

  1. grep "a*" liao.txt
  2. #查询所有的字符任意多次
  3. grep "aa*" liao.txt
  4. #查询带有a的行
  5. grep "aaa*" liao.txt
  6. #查询带有两个a以上的行
  7. grep "s..d" liao.txt
  8. #在s和d之间有两个字符的行
  9. grep "^M" liao.txt
  10. #以M开头的行
  11. grep "N$" liao.txt
  12. #以大写N结尾的行
  13. grep "s[ao]id" liao.txt
  14. #在s和id之间,有a或者o的行
  15. grep "[0-9]" liao.txt
  16. #查询匹配任意一个数字
  17. grep "[A-Z]" liao.txt
  18. #匹配A-Z
  19. grep "[^a-zA-Z]" liao.txt
  20. #匹配除了英文以外的任意一个字符

更难一些的匹配原则

  1. grep "a\{3\}" liao.txt
  2. #匹配出现3次a的行
  3. grep "1[0-9]\{10,\}" liao.txt
  4. #匹配1之后任意数字出现10次的行 (电话号)
  5. grep "tes\{1,4\}t" liao.txt
  6. #匹配te和t之间,s出现1到4次的行

扩展正则

Shell脚本基础 - 图16

其实正则表达式中本来就有这四个字符,只是shell觉得linux中可能很少用到,就把他分隔开了,实际用法并没啥变换。

  1. grep -E "lia+o" liao.txt
  2. #匹配liao,并且a可以无限多,只要他有
  3. grep -E "liao?1" liao.txt
  4. #匹配liao1,或者是lia1
  5. grep -E "liao|wuhu" liao.txt
  6. #匹配包含liao和wuhu的行
  7. grep -E "liao is (geji|ba)" liao.txt
  8. #会匹配 liao is geji 或者 liao is ba

字符串处理值${}

1.获取字符串长度

  1. # VAR="hello world"
  2. # echo $VAR
  3. hello world
  4. # echo ${#VAR}
  5. 12

2.字符切片

格式:

${parameter:offset}

${parameter:offset:length}

截取从 offset 个字符开始,向后 length 个字符。

  1. # VAR='HELLO WORLD!'
  2. # echo ${VAR:0:5}
  3. HELLO #截取前五个字符
  4. # echo ${VAR:(-1)}
  5. ! #截取末尾的字符
  6. echo ${VAR:(-2)}
  7. D! #截取末尾两个字符
  8. # echo ${VAR:(-3):2}
  9. LD

3.替换字符串

格式:${parameter/pattern/string}

  1. # VAR='hello world world!'
  2. 将第一个 world 字符串替换为 WORLD
  3. # echo ${VAR/world/WORLD}
  4. hello WORLD world!
  5. 将全部 world 字符串替换为 WORLD
  6. # echo ${VAR//world/WORLD}
  7. hello WORLD WORLD!
  8. 替换正则匹配为空:
  9. # VAR=123abc
  10. # echo ${VAR//[^0-9]/} #去掉所有的字母
  11. 123
  12. # echo ${VAR//[0-9]/} #去掉所有的数字
  13. abc

patterm 前面开头一个正斜杠为只匹配第一个字符串,两个正斜杠为匹配所有字符。

4.字符串提取

格式:
${parameter#word} # 删除匹配前缀
${parameter##word}
${parameter%%word} # 删除匹配后缀
${parameter%%word}

去掉左边,最短匹配模式,##最长匹配模式。

% 去掉右边,最短匹配模式,%%最长匹配模式。

  1. # URL="http://www.baidu.com/baike/user.html"
  2. //为分隔符截取右边字符串:
  3. # echo ${URL#*//}
  4. www.baidu.com/baike/user.html
  5. 以/为分隔符截取右边字符串:
  6. # echo ${URL##*/}
  7. user.html
  8. //为分隔符截取左边字符串:
  9. # echo ${URL%%//*}
  10. http:
  11. 以/为分隔符截取左边字符串:
  12. # echo ${URL%/*}
  13. http://www.baidu.com/baike
  14. 以.为分隔符截取左边:
  15. # echo ${URL%.*}
  16. http://www.baidu.com/baike/user
  17. 以.为分隔符截取右边:
  18. # echo ${URL##*.}
  19. html

5.变量状态赋值

${VAR:-string} 如果 VAR 变量为空则返回 string

${VAR:+string} 如果 VAR 变量不为空则返回 string

${VAR:=string} 如果 VAR 变量为空则重新赋值 VAR 变量值为 string

${VAR:?string} 如果 VAR 变量为空则将 string 输出到 stderr

  1. 如果变量为空就返回 hello world!:
  2. # VAR=
  3. # echo ${VAR:-'hello world!'}
  4. hello world!
  5. 如果变量不为空就返回 hello world!:
  6. # VAR="hello"
  7. # echo ${VAR:+'hello world!'}
  8. hello world!
  9. 如果变量为空就重新赋值:
  10. # VAR=
  11. # echo ${VAR:=hello}
  12. hello
  13. # echo $VAR
  14. hello
  15. 如果变量为空就将信息输出 stderr
  16. # VAR=
  17. # echo ${VAR:?这没变量啊}
  18. -bash: VAR: 这没变量啊

6.字符串颜色

字体颜色:

30:黑
31:红
32:绿
33:黄
34:蓝色
35:紫色
36:深绿
37:白色

字体背景颜色

40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色

显示方式

0:终端默认设置
1:高亮显示
4:下划线
5:闪烁
7:反白显示
8:隐藏

格式:

\033[1;31;40m # 1 是显示方式,可选。31 是字体颜色。40m 是字体背景颜色。

\033[0m # 恢复终端默认颜色,即取消颜色设置。

shell表达式与运算符

1.条件表达式

表达式 示例
[ expression ] [ 1 -eq 1 ]
[[ expression ]] [[ 1 -eq 1 ]]
test expression test 1 -eq 1 ,等同于[]

2.整数比较符

比较符 描述 示例
-eq,equal 等于 [ 1 -eq 1 ]为 true
-ne,not equal 不等于 [ 1 -ne 1 ]为 false
-gt,greater than 大于 [ 2 -gt 1 ]为 true
-lt,lesser than 小于 [ 2 -lt 1 ]为 false
-ge,greater or equal 大于等于 [ 2 -ge 1 ]为 true
-le,lesser or equal 等于小于 [ 2 -le 1 ]为 false

3.字符串比较符

运算符 描述 示例
== 等于 [ “a” == “a” ]为 true
!= 不等于 [ “a” != “a” ]为 false
> 大于序 在[]表达式中:[ 2 > 1 ]为 true
< 小于 在[]表达式中:[ 2 < 1 ]为 false
>= 大于等于 在(())表达式中:(( 3 >= 2 ))为 true
<= 小于等于 在(())表达式中:(( 3 <= 2 ))为 false
-n 字符串长度不等于 0 为真 VAR1=1;VAR2=””
[ -n “$VAR1” ]为 true
-z 字符串长度等于 0 为真 [ -z “$VAR1” ]为 false
str 字符串存在为真 [ $VAR2 ]为 false

需要注意的是,使用-z 或-n 判断字符串长度时,变量要加双引号。

举例:

  1. $a=1
  2. # [ -z $a ] && echo yes || echo no
  3. yes
  4. # [ -n $a ] && echo yes || echo no
  5. yes
  6. # 加了双引号才能正常判断是否为空
  7. # [ -z "$a" ] && echo yes || echo no
  8. yes
  9. # [ -n "$a" ] && echo yes || echo no
  10. no
  11. # 使用了双中括号就不用了双引号
  12. # [[ -n $a ]] && echo yes || echo no
  13. no
  14. # [[ -z $a ]] && echo yes || echo no
  15. yes

4.文件测试

测试符 描述 示例
-e 文件或目录存在为真 [ -e path ] path 存在为 true
-f 文件存在为真 [ -f file_path ] 文件存在为 true
-d 目录存在为真 [ -d dir_path ] 目录存在为 true
-r 有读权限为真 [ -r file_path ] file_path 有读权限为 true
-w 有写权限为真 [ -w file_path ] file_path 有写权限为 true
-x 有执行权限为真 [ -x file_path ] file_path 有执行权限为 true
-s 文件存在并且大小大于 0 为真 [ -s file_path ] file_path 存在并且大小大于 0 为 true

5.布尔运算符

运算符 描述 示例
非关系,条件取反 [ ! 1 -eq 2 ]为 true
-a 和关系,在[]表达中使用 [ 1 -eq 1 -a 2 -eq 2 ]为 true
-o 或关系,在[]表达中使用 [ 1 -eq 1 -o 2 -eq 1 ]为 true

6.逻辑判断符

判断符 描述
&& 逻辑和,在[[]]和(())表达式中 或判断表达式是否为真时使用
|| 逻辑或,在[[]]和(())表达式中 或判断表达式是否为真时使用

示例:

  1. [[ 1 -eq 1 && 2 -eq 2 ]]为 true
  2. (( 1 == 1 && 2 == 2 ))为 true
  3. [ 1 -eq 1 ] && echo yes 如果&&前
  4. 面表达式为 true 则执行后面的
  5. [[ 1 -eq 1 || 2 -eq 1 ]]为 true
  6. (( 1 == 1 || 2 == 2 ))为 true
  7. [ 1 -eq 2 ] || echo yes 如果||前
  8. 面表达式为 false 则执行后面的

7.特殊运算工具

命令 描述
let 赋值并运算,支持++、—
expr 乘法*需要加反斜杠转义
bc 计算器,支持浮点运算、 平方等

示例:

  1. let VAR=(1+2)*3 ; echo $VAR
  2. let x++;echo $x #每执行一次 x 加 1
  3. let x+=2 #每执行一次 x 加 2
  4. expr 1 \* 2 #运算符两边必须有空格
  5. expr length "string" #获取字符串长度
  6. expr index "string" str #获取字符在字符串中出现的位置
  7. expr match "string" s.* #获取字符串开始字符出现的长度
  8. echo "1.2+2" |bc
  9. echo "10^10" |bc
  10. echo 'scale=2;10/3' |bc #保留两位小数点
  11. [ $(echo "2.2 > 2" |bc) -eq 1 ] && echo yes || echo no

8.shell括号用途

括号样式 用途
() 优先计算|数组|匹配分组
(()) 表达式,只支持>,<,<=,||,&&,不支持-eq
$() 执行shell命令,与反撇号等效
$(()) 简单算数运算|支持三目运算
[] 条件表达式,里面不支持逻辑判断符
[[]] 条件表达式,只支持>,<,<=,||,&&,不支持-eq。支持=~模式匹配,也可以不用双引号也不会影响原意,比[]更加通用
$[] 简单算数运算
{} 对逗号(,)和点点(…)起作用,比如 touch {1,2}创建 1 和 2 文件,touch {1..3}创建 1、2 和 3 文件
${} 引用变量|字符串处理

9.shell流程控制

格式:if list; then list; [ elif list; then list; ] … [ else list; ] fi

单分支

  1. if 条件表达式; then
  2. 命令
  3. fi

示例:

  1. #!/bin/bash
  2. N=10 #设置变量
  3. if [ $N -gt 5 ]; then #如果N大于5则
  4. echo yes #输出 yes
  5. fi

双分支

  1. if 条件表达式; then
  2. 命令
  3. else
  4. 命令
  5. fi

示例1:

  1. #!/bin/bash
  2. N=10
  3. if [ $N -lt 5 ]; then #如果10小于5
  4. echo yes #正确了输出yes
  5. else
  6. echo no #要么输出no
  7. fi

示例 2:判断 crond 进程是否运行

  1. #!/bin/bash
  2. NAME=cround #进程名称
  3. NUM=$(ps -ef |grep $NAME |grep -vc grep)
  4. #查看全部进程|搜索进程并打印|输出除了grep意外的全部进程(此时就是cround+grep,所以需要用到-vc)
  5. if [ $NUM -eq 1 ]; then #如果等于1
  6. echo "$NAME running" #正在运行
  7. else
  8. echo "$NAME not running"
  9. fi

示例3:检查主机是否存活

/dev/null 为一个黑洞文件,你输入任何信息,都无法保存。

  1. #!/bin/bash
  2. if ping -c 1 58.247.214.47 >/dev/null; then
  3. #将ping的消息输入到/dev/null中,命令成功则为yes
  4. echo "ok"
  5. else
  6. echo "NO"
  7. fi

if 语句可以直接对命令状态进行判断,就省去了获取$?这一步!

多分支

  1. if 条件表达式; then
  2. 命令
  3. elif 条件表达式; then
  4. 命令
  5. else
  6. 命令
  7. fi

当不确定条件符合哪一个时,就可以把已知条件判断写出来,做相应的处理。

示例1:

  1. #!/bin/bash
  2. read -t 30 -p "输入您的成绩:" N
  3. if [ $N -ge 95 ]; then
  4. echo "您的成绩:优!"
  5. elif [ $N -ge 85 ]; then
  6. echo "您的成绩:良!"
  7. elif [ $N -ge 75 ]; then
  8. echo "您的成绩:一般!"
  9. else
  10. echo "您的成绩太差了"
  11. fi

示例 2:根据 Linux 不同发行版使用不同的命令安装软件

  1. #!/bin/bash
  2. if [ -e /etc/redhat-release ]; then
  3. yum install vim -y
  4. elif [ $(cat /etc/issue | cut -d'' -f1) == "Ubuntu"]; then
  5. apt-get instann wget -y
  6. else
  7. echo "您不是Centos或者Ubuntu系统"
  8. fi

for语句

格式:for name [ [ in [ word … ] ] ; ] do list ; done

  1. for 变量名 in 取值列表; do
  2. 命令
  3. done

示例:

  1. #!/bin/bash
  2. for i in {1..3}; do
  3. echo $i
  4. done

或者这样写:

  1. #!/bin/bash
  2. for i in "$@"; { # $@是将位置参数作为单个来处理
  3. echo $i
  4. }
  5. bash for2.sh 1 23 343 5
  6. 1
  7. 23
  8. 343
  9. 5

默认 for 循环的取值列表是以空白符分隔

  1. #!/bin/bash
  2. for i in 12 34; do
  3. echo $i
  4. done
  5. # bash test.sh
  6. 12
  7. 34

如果想指定分隔符,可以重新赋值$IFS 变量:

  1. #!/bin/bash
  2. OLD_IFS=$IFS
  3. IFS=":"
  4. for i in $(head -1 /etc/passwd); do
  5. echo $i
  6. done
  7. IFS=$OLD_IFS # 恢复默认值
  8. # bash test.sh
  9. root
  10. x
  11. 0
  12. 0
  13. root
  14. /root
  15. /bin/bash

for 循环还有一种 C 语言风格的语法,常用于计数、打印数字序列: for (( expr1 ; expr2 ; expr3 )) ; do list ; done

  1. #!/bin/bash
  2. for ((i=1;i<=5;i++)); do # 也可以 i--
  3. echo $i
  4. done

示例 1:检查多个主机是否存活

  1. #!/bin/bash
  2. for ip in 192.168.128.{1..254}; do #将ip赋值1到254
  3. if ping -c 1 $ip > /dev/null; then #如果成功打印
  4. echo "$ip OK"
  5. else
  6. echo "$ip NO"
  7. fi
  8. done

示例 2:检查多个域名是否可以访问

  1. #!/bin/bash
  2. URL="www.baidu.com www.sina.com www.jd.com"
  3. for url in $URL; do
  4. HTTP_CODE=$(curl -o /dev/null -s -w %{http_code} http://$url)
  5. #通过安静模式(-s)将输出信息打印到/dev/null(-o),打印http状态码(-w)
  6. if [ $HTTP_CODE -eq 200 -o $HTTP_CODE -eq 301 ]; then
  7. #如果状态码为200或者301则为true
  8. echo "$url OK."
  9. else
  10. echo "$url NO!"
  11. fi
  12. done

while语句

格式:while list; do list; done

  1. while 条件表达式; do
  2. 命令
  3. done

示例1:

  1. #!/bin/bash
  2. N=0
  3. while [ $N -lt 5 ]; do
  4. echo $N
  5. let N++
  6. done

当条件表达式为 false 时,终止循环。

示例 2:条件表达式为 true,将会产生死循环

  1. #!/bin/bash
  2. while true; do
  3. echo "yes"
  4. done

还可以条件表达式用冒号,冒号在 Shell 中的意思是不做任何操作。但状态是 0,因此为 true:

  1. #!/bin/bash
  2. while :; do
  3. echo "yes"
  4. done

示例 3:逐行处理文本

要想使用 while 循环逐行读取 a.txt 文件,有三种方式:

  1. # cat a.txt
  2. a b c
  3. 1 2 3
  4. x y z

方式1:

  1. #!/bin/bash
  2. cat ./a.txt | while read LINE; do
  3. echo $LINE
  4. done

方式2:

  1. #!/bin/bash
  2. while read LINE; do
  3. echo $LINE
  4. done < ./a.txt

方式3:

  1. #!/bin/bash
  2. exec < ./a.txt # 读取文件作为标准输出
  3. while read LINE; do
  4. echo $LINE
  5. done

与 while 关联的还有一个 until 语句,它与 while 不同之处在于,是当条件表达式为 false 时才循 环,实际使用中比较少,这里不再讲解。

break和continue

break 是终止循环。 continue 是跳出当前循环。

示例1: break

  1. #!/bin/bash
  2. N=0
  3. while true; do
  4. let N++ #n加1
  5. if [ $N -eq 5 ]; then #如果N等于5了
  6. break #跳出循环
  7. fi
  8. echo $N
  9. done

示例2:continue

  1. #!/bin/bash
  2. N=0
  3. while [ $N -lt 5 ]; do #当N小于5
  4. let N++ #N加1
  5. if [ $N -eq 3 ]; then #如果等于3
  6. continue #跳过
  7. fi
  8. echo $N
  9. done

注意:continue 与 break 语句只能循环语句中使用。

case语句

case 语句一般用于选择性来执行对应部分块命令。

格式:case word in [ [(] pattern [ | pattern ] … ) list ;; ] … esac

  1. case 模式名 in
  2. 模式 1)
  3. 命令
  4. ;;
  5. 模式 2)
  6. 命令
  7. ;;
  8. *)
  9. 不符合以上模式执行的命令
  10. esac

每个模式必须以右括号结束,命令结尾以双分号结束。

示例1:

  1. #!/bin/bash
  2. case $1 in #给输入的第一个字符赋值
  3. start) #如果为start
  4. echo "start."
  5. ;;
  6. stop) #如果为stop
  7. echo "stop."
  8. ;;
  9. restart)#如果为restart
  10. echo "restart."
  11. ;;
  12. *) #如果都不是
  13. echo "Usage: $0 {start|stop|restart}" #$0脚本名称
  14. esac

上面例子是不是有点眼熟,在 Linux 下有一部分服务启动脚本都是这么写的。

模式也支持正则,匹配哪个模式就执行那个:

  1. #!/bin/bash
  2. case $1 in
  3. [0-9])
  4. echo "This is Number"
  5. ;;
  6. [a-z])
  7. echo "This is letter"
  8. ;;
  9. '-h'|'--help')
  10. echo "help"
  11. ;;
  12. *)
  13. echo "Input error"
  14. exit
  15. esac

select语句

select 是一个类似于 for 循环的语句。

格式:select name [ in word ] ; do list ; done

  1. select 变量 in 选项 1 选项 2; do
  2. break
  3. done

示例1:

  1. #!/bin/bash
  2. select mysql_version in 5.1 5.6; do
  3. echo $mysql_version
  4. done

用户输入编号会直接赋值给变量 mysql_version。作为菜单用的话,循环第二次后就不再显示菜单 了,并不能满足需求。

示例2:

  1. #!/bin/bash
  2. while true; do #一直循环
  3. select mysql_version in 5.1 5.6; do
  4. echo $mysql_version #输出字符
  5. break #停止select循环
  6. done
  7. done

如果再判断对用户输入的编号执行相应的命令,如果用 if 语句多分支的话要复杂许多,用 case 语 句就简单多了。

  1. #!/bin/bash
  2. PS3="选择虚拟机编号:" #更换界面提示符
  3. while true; do
  4. select mysql in 5.1 5.6; do
  5. case $mysql in
  6. 5.1)
  7. echo "mysql 5.1"
  8. break
  9. ;;
  10. 5.6)
  11. echo "mysql 5.6"
  12. break
  13. ;;
  14. quit)
  15. echo "quit bye!"
  16. exit
  17. ;;
  18. *)
  19. echo "error!!!!!"
  20. break
  21. esac
  22. done
  23. done

如果不想用默认的提示符,可以通过重新赋值变量 PS3 来自定义。这下就比较完美了!

数组与函数

1.函数

格式:

  1. func() {
  2. command
  3. }

function 关键字可写,也可不写。

示例1:

  1. #!/bin/bash
  2. func(){
  3. echo "This is a function"
  4. }
  5. func #调用函数
  6. func #调用函数

Shell 函数很简单,函数名后跟双括号,再跟双大括号。通过函数名直接调用,不加小括号。

示例2:函数返回值

  1. #!/bin/bash
  2. func(){
  3. VAR=$((1+1))
  4. return $VAR
  5. echo "This is a function"
  6. }
  7. func
  8. echo $? #输出上一个指令的返回值

return 在函数中定义状态返回值,返回并终止函数,但返回的只能是 0-255 的数字,类似于 exit。

示例3:函数传参

  1. #!/bin/bash
  2. func(){
  3. echo "hello $1"
  4. }
  5. func world

通过 Shell 位置参数给函数传参。

函数也支持递归调用,也就是自己调用自己。

示例:

  1. #!/bin/bash
  2. test(){
  3. echo $1
  4. sleep 1
  5. test hello
  6. }
  7. test

执行会一直在调用本身打印 hello,这就形成了闭环。

像经典的 fork 炸弹就是函数递归调用: :(){ :|:& };: 或 .(){.|.&};.

这样看起来不好理解,我们更改下格式:

  1. :() {
  2. :|:&
  3. };
  4. :
  5. bomb() {
  6. bomb|bomb&
  7. };
  8. bomb

分析下:

:(){ } 定义一个函数,函数名是冒号。
: 调用自身函数
| 管道符
: 再一次递归调用自身函数
:|: 表示每次调用函数”:”的时候就会生成两份拷贝。
& 放到后台
; 分号是继续执行下一个命令,可以理解为换行。
: 最后一个冒号是调用函数。
因此不断生成新进程,直到系统资源崩溃。 一般递归函数用的也少,了解下即可!

2.数组

数组是相同类型的元素按一定顺序排列的集合。
格式: array=(元素 1 元素 2 元素 3 …)
用小括号初始化数组,元素之间用空格分隔。
定义方法 1:初始化数组 array=(a b c)
定义方法 2:新建数组并添加元素 array[下标]=元素
定义方法 3:将命令输出作为数组元素 array=($(command))

数组操作:

  1. 获取所有元素:
  2. # echo ${array[*]} # *和@ 都是代表所有元素
  3. a b c
  4. 获取元素下标:
  5. # echo ${!a[@]}
  6. 0 1 2
  7. 获取数组长度:
  8. # echo ${#array[*]}
  9. 3
  10. 获取第一个元素:
  11. # echo ${array[0]}
  12. a
  13. 获取第二个元素:
  14. # echo ${array[1]}
  15. b
  16. 获取第三个元素:
  17. # echo ${array[2]}
  18. c
  19. 添加元素:
  20. # array[3]=d
  21. # echo ${array[*]}
  22. a b c d
  23. 添加多个元素:
  24. # array+=(e f g)
  25. # echo ${array[*]}
  26. a b c d e f g
  27. 删除第一个元素:
  28. # unset array[0] # 删除会保留元素下标
  29. # echo ${array[*]}
  30. b c d e f g
  31. 删除数组:
  32. # unset array

示例 1:讲 seq 生成的数字序列循环放到数组里面

  1. #!/bin/bash
  2. for i in $(seq 1 10); do
  3. array[a]=$i
  4. let a++
  5. done
  6. echo ${array[*]}

示例2:遍历数组元素

  1. #!/bin/bash
  2. IP=(192.168.1.1 192.168.1.2 192.168.1.3)
  3. for ((i=0;i<${#IP[*]};i++)); do
  4. echo ${IP[$i]}
  5. done
  6. #!/bin/bash
  7. IP=(192.168.1.1 192.168.1.2 192.168.1.3)
  8. for IP in ${IP[*]}; do
  9. echo $IP
  10. done

字符串截取和替换命令

1.cut

列提取命令

  1. cut [选项] 文件名

-f 列号: 提取第几列

-d 分隔符: 按照指定分隔符分割列

-c 字符范围:不依赖分隔符来区分列,而是通过字符范围(行首为 0)来进行字段提取

示例:1

  1. ID Name gender Mark
  2. 1 liao M 80
  3. 2 wuhu W 50
  4. 3 gouzi M 69

命令

  1. cut -f 2 liao.txt #提取第二列内容
  2. cut -f 2,3 liao.txt #提取第二第三列的内容
  3. cut -d ":" -f 1,3 /etc/passwd #以:作为分隔符,提取/etc/passwd文件的第一列和第三列
  4. grep "/bin/bash" /etc/passwd | grep -v "root" | cut -d ":" -f 1
  5. #查询passwd文件中的普通用户