Shell编程不花里胡哨,一篇就够~
一、基础入门
1.1 shell脚本的第一行代码
#!/bin/bash
Linux环境下的任何脚本语言,都是以这样一个被称为shebang的特殊行作为起始的。
1.2 脚本的运行方式
将脚本作为sh的命令行参数
$ sh script.sh
将脚本作为具有执行权限的可执行文件(这种方式shebang行可以不添加)
$ chmod a+x script.sh
$ /Users/xxPath/xxPath/script.sh
1.3 终端打印
$ echo "Hello shell..." # 无情的打印
在终端中生成彩色输出还是比较好玩的,系统日志看起来总那么“无情”,何不给日志来点有温度的颜色
$ echo -e "\033[30m 黑色字 \033[0m"
$ echo -e "\033[31m 红色字 \033[0m"
$ echo -e "\033[32m 绿色字 \033[0m"
$ echo -e "\033[33m 黄色字 \033[0m"
$ echo -e "\033[34m 蓝色字 \033[0m"
$ echo -e "\033[35m 紫色字 \033[0m"
$ echo -e "\033[36m 天蓝字 \033[0m"
$ echo -e "\033[37m 白色字 \033[0m"
1.4 变量的使用
在shell中,每个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式储存。
$ days1=1
$ echo $days1
有时候我们想把一个命令的执行输出结果赋值给一个变量,可以通过反引号字符 `` 或 $() 的格式,比如
$ s1=`date`
$ s2=$(date)
如果shell脚本里头又启动了其它子shell脚本进程的话,很可能涉及环境变量,shell中设置环境变量通过export,使用演示如下:
$ export env_name=env_val
当环境不需要的时候可以取消:
$ unset env_name
1.5 通过shell进行数学运算
在shell中,可以通过let、(( ))、[ ]执行基本的算数操作,如果进行高级操作时,expr和bc这两个工具也会很有用。
#!/bin/bash
num1=1
num2=2
# ------------------- 求和 -----------------
let sum=num1+num2
echo $sum
# ------------------- 自增/自减 -----------------
let num1++
echo $num1
let num1--
echo $num1
# ------------------- 加/减某个值 -----------------
let num1+=1 # 或let num1=num1+1
echo $num1
let num1-=1 # 或let num1=num1-1
echo $num1
# ---- 可以留意到,用let运算,操作变量不需要加$ ----
作为快速入门,要学先做减法,因此这里运算符只简单介绍一种,其他方式可以在真正用到的时候查,大同小异。当然要注意的是:let、(( ))、[ ]、expr这些方式都只能用于整数运算,不支持浮点数,此时bc工具就派上用场了,举个栗子:
$ echo "4 * 5.54" | bc
22.16
1.6 普通数组
# 普通数组赋值的两种方式:
# 1)创建变量时赋值
arr=(t1 t2 t3 t4)
# 2)索引-值赋值
arr[0]="test1"
arr[1]="test2"
arr[2]="test3"
arr[3]="test4"
arr[4]="test5"
# 打印数组所有值的方式
echo ${arr[*]}
echo ${arr[@]}
# 打印数组的长度
echo ${#arr[*]}
1.7 关联数组(可以用字符串作为索引)
关联数组的定义:declare -A ass_array,关联数组从Bash 4.0版本开始被引入,并非所有shell版本支持,看情况用吧
# 关联数组的赋值的两种方式:
# 1)创建变量时赋值
declare -A ass_array
ass_array=([name1]=ricky1 [name2]=ricky2)
# 2)键值对的形式赋值
declare -A ass_array2
ass_array2[name3]=ricky3
ass_array2[name4]=ricky4
echo ${ass_array[*]}
1.8 调试脚本
使用选项-x,启动shell脚本,能打印出所执行的每一行命令以及当前状态。
$ sh -x script.sh
当然啦,有时候不需要打印所有行的执行情况,可以用:
set -x : 在执行时显示参数和命令
set +x : 禁止调试
arr=(t1 t2 t3 t4)
set -x
echo ${#arr[*]}
set +x
echo "Ohter code"
在上面的脚本中,仅在-x和+x所限制的区域内的调试信息才会被打印出来,打印结果如下:
$ sh script.sh
+ echo 4
4
+ set +x
Ohter code
1.9 状态码
shell每个命令执行完后都有一个退出状态码,表示它已运行完毕,退出状态码是一个0~255的整数值。在脚本中可以捕获上一个命令的状态码做一些逻辑处理。Linux提供了一个专门的变量$?来保存上个已执行命令的退出状态码,使用演示:
$ date
2020年12月25日 星期五 00时10分02秒 CST
$ echo $?
0
一般命令成功执行时,退出状态码是0,如果命令结束时有错误,退出状态码一般是一个正数值,如:
$ asdfas
zsh: command not found: asdfas
$ echo $?
127
常见退出状态码以及描述(当然,表格里的状态码我没都遇到过,但比如127的退出状态码就比较容易常见了)
状态码 | 描述 |
---|---|
0 | 命令成功执行 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效的退出参数 |
130 | 通过CTRL+C终止命令 |
255 | 正常范围之外的退出状态码 |
那我们整份脚本文件运行结束的退出状态码呢?
Shell脚本会以脚本中的最后一个命令的退出状态码退出,你也可以改变这种默认行为,返回自己的退出状态码,exit命令就派上用场了:
#!/bin/bash
s1=10
s2=20
let sum=s1+s2
echo $sum
exit 5
假设上述代码为script.sh文件的脚本内容,则在终端的执行结果为:
$ sh script.sh
30
$ echo $?
5
如果我自定义的退出状态码大于前面的0~255的范围呢?比如exit 300(将上述script.sh文件的exit 5改为exit 300),则输出结果为:
$ sh script.sh
30
$ echo $?
$ 44
shell中会对最终结果除以256进行求模,300/256的模为44(即整除后的余数)
1.10 函数的定义和调用
1.10.1 函数的定义和调用
#!/bin/bash
function first_func_demo
{
echo "函数的定义就这样了..."
}
# 或者
second_func_demo()
{
echo "函数也可以这么定义..."
}
# 调用函数
first_func_demo
second_func_demo
直接复制上面的代码跑一下吧,一秒就会。如果要向函数传递参数,又如何呢,继续上面的脚本,向函数传递两个参数,演示如下:
function third_func_demo
{
echo "调用函数时传递参数以及读取参数就这么简单..."
# 读取第一个参数值
echo $1
# 读取第二个参数值
echo $2
}
# 1) 向函数传递两个参数,如果参数更多,空格分界,以此类推...
num1=10
num2=100
third_func_demo $num1 $num2
直接复制上面的代码跑一下吧,又是一秒就会。
注意,你可能经常看到shell脚本里头有1、n、*、$#...emmm,别慌,总结在下面:
$0: 表示程序名
$1...n:表示第1个...第n个参数
$#: 表示输入参数的个数
$@: 获取传入的参数数组,可用于for循环遍历处理场景
$*: 把所有命令行参数当做一个整体
1.10.2 函数的返回值
shell会把函数当作一个小型脚本,运行结束时会返回一个状态码,默认情况,函数的退出状态码是函数中最后一条命令返回的退出状态码,但这样你就无法得知函数里头的代码是否都被正确的执行,因此我们也可以用return命令返回函数值。
function func1 {
result=200
return $result
}
func1
echo "func1 run result: $?"
当然我们也可以通过echo输出来返回函数值
function func2 {
result=200
echo $result
}
echo "func2 run result: $(func2)"
1.10.3 在.bashrc文件中定义函数
shell每次启动时会重新载入.bashrc(因为我的Mac是用zsh,所以是.zshrc,为了名字通用,我们都先同意叫bashrc吧。。。),bashrc文件一般放在/home/linux用户名下,我们可以定义自己工具类函数,用于处理日常一些繁琐的工作流程,使其自动化,这里举两个栗子吧:
1.比如每天都要更新所有的git仓库并进行备份;
2.常常很多命令没记错,比如dumpsys package、meminfo、activity,am,pm等等以及其他串口命令,可以自定整理一份文档之后,之后通过调用函数直接打印这些常用命令
下面开始演示,我就定义个简单的栗子吧,在我的.zshrc文件中添加:
# 在.zshrc追加函数
function addem {
echo $[ $1 + $2 ]
}
重启zsh终端,然后运行试试:
$ addem 10 20
30
1.11 命令行参数的处理
上面展示了调用函数如何输入参数和读取参数,那如果执行某个脚本文件或者执行某个命令行的参数输入、读取呢?其实跟函数参数的输入、读取基本一致,直接上小结吧
1.命令行参数的读取,$0:程序名,$1,$2...表示第一个、第二个参数依次类推
2.特殊参数变量:$#(返回命令参数的个数)
3.获取所有命令行参数:$*(把所有命令行参数当做一个整体)、$@(获取命令行数组,主要用于for循环)
这3点问题不大,直接上个小demo演示,scirpt.sh脚本内容如下:
#!/bin/bash
echo $0
echo $1
echo $2
在终端执行script.sh脚本结果演示:
$ sh /Users/xxx/Desktop/script.sh HaHa 123
/Users/xxx/Desktop/script.sh
HaHa
123
1.12 命令行选项的处理
命令行选项也没什么特殊的。在命令行上,它们紧跟在脚本名之后,就跟命令行参数一样。实际上,如果愿意,你可以像处理命令行参数一样处理命令行选项。选项是跟在单破折线后面的单个字母,根据不同的选项执行不同的逻辑,有点想switch语句。script.sh演示脚本如下:
#!/bin/bash
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found -a option" ;;
-b) echo "Found -b option" ;;
*) echo "$1 option not found" ;;
esac
shift
done
在终端执行script.sh脚本并输入选项试试:
$ sh script.sh -a -b -c -d
Found -a option
Found -b option
-c option not found
-d option not found
那命令行参数和命令行选项同时用呢?通过双破折线(--)来实现参数和选项的分离。shell会用双破折线来表明选项列表的结束,在双破折线之后,脚本就可以放心地读剩下的命令行参数了,而不是当选项处理。
#!/bin/bash
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found -a option" ;;
-b) echo "Found -b option" ;;
--) shift
break ;;
*) echo "$1 option not found" ;;
esac
shift
done
count=1
for param in $@
do
echo "Param: $param"
let count+=1
done
我们执行上面的script.sh脚本,并传入选项参数、命令行参数,得到执行结果如下:
$ sh script.sh -a -b -c -d -- HAHA 123
Found -a option
Found -b option
-c option not found
-d option not found
Param: HAHA
Param: 123
那如果有些选项参数需要传值呢?我们在继续基于上面的脚本修改:
#!/bin/bash
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found -a option" ;;
-b) param_value="$2"
echo "Found -b option with value $param_value"
shift ;;
--) shift
break ;;
*) echo "$1 option not found" ;;
esac
shift
done
count=1
for param in $@
do
echo "Param: $param"
let count+=1
done
脚本修改之后的执行结果:
$ sh script.sh -a -b ss -c -d -- HAHA 123
Found -a option
Found -b option with value ss
-c option not found
-d option not found
Param: HAHA
Param: 123
1.13 getopt命令
有时候我们需要合并选项参数,比如这样的格式:
$ sh script.sh -ab
getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格式。getopt的命令格式如下:
getopt optstring params
optstring是关键所在,它定义了命令行所有需要输入的选项参数字母,如果选项参数需要带值,则在对应的字符后面加冒号(:)
有点绕?我们用上面在终端运行的脚本作为栗子:
$ sh script.sh -a -b ss -c -d -- HAHA 123
一眼就看到script.sh脚本的选项参数有,abcd,其实b还带参数值,那如果用getopt命令来处理后面这一大串选项、参数会是怎样的呢?我们在终端尝试运行下面的命令:
$ getopt ab:cd -a -b ss -cd HAHA 123
-a -b ss -c -d -- HAHA 123
在命令选项的工作机制之后,假设我们以后想了解某个命令行怎么用?可以怎么做呢?还是做个demo吧:
function read_params
{
while [[ -n $1 ]]
do
case $1 in
-a) echo "Found option -a" ;;
-b) echo "Found option -b"
echo "Value is $2"
shift ;;
--) shift
break ;;
*) echo "Option not found" ;;
esac
shift
done
count=1
for param in $@
do
echo $param
let count+=1
done
}
read_params `getopt ab: $*`
然后在终端就可以:
$ sh script.sh -ab HAHA 123 456
PS:我们常常用grep命令,比如:
$ grep -ir shell filename
$ grep -i -r shell filename
$ grep -r -i shell filename
相信在了解了getopt命令以及如何shell脚本如何解析命令行选项和命令行参数后,就大概知道上面的grep命令内部是如何解析输入的参数了吧~
二、结构化命令
2.1 if-then的使用
if [ condition ]
then
cmds
elif [ condition ]
then
cmds
else
cmds
fi
# 或者
if [ condition ]; then
cmds
elif [ condition ]
then
cmds
else
cmds
fi
方括号两边各有一个空格,必须加上,否则报错;方括号内可进行数值比较、字符串比较、文件比较。
2.1.1 数值比较
比较 | 描述 |
---|---|
n1 -eq n2 | equal |
n1 -ge n2 | great or equal |
n1 -gt n2 | great than |
n1 -le n2 | less or equal |
n1 -lt n2 | less than |
n1 -ne n2 | not equal |
2.1.2 字符串比较
比较 | 描述 |
---|---|
str1 = str2 | str1是否等于str2 |
str1 != str2 | str1是否不等于str2 |
str1 < str2 | str1小于str2 |
str1 > str2 | str1大于str2 |
-n str1 | str1的长度是否不为0 |
-z str1 | str1的长度是否为0 |
关于字符串大于、小于的比较,我们都知道shell里头有重定向符 > ,因此在做大小比较时需要添加转义,如:
if [ $str1 \> $str2 ]
是不是很麻烦?此时我们可以用if-then的高级特性,双方括号[[ expression ]],那双方括号高级在哪里呢?除了不需要再做转义之外,还可以在双方括号里头写匹配模式,如:
if [[ $USER == r*ky ]]
PS:忘记单方括号的使用吧,直接用双方括号!
2.1.3 文件比较
文件的比较在shell里头用得非常非常的多,也非常的有用!!
比较 | 描述 |
---|---|
-d file | 检查file是否存在且是一个目录 |
-e file | 检查file是否存在 |
-f file | 检查file是否存在且是文件 |
-r file | 检查file是否存在且可读 |
-w file | 检查file是否存在且可写 |
-x file | 检查file是否存在且可执行 |
file1 -nt file2 | equal |
file1 -ot file2 | equal |
2.1.4 复合条件
if [ condition1 ] && [ condition2 ]
if [ condition1 ] || [ condition2 ]
2.2 case的使用
case variable in
pattern1 | pattern2) cmds1;;
pattern3) cmds2;;
*) default cmds;;
esac
2.3 for循环的使用
for var in list
do
cmds
done
2.4 while的使用
while conditions
do
cmds
done
eg:
var1=10
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1-1 ]
done
2.5 until的使用
until conditions
do
cmds
done
2.6 break命令
while [ $var1 -lt 10 ]
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Iteration: $var1"
var1=$[ $var1 + 1 ]
done
2.7 continue命令
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
continue
fi
echo "Iteration number: $var1"
done
三、好用命令行工具集
$ tr
$ sed
$ awk
$ cut
$ find
$ grep