shell编程
文章目录
快速入门
初识
shell是我们通过命令行与操作系统沟通的语言。
shell脚本可以直接在命令行中执行,也可以将一套逻辑组织成一个文件,方便复用。
Linux中常见的shell脚本有很多:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- zsh
但是Linux系统中一般默认使用bash
脚本格式
1.脚本需要以#!/bin/bash
开头
2.脚本需要可执行权限
脚本的常用执行方式
方式一:输入脚本的相对路径或者绝对路径
前提要求是:必须要给脚本增加可执行权限
方式二:sh(bash) + 脚本
这个可以不用给脚本可执行权限
创建第一个Shell脚本
# 创建一个文件
vim hello.sh
# vim中编辑的内容
#!/bin/bash
echo "hello world"
# 两种方式运行
# 第一种方式
sh hello.py
# 第二种方式
chmod u+x hello.sh
./hello.sh # 这里使用相对路径,也可以使用绝对路径
shell变量
shell变量的介绍
1.Linux Shell中的变量分为:系统变量和用户自定义变量
2.系统变量:$HOME
$SHELL
$UESR
等等
3.显示当前shell中所用的变量:set
命令
shell变量的定义
基本语法
1、定义变量:变量=数值
,注意:等号两边不能加空格
1.1变量定义的3中方式
name1='zhy' # 单引号定义字符串
name2="zhy" # 双引号定义字符串
name3=zhy # 也可以不加引号,同样表示字符串
1.2使用变量
使用变量的时候需要加上$
符号,或者${}
。花括号是可选的,主要是为了帮助解释器识别变量的边界。
name=zhy
echo $name # zhy
echo ${name} # zhy
echo ${name}ALG # zhyALG
echo $name ALG # zhy ALG
echo $nameALG # 错误
2、删除变量:unset 变量
name=zhy
unset name
echo $name # 输出空
3、声明静态变量:readonly 变量名
或者declare -r 变量名
,注意:静态变量不可以unset
name=zhy
readonly name # 写法1
declare -r name # 写法2
name=abc # 报错
4、将指令/命令的返回值赋值给变量,可以使用 反引号或者$()
案例
1.定义变量A,并赋值为666,打印变量A,撤销变量A
2.定义变量B,并赋值为888,打印并撤销
#!/bin/bash
A=666
echo A=$A # 输出A=666
echo "A=$A" # 这两种输出方式都可以
unset A
echo A=$A # 输出A= (这里无输出)
readonly B=888
echo B=$B # 输出B=888
3.将date指令的返回值赋值给变量C
#!/bin/bash
# 两种方式
C=`date`
C=$(date)
变量类型之设置环境变量
变量有两种类型:
1.自定义类型变量(局部变量)
子进程不能访问的变量
2.环境变量(全局变量)
子进程可以访问的变量
基本语法
1.将自定义变量改成环境变量
1.1.export 变量名=变量值
(设置shell的环境变量/全局变量),或者declare -x 变量名
1.2.source 配置文件
(让修改后的配置文件立刻生效)
name=zhy # 定义变量
# 第一种方式
export name # 也可以直接写成一行,即export name=zhy
# 这里需要 source 文件名
# 第二种方式
declare -x name
2.将环境变量改成自定义变量
2.1declare +x 变量名
export name=zhy # 创建一个环境变量
declare +x name # 设置成为自定义变量
案例
1.在/home 中创建一个文件homepath
设置环境变量HOMEPATH
2.查看HOMEPATH环境变量
3.在shell程序中查看环境变量
cd /home
vim homepath
#!/bin/bash
export HOMEPATH=/home # 设置环境变量
source homepath # 刷新配置文件
echo $HOMEPATH # 查看环境变量
# 在一个shell脚本中查看环境变量
vim a.sh
echo HOMEPATH=$HOMEPATH
sh a.sh
shell字符串
shell中的变量其实都是字符串,所以需要了解一下字符串的使用方法和注意事项
1.字符串使用单引号、双引号均可。区别在于单引号‘’
中间的内容不会执行,不会转义。双引号“”
中间的内容会执行,会转义。(前面说了不加引号也可以定义变量,而不加引号的效果同加了双引号)
name=zhy
echo 'hello $name \"ALG\"' # hello $name \"ALG\"
echo "hello $name \"ALG\"" # hello zhy "ALG"
echo hello $name \"ALG\" # hello zhy "ALG"
2.获取字符串长度,${#变量名}
name=zhy
echo ${#name} # 3
3.提取字符串,${变量名:left:right}(提取从left到right中间的字符)
name="hello zhy!"
echo ${name:6:9} # zhy
shell变量定义规范
1.变量名称可以由字母、 数字和下划线组成,但是不能以数字开头。5A= 200(x)
2.等号两侧不能有空格
3.变量名称一般习惯为大写,这是一个规范,我们遵守即可
shell注释
-
单行注释
# 我是一个单行注释
-
多行注释
:<<! 我是 一个 多行注释 !
默认变量-文件参数变量
初识
当我们执行一个shell脚本时 ,如果希望获取到命令行的参数信息,就可以使用到位置参数变量
比如: ./myshell.sh 100 200,这个就是一个执行shell的命令行 ,可以在myshell脚本中获取到参数
信息
基本语法
在执行shell脚本时,可以向脚本传递参数。$1是第一个参数,$2是第二个参数,以此类推。特殊的,$0是文件名(包含路径)。
参数 | 说明 |
---|---|
$n | n为数字,特殊的,$0是文件名(包含路径), $1~$9表示第一个参数到第九个参数,十以上的参数需要使用大括号包含,如${15} |
$# | 代表文件传入的参数个数 |
$* | 表示命令行中所有的参数,$* 把所有的参数看成一个整体 |
$@ | 表示命令行中所有的参数,$@把所有的参数当做单独的参数区别对待 |
$$ | 脚本当前运行的进程ID |
$? | 上一条命令的退出状态。0表示正常退出,其他值表示错误 |
$(command) | 返回command 这条命令的stdout |
command | 返回command 这条命令的stdout,同$(command) |
$n,n为数字,特殊的,$0是文件名(包含路径), $1~$9表示第一个参数到第九个参数,十以上的参数需要使用大括号包含,如${15}
$*,表示命令行中所有的参数,$*把所有的参数看成一个整体
$@,表示命令行中所有的参数,$@把所有的参数当做单独的参数区别对待
$#,表示命令行中参数的个数
案例
编写一个shell脚本pos.sh
,在脚本中获取命令行中的各个参数
vim pos.sh
#!/bin/bash
echo "文件名:"$0
echo "第一个参数:"$1
echo "第二个参数:"$2
echo "所有的参数:"$*
echo "所有的参数:"$@
echo "参数的个数:"$#
sh pos.sh 1 2 3
:<<!
输出的内容
文件名:pos.sh
第一个参数:1
第二个参数:2
所有的参数:1 2 3
所有的参数:1 2 3
参数个数:3
!
数组
初识
shell中的数组可以存放多个不同类型的值,但是只支持一维数组,初始化时不需要指明数组的大小。而且和其他语言一样数组的下标从0开始。
基本语法
1.定义:
有两种方式:
1.1.数组可以直接使用小括号表示,元素之间使用空格隔开。例如:
array=(123 abc "zhy")
1.2.也可以直接定义数组中某一个元素的值,而且可以不用连续的考空间。
array[0]=123
array[1]=abc
array[2]="zhy"
array[666]='ALG' # 可以直接在下标为666的地方定义一个值为'ALG'
2.读取数组中的某一个元素的值:
语法:
${array[index]}
例如:
array=(123 abc "zhy")
echo ${array[0]}
echo ${array[1]}
echo ${array[2]}
3.读取整个数组
语法:
${array[@]} # 第一种方式
${array[*]} # 第二种方式
例如:
array=(123 abc "zhy")
echo ${array[@]} # 第一种方式
echo ${array[*]} # 第二种方式
4.获取数组的长度
语法:
${#array[@]} # 第一种写法
${#array[*]} # 第二种写法
例如:
array=(123 abc "zhy")
echo ${#array[@]} # 第一种写法
echo ${#array[*]} # 第二种写法
运算符
初识
学会在shell中将变量进行计算,就需要运算符
基本语法
有三种方式:
方式一:$((运算式))
注意外面的()
不能省略
echo $((1 + 2) + 3)
方式二:$[运算式]
这种方式比较简单,比较推荐
echo $[(1 + 2) + 3]
方式三:expr 运算式(注意这里的运算式和上面的不一样
expr命令
初识
expr命令用户求表达式的值,语法如下:
expr 表达式
表达式注意事项:
1 expr 运算符之间需要有空格,而且必须要是用``(反引号)将计算的值返回
2.用反斜杠放在shell特定的字符前面
3.对包含空格和其他特殊字符的字符串要用引号括起来
4.expr会在stdout中输出结果。如果为逻辑关系表达式,则结果为真,stdout为1,否则为0。
5.expr的exit code:如果为逻辑关系表达式,则结果为真,exit code为0,否则为1。
6.运算符 \*(乘,要注意有一个\),\(除),+(加),-(减)
字符串表达式
1.length STRING
可以返回字符串的长度
str="hello world"
echo `expr length "$str"`
2.index STRING CHAR
CHAR
中任意单个字符在STRING
中最前面的字符位置,下标从1开始。如果在STRING
不存在CHAR
中的字符,就返回0。
str="hello world"
echo `expr index "$str" l` # l出现的第一个位置为3
3.substr STRING POSITION LENGHT
返回STRING
字符串中从POSITION
位置开始(下标从1开始),长度最大为LENGTH
的子串。如果POSITION
或者LRNGHT
为负数,0或者是非数值,就返回空字符串。
str="hello world"
echo `expr substr "%str" 1 5` # 下标从1开始,输出hello
整数表达式
expr支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
1.+ -
加减运算。两端参数会转换为整数,如果转换失败则报错。
2.\* / %
乘,除,取模运算。两端参数会转换为整数,如果转换失败则报错。
3.\(\)
可以该表优先级,但需要用反斜杠转义
例如:
a=1
b=2
echo `expr $a + $b`
echo `expr $a - $b`
echo `expr $a \* $b` # *需要转义
echo `expr $a / $b`
echo `expr $a % $b`
echo `expr \( $a + $b \) \* 3` # ()需要转义,计算(a+b)*3
逻辑关系表达式
1.|
,如果第一个参数非空且非0,则返回第一个参数的值,否则返回第二个参数的值(短路原则)。但要求第二个参数的值也是非空或非0,否则返回0。如果第一个参数是非空或非0时,不会计算第二个参数。
2.&
如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参为0或为空,则不会计算第二个参数。
3.< <= = == != >= >
比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。(使用的时候需要转义或者使用单引号隔开)
4.()
可以该表优先级,但需要用反斜杠转义
a=1
b=2
echo `expr $a \> $b` #0
echo `expr $a '<' $b` #1
echo `expr $a \>\= $b` #0
echo `expr $a '<=' $b` #1
c=0
d=3
echo `expr $c \& $d` # 0
echo `expr $d \& $a` # 3
echo `expr $c \| $d` # 3
echo `expr $d \| $a` # 3
案例
1.使用三种方式计算(1 + 2) * 3
#!/bin/bash
# 方式一
ANS1=$(((1 + 2) * 3))
echo "ans1=$ANS1"
# 方式二
ANS2=$[(1 + 2) * 3]
echo "ans2=$ANS2"
# 方式三
TEMP=`expr \( 1 + 2 \) \* 3`
echo "TEMP=$TEMP"
2.求出命令行中的两个参数的和
#!/bin/bash
ANS=$[$1 + $2]
echo ANS
read命令
基本语法即介绍
read命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code为1,否则为0。
当read遇到文件结束符的时候会自动结束,在while
循环中可以使用ctrl + d
的方式表示文件结束符。
read name
echo $name
可选参数:
-p: 后面可以接提示信息
-t:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令
案例
定义一个变量name
输入,并且设置提示语句和最多等待时间,最后输出在终端上。
read -p "Please Input into your name:" -t 30 name
echo "hello ${name}!"
echo命令
常见用法
echo
用于输出字符串,语法如下:
echo STRING
1、显示字符串
直接echo STIRNG
即可,可以使用双引号或者不加引号。注意:不能不加引号
echo "hello world"
echo hello world
2、显示转义字符串
在双引号或者不加引号的情况下,可以输出转义字符。但是如果想要使用\n \t
等更多的转义字符,需要使用echo-e
选项,才可以使用。
echo " \"hello world\" " # 输出“hello world"
echo \"hello world\" # 输出"hello wordl"
3、显示变量
定义变量,然后$变量
使用变量
name=zhy
echo "hello $name"
4、显示换行
使用echo -e
输出\n
的转义字符,而且必须使用双引号才可以使用。
echo -e "hello \n world"
5、显示不换行
使用echo -e
输出\c
选项,而且必须使用双引号才可以使用。
echo -e "hello\c "
echo "world"
# 输出 hello world
6、显示结果重定向
可以使用>
或者>>
方式进行字符串的输入重定向和追加重定向
echo "hello world" > hello.txt # 将字符串的内容覆盖式的加入hello.txt文件当中
echo "Hello World" >> hello.txt # 将字符串的内容添加在hello.txt文件的后面
# 最后得到一个文件hello.txt
# 文件的内容是hello world Hello World
7、原样输出字符串,不进行转义或者取变量
上面说活可以使用双引号或者不加引号的形式形式字符串或者转义字符串。如果使用单引号就可以输出字符串本身。
name=zhy
echo '$name \n' # 输出'$name \n'
8、显示命令的执行结果
使用``或者$()
的方式得到命令的结果
echo `date` # 输出日期
printf命令
和c语言中的printf
函数的用法差不多,也是用于格式化输出,但是不需要添加括号。
基本语法
printf format [arguments...]
printf "%d\n" 123 # 输出123
printf "%s\n" zhy # 输出zhy
printf "%d * %d = %d\n" 2 3 `expr 2 \* 3` # 输出2 * 3 = 6
test命令和判断符号[]
逻辑运算符&&和||
&& 表示与,|| 表示或
二者具有短路原则:
-
expr1 && expr2:当expr1为假时,直接忽略expr2。当expr1为真时,才会执行expr2
-
expr1 || expr2:当expr1为真时,直接忽略expr2。当expr1为假时,才会执行expr1
表达式的exit code
为0,表示真;为非零,表示假。
注意:exit code
和stdout
不一样,一般在C++/C
中的main函数中,最后是以return 0
结尾,说明的是这个进程是正常结束的。如果该进程不正常结束,就以非0结尾。
补充:可以使用&&
和||
搭配使用做到if else
的条件判断
test -e test.txt && echo "exist" || echo "no exist"
test -e test.txt
这个是利用test
做出的判断,后面会介绍,其实就是判断test.txt
这个文件是否存在。如果文件存在返回0
表示真,所以就会执行echo “exist”
;如果文件不存在返回1
表示假,所以就会不执行echo “exist"
,然后因为前面都是假,所以最后会执行echo “noexist
。这样就是利用test
+&& ||
使用类似if else
的判断。
test命令
test
命令用于判断文件类型,以及对变量做比较。
test
命令用exit code
返回结果,而不是使用stdout
。0表示真,非0表示假。
文件类型判断
利用test
命令判断文件的类型
格式:
test 选项 filename
选项 | 说明 |
---|---|
-e | 文件是否存在 |
-f | 是否为文件 |
-d | 是否为目录 |
文件权限判断
判断一个文件或者目录的权限。
格式:
test 选项 filename
选项 | 说明 |
---|---|
-r | 是否可读 |
-w | 是否可写 |
-x | 是否可执行 |
-s | 是否为非空文件 |
整数的比较判断
利用test
命令判断两个整数之间的关系
格式:
test $a 比较符 $b
比较符 | 说明 |
---|---|
-eq | a是否等于b |
-ne | a是否不等于b |
-lt | a是否小于b |
-gt | a是否大于b |
-le | a是否小于等于b |
-ge | a是否大于等于b |
字符串的比较判断
利用test
命令判断两个字符串之间的比较判断。
参数 | 说明 |
---|---|
test -z str | 判断str是否为空 |
test -n str | 判断str是否非空 |
test str1 == str2 | 判断str1和str2是否相等 |
test str1 != str2 | 判断str1和str2是否不相等 |
test str1 \> str2 | 判断str1是否>str2 |
test str1 \>\= str2 | 判断str1是否>=str2 |
多重条件判断
利用test
命令同时判断两个条件的真假,类似于c/c++
中的&& ||
格式:
test 条件1 选项 条件2
选项 | 说明 |
---|---|
-a | 判断两条件是否同时成立 |
-o | 判断两条件是否至少一个成立 |
! | 取反 |
[ ]判断命令
[]
是一个命令,和test
命令用法差不多,更常用于if语句中。另外[[]]
是[]
的加强版,支持的特性更多。
例如:
[ 2 -t 3]
echo $? # 输出上一个进程的退出状态.这里输出0
注意:
[]
内的每一项都要用空格隔开,因为[]
是一个命令- 中括号内的变量,最好用双引号括起来
- 中括号内的常数,最好用单或双引号括起来
判断语句
if判断
单分支if
语法格式:
if condition
then
语句
fi
例如:
if [ 1 -lt 2] && [ 3 -gt 2]
then
echo 1最小
fi
# 输出1最小
单分支层if-else
语法格式:
if condition
then
语句
else
语句
fi
例如:
a=10
b=20
if [ "$a" -ge "$b" ] || [ "$b" -le "$a" ]
then
echo "$a"大于"$b"
else
echo "$a"小于"$b"
fi
多分支判断if-elif-else
语法格式:
if condition
then
语句
elif
then
语句
else
语句
fi
例如:
a=10
if [ $a -eq 1 ]
then
echo $a==1
elif [ $a -eq 2 ]
then
echo $a==2
else
echo $a未知
fi
case判断
语法格式:
case $变量名 in
...)
语句
;;
...)
语句
;;
*) # 表示默认
语句
;;
esac
例如
a=1
case $a in
1)
echo "$a == 1"
;;
2)
echo "$a == 2"
;;
*)
echo "$a == $a"
;;
esac
循环语句
循环语句有3种循环,4中写法
for … in … do … done
将in后面的变量一个一个赋值给in前面的变量,类似于python
中的for循环
for var in var1 var2 var3
do
echo $i
done
例如:
例子1:
for i in 1 a &
do
echo $i
done
# 输出 1 a &
例子2:
也可以输出,``中命令的返回值。输出当前路径下的所有文件名,每个文件名一行:
for i in `ls`
do
echo $i
done
例子3:
可以使用seq
命令,输出一段范围的数字。
for i in `seq 1 10`
do
echo $i
done
例子3:
在文本中可以使用{1..10}
这样的语法输出替代seq
的作用,而且支持字母。
输出a~z
for i in {a..z}
do
echo $i
done
for ((…, … , …)) do … done
for循环还有一种写法
for ((expression; condition; expression))
do
语句
done
例如:
for ((i = 0; i <= 10; i ++))
do
echo $i
done
while … do … done
语法格式:
while condition
do
语句
done
例如:
一直输入name,知道遇到文件结束符,ctrl + d
while read name
do
echo $name
done
untill… do … done
until
循环和while
循环相反,如果condition为假才继续,为真就停止了。
语法格式:
until condition
do
语句
done
例如:
当输入yes
的时候,才能停止输入
until [ "$word" == yes ]
do
read -p "please input yes:" word
done
break
跳出当前一层循环,注意与C/C++不同的是:break不能跳出case语句。
例如:
输出1~10,但是到了5就停止
for ((i = 1; i <= 10; i ++))
do
if [ $i -eq 5 ]
then
break
fi
echo $i
done
continue
跳出当前一次循环。
例如:
输入1~10中间的奇数
for ((i = 1; i <= 10; i ++))
do
if [ $[$i % 2] == 0 ]
then
continue
fi
echo $i
done
处理死循环
1.可以使用top
命令找到死循环的进程号PID
2.然后使用kill -9 PID
强制结束这个进程
函数
基本语法
bash
中的函数类似于C/C++
中的函数,但return
的返回值与C/C++
不同,返回的是exit code
,取值为0-255
,0
表示正常结束。
如果想获取函数的输出结果,可以通过echo
输出到stdout
中,然后通过$(function_name)
来获取stdout
中的结果。
函数的return
值可以通过$?
来获取。
语法格式:
func_name () {
语句
...
}
例如:
func() {
name=zhy
echo $name
}
func # 调用函数不用加(),而是直接像命令一样直接写函数名
获取 return值和stdout值
不写return时,默认return 0。
例如:
func() {
name=zhy
echo "hello $name"
return 100
}
output=$(func) # 使用$()返回nc函数的stdout
returnVal=$? # 使用$?返回func的返回值
echo "output = $ouput"
echo "returnVal = $returnVal"
函数的输入参数
在函数内,$1
表示第一个输入参数,$2
表示第二个输入参数,依此类推。
注意:函数内的$0
仍然是文件名,而不是函数名。
例如:
func() {
for ((i = 0; i <= $1; i ++))
do
echo $i
done
}
echo $(func 10)
函数内的局部变量
可以在函数内定义局部变量,作用范围仅在当前函数内。
可以在递归函数中定义局部变量。
语法格式:
local 变量名=变量值
exit命令
exit
命令用来退出当前shell
进程,并返回一个退出状态;使用$?
可以接收这个退出状态。
exit
命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit
退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
if [ $# -eq 1 ]
then
eixt 0
else
exit 1
fi
# 传入一个参数就返回0退出,如果传入的参数参数个数不是1就返回1退出
文件重定向
每个进程默认打开3个文件描述符:
-
stdin标准输入,从命令行读取数据,文件描述符为0
-
stdout标准输出,向命令行输出数据,文件描述符为1
-
stderr标准错误输出,向命令行输出数据,文件描述符为2
可以用文件重定向将这三个文件重定向到其他文件中。
命令 | 说明 |
---|---|
command > file | 将stdout重定向到file中 |
command < file | 将stdin重定向到file中 |
command >> file | 将stdout以追加方式重定向到file中 |
例如:
echo "hello world" > tmp.txt
read str < tmp.txt
echo $str
引入外部脚本
类似于C/C++
中的include
操作,bash
也可以引入其他文件中的代码。
语法格式:
. filename
或者
source filename
例如:
创建一个test1.sh
#! /bin/bash
str=hello world
再创建一个test2.sh
#! /bin/bash
source test1.sh
echo $str # 输出hello world