一、shell 编程概述
在 Linux 下有一门脚本语言叫做:Shell 脚本,这个脚本语言可以帮助我们简化很多工作,例如编写自定义命令等,所以还是很有必要学习它的基本用法的。
本质上讲,shell 是 linux 命令集的概称,是属于命令行的人机界面。shell 里语句可以直接是 linux 命令的执行。
一个简单的 hello.sh
脚本像下面这样:
#!/bin/bash # 标识该 Shell 脚本由哪个 Shell 解释
echo "Hello World!" # 打印输出字符 Hello World!
运行 shell 脚本:在 CMD 命令行下,sh hello.sh
即可,也可 ./hello.sh
运行
后台运行:sh hello.sh &
,若退出终端的运行就会挂掉。
后台挂起运行:nohup sh hello.sh &
,hohup 的作用是:用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。
Shell 的编写流程:
- 编写 Shell 脚本
- 赋予可执行权限(一般可以忽略)
- 执行,调试
二、开始 shell
shell 语言是 linux 命令集的概称,shell 可以通过其条件语句和循环语句等,把一系列 Linux 命令结合在一起,形成一个相当于面向过程的程序。
同时,在语法上,shell 语言格式与其他编程需要有相当大的不同。这个做一个简单的总结:
- 用 # 进行单行注释,也可多行注释,多行注释请百度,一般用的少
- 用空格分割命令,空格不能随便用
- 用换行分割语句,分号也可用来分割语句。语句可以是一个命令,也可以是一个关键词。
- 对缩进没有要求,但是尽可能好看点,便于阅读
2.1 shell 关键字
2.1.1 基本关键字
基本常用的关键字如下:
- echo:打印文字到屏幕
- exec:执行另一个 Shell 脚本
- read:读标准输入
- expr:对整数型变量进行算术运算
- test:用于测试变量是否相等、 是否为空、文件类型等
- exit:退出
看个例子,新建文件 test.sh,写入:
#!/bin/bash
echo "Hello Shell" # 打印输出字符串 Hello Shell
# 读入变量(从屏幕输入)
read VAR
echo "VAR is $VAR"
# 计算变量
expr $VAR - 5 #算术运算,并返回值
# 测试字符串
test "Hello"="HelloWorld"
# 测试整数
test $VAR -eq 10
# 测试目录
test -d ./Android
# 执行其他 Shell 脚本
exec ./othershell.sh
# 退出
exit
$ 符号是对变量取值
2.1.1 各种重要符号
$ 变量名
:对变量取值${ 变量名 }
:对变量取值,花括号的作用仅仅是精确界定变量名称的范围。$(命令或函数名)
:执行命令或函数,并返回执行结果。与反引号 ` 命令或函数名 ` 作用一致引号:见 2.2.1 章节
$[1+2]
与$((2+1))
:进行整数运算,注意这里可以没有空格[]:判断符号,用于条件测试,见 2.5 章节
(()):整型的计算
[[]]:双方括号为字符串比较提供高级功能,模式匹配。常见用法见 2.4.2
[[是 bash 程序语言的关键字。
()与 {} 的区别
相同点:
- ()和 {} 都是把一串的命令放在括号里面,如果命令在一行,则命令之间用 ; 隔开
不同点:
()只是把一串命令重新开一个子 shell 进行执行,不影响当前 shell 环境;{}对一串命令在当前 shell 执行, 影响当前 shell 环境
()最后一个命令不用分号,{}最后一个命令要用分号
()里的第一个命令和左边括号不必有空格,{}的第一个命令和左括号之间必要要有一个空格
()和 {} 中括号里面的某个命令的重定向只影响改名了,但括号外的重定向则影响到括号里的所有命令
== 与 = 的区别
- == 可用于判断变量是否相等,= 除了可用于判断变量是否相等外,还可以表示赋值。
- = 与 == 在 [] 中表示判断 (字符串比较) 时是等价的
- 在 (()) 中 = 表示赋值, == 表示判断(整数比较),它们不等价
$()的补充用法
- ${var:-word}:若 var 为空或未设置,返回默认值但是并不把默认值赋值给该变量,若 var 不为空,则不替换,var 的值不变
- ${var:=word}:若 var 为空或未设置,把默认值赋值给该变量。若 var 设置了,则不替换,var 的值不变
- ${var:+word}:若 var 不为空时,返回默认值,并且也不重新赋值
2.2 字符串
shell 就两种数据类型,字符型和数组。数字(整型)也可以看作是一种字符串。
在 Shell 中所有的变量默认都是字符串型。字符串可以用单引号,也可以用双引号,也可以不用引号。
字符串赋值时,变量名和等号之间 不能有空格
取值时,只要在变量名前面加美元符号 $ 即可,也可使用 ${变量名}
2.2.1 引号
单引号
str='this is a string'
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号
name="Jack" str="my name is $name"
双引号的优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
不加引号
path=/home/qiang/ file=$path/xor.txt
不加引号特点:
- 只能是连续字符串,不能有空格
- 可以有变量
- 可以出现转义字符
这里补充一个符号:尖角号 `(或叫反引号)
这个符号通常位于键盘 Esc 键的下方。当用尖角号括起来一个 shell 命令时,代表执行这个命令,并将执行的结果返回给赋给的变量。
2.2.2 字符串方法
获取子串长度:
${#str}
,获取 str 变量的字符串长度提取子串(切片):
${str:1:4}
,从第 2 个字符开始,截取 4 个字符${str:2}
,从第 3 个字符开始,截取后面所有字符${str:2:-1}
,从左往右第 3 位开始截取,从右往左截取到第一位查找子串:
expr index "$str" xy #查找字符 x 或 y 的位置(哪个字母先出现就计算哪个)
拼接字符串:”hello, “$name” !”,直接写在一块就行。
匹配删除:
${str#*chr}
:表示删除从左到右第一个遇到的字符 chr 及其左侧的字符${str##*chr}
:表示删除从左到右最后一个遇到的字符 chr 及其左侧的字符(贪婪模式)${str%chr*
:表示删除从右向左第一个遇到的字符 chr 及其右侧的字符${str%%chr*}
:表示删除从右到左最后一个遇到的字符 chr 及其右侧的字符(贪婪模式)
2.3 shell 变量类型
Shell 变量分为 3 种:
- 用户自定义变量
- 等号前后不要有空格:
NUM=10
- 一般变量名用大写:
M=1
- 等号前后不要有空格:
- 预定义变量
- 环境变量
2.3.1 自定义变量
这种变量 只支持字符串类型,不支持其他字符,浮点等类型,常见有这 3 个前缀:
unset
:删除变量readonly
:标记只读变量export
:指定全局变量
一个例子:
#!/bin/bash
# 定义普通变量
CITY=SHENZHEN #将字符串 SHENZHEN 赋给变量 CITY
# 定义全局变量
export NAME=cdeveloper
# 定义只读变量
readonly AGE=21
# 打印变量的值
echo $CITY
echo $NAME
echo $AGE
# 删除 CITY 变量
unset CITY
2.3.2 预定义变量
预定义变量常用来获取命令行的输入,有下面这些:
$0
:脚本文件名$1-9
:第 1-9 个命令行参数名$#
:命令行参数个数$@
:所有命令行参数$*
:所有命令行参数$?
:前一个命令的退出状态,可用于获取函数返回值$$
:执行的进程 ID
在运行 shell 脚本的时候,可以外部传参,供脚本内部使用:
sh test.sh param1 parma2 parma3 #运行 test.sh 脚本并传入三个参数 param1 parma2 parma3
这时候这 7 个预定义变量的作用就容易出来了。
一个例子执行sh.test.sh x y z
:
#!/bin/bash
echo $0 #输出 test.sh
echo $1 #输出 x
echo $2 #输出 y
echo $3 #输出 z
echo $# #输出 3
echo $@ #输出 x y z
echo $* #输出 x y z
echo $? #输出 0
echo $$ #输出 184715
2.3.3 环境变量
环境变量默认就存在,常用的有下面这几个:
HOME
:代表用户主目录PATH
:代表系统环境变量 PATHTERM
:代表当前终端UID
:代表当前用户 IDPWD
:代表当前工作目录,绝对路径
看一个例子:
#!/bin/bash
echo "print env"
echo $HOME #输出 /home/origin
echo $PATH #输出 /home/orange/anaconda2/bin: 后面还有很多
echo $TERM #输出 xterm
echo $PWD #输出 /home/origin/test
echo $UID #输出 1674
2.4 shell 运算符
2.4.1 算术运算
shell 的算术运算只支持整型数字运算(包括正负整型),不支持浮点数。在算术运算时,非数字的单字符或字符串都被 shell 当作 0。
由于 Shell 中所有的变量默认都是字符串型,故在运算时,不同于常规运算方法。
常用的四种运算格式(都是计算 m+1):
m=$[m + 1]
m=expr $m + 1
let m=m+1
m=$((m + 1))
另外:还支持自增 ++ 和自减–。
2.4.2 逻辑运算符
2.4.3 布尔运算符
2.5 条件测试
shell 条件测试一共就三种: 文件测试 , 数值比较 , 字符串比较
条件测试分为三种用法,但作用是一样的:
- test 条件表达式
- [条件表达式] 注意
[
后要加空格,]
前也有空格 - [[条件表达式]]
推荐使用第二种。
值得注意的是:对于条件测试,其中的条件表达式 condition 为真返回 0,假返回 1。这与其他语言不同!
在 shell 中,命令语句执行完,其退出状态码,0(成功)表示真,非 0(失败)表示假
但是用 if [] 的判断条件 if [ 1]时,任何数字都返回真。
2.5.1 文件测试
用法示例:
[! -d /home/test] && makdir /home/test #目录不存在就创建
[-d /home/test] || makdir /home/test #目录存在就不创建
test -d /home/test
echo $?
0 #在 shell 中,为真的退出状态码为 0,为假时状态码为 1
2.5.2 数值比较
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
用法示例:
disk_use=`df -Th`|grep '/$'|awk '{print $(NF-1)}'|awk -F "%" '{print $1}' #磁盘利用率
user=Alice
if [$disk_use -ge 90]; then
echo "`data +%F-%H` disk: ${disk_use}%"|mail -s "disk war..." $user
fi
数值比较还支持 C 语言风格:如 ((1<10)),((1<=10))
这种,这里不多做介绍。
2.5.3 字符串比较
字符串变量要使用双引号引起来
用法示例:
[aaa = abb];echo $? #输出 1 1 为假
["aaa" = "aaa"];echo $? #输出 0 0 为真
str=asd
str2=abc
["$str" = "$str2"];echo $? #输出 1
2.6 shell 语句
2.6.1 if 语句
格式:
if 条件测试 1
then
执行语句 1
elif 条件测试 2
then
执行语句 2
else
执行语句 2
fi
用法示例:
#!/bin/bash
VAR=10
if [$VAR -eq 10] #if test $VAR -eq 10
then
echo "true"
else
echo "false"
fi
2.6.2 case 语句
格式:
case 变量 in
值 1)
执行语句 1
;;
值 2)
执行语句 2
;;
*)
执行语句 3
;;
esac
2.6.3 for 语句
格式:
for ((i = 1; i <= 3; i++))
do
echo $i
done
for j in 1 2 3
do
echo $j
done
for j in {0..100} #输出 0-100
do
echo $j
done
2.6.4 while 语句
格式:
while 条件测试 #条件为真,执行
do
执行语句
done
2.6.5 until 语句
格式:
until 条件测试 #条件为假,执行
do
echo $i
i=$[$i + 1]
done
2.6.6 break
Shell 中的 break
用法与高级语言相同,都是 跳出循环,来看个例子:
for VAR in 1 2 3
do
if [$VAR -eq 2]
then
break
fi
echo $VAR
done
2.6.7 continue
continue
用来 跳过本次循环,进入下一次循环
for VAR in 1 2 3
do
if [$VAR -eq 2]
then
continue
fi
echo $VAR
done
2.7 shell 数组
shell 就两种数据类型,字符型和数组。字符串之前已经介绍了,这里介绍数组。
数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。
2.7.1 数组定义
# 第一种
arr=(v1 v2 v3 v4 v5) #v 可以是字符串或者数字,不必类型相同
#第二种
arr1=(
xxx
yyy
zzz
)
#单独赋值
arr[1]=10
一对括号表示的是数组,数组元素用空格符号分隔开
2.7.2 读取与赋值
- 读取数组:
- 读取整个数组:
${arr[@]}
或者${arr[*]}
- 按索引读取:
${arr[index]}
,索引从 0 开始
- 读取整个数组:
- 读取长度:
${#arr[@]}
或者${#arr[*]}
- 赋值:
arr[1]=100
- 删除:
unset arr[1]
或unset arr
- 替换:
${a[@或 *] / 查找字符 / 替换字符 }
,该操作不会改变原来的数组内容
一个常用的例子:
caselist=($(ls /home/template_dir | grep -v 'xor'))
for case in ${caselist[*]}
do
echo $case
done
2.8 shell 函数
shell 函数中,默认均为全局变量,如想用局部变量加local
关键字
2.8.1 函数定义与调用
格式 1:
function fun1(){ #如果写了 function 关键字,()可以省略
echo $1 #返回方式 1
echo $2
return 0 #返回方式 2
}
#1 直接函数名调用, 并传参
fun1 10 20
#2 间接调用
F=`fun1 100 200`
echo $F
格式 2:
fun2()
{
echo "fun2 call"
}
#3 直接函数名调用,不带参数方式
fun2
Shell 函数返回值,常用的两种方式:return、echo。二者是有差别的
- return:只能用来返回整数值(0-255),一般是用来表示函数执行成功与否的,0 表示成功,其他值表示失败。
- echo:标准输出返回,可以返回任何类型的数据。
2.8.2 获取函数返回值
函数用 echo 输出返回:直接函数名调用
函数用 return 输出返回:
$?
接收,$?
要紧跟在函数调用处的后面
先举个例子:
fun2()
{
echo "fun2 call"
return 100
}
fun2 # 输出 echo 的内容: fun2 call
echo $? #输出 return 的内容:100
echo $? #输出函数退出码:0
还有 2 种:
- 函数名当命令执行:`fun2` (尖角号) 或者 $(fun2)
- 传参输出返回:$(fun2 “/temp”)
再举个例子:
fun3()
{
echo "fun3 call"
}
echo `fun3` #输出打印 fun3 call
echo $(fun3) #输出打印 fun3 call
re=$(fun3)
echo ${re} #输出打印 fun3 call
2.8.3 高级收参
$@ 可以接收所有参数,以数组的方式。
function getsum(){
local sum=0
for n in $@
do
((sum+=n))
done
echo $sum
return 0
}
total=$(getsum 10 20 55 15)
echo $total
2.9 shell 重定向
2.9.1 输出重定向
命令的结果不再输出到显示器上,而是输出到指定的文件里。
- 命令输出重定向
格式:
command > file #覆盖写入到文件
command >> file #追加写入到文件
用法示例:
echo "hello world!" > data.txt
ls -l >> data.txt
- cat 输出重定向 : 用于多行文本写入
用法示例:
cat > file << EOF
export ORACLE_SID=yqpt
export PATH=\$PATH:\$ORACLE_HOME/bin
EOF
作用:将两个 EOF 之间的内容输出写到 data.txt 文件里。
- EOF 只是一个标识符,也可使用其他字符代替
- 多行文本如有 $ 等特殊字符时,须利用转义字符 \
2.9.2 输入重定向
使用文件作为命令的输入。
command < file #将 file 文件中的内容作为 command 的输入
command << END #从标准输入(键盘)中读取数据,直到遇见分界符 END 才停止
高级用法(代码输入重定向)示例:
#!/bin/bash
sum=0
while read n; do
((sum += n))
done < nums.txt #输入重定向
echo "sum=$sum"
2.10 shell 调试
检查是否有语法错误:
sh -n script_name.sh
执行并逐步调试 shell 脚本:
sh -x script_name.sh
带有 +
表示的是 Shell
调试器的输出 , 不带 +
表示我们程序的输出。
欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net