文章目录
shell脚本是一种为 shell 编写的脚本程序, 一般文件后缀为 .sh
shell 的解释器种类众多:
- sh: 即 Bourne Shell, sh 是 Unix 标准默认的 shell
- bash: 即 Bourne Again Shell, bash 是 linux 标准默认的 shell
…
shell 脚本中开头第一句的#!
告诉系统其后路径所指的程序即是解释此脚本文件的
shell 解释器
shell 变量
shell 变量命名:
- 只能使用英文字母, 数字和下划线, 首个字符不能以数字开头
- 中间不能有空格可以使用下划线
- 不能使用标点符号
- 不能使用 bash 里的关键字
局部变量和全部变量
shell 脚本中的变量默认都是全局的, 即其作用于从声明定义处一直到程序结束,如:
#!/bin/bash
func()
{
i="hello world" #变量定义是=左右不能有空格
}
func
echo ${i}
输出为:
hello world
想要声明局部变量使用local
关键字
预定义变量
- $0~9, $0 是脚本文件名, $1~9 是命令行参数
- $# 命令行参数个数
- $@ 所有命令行参数
- $$ 执行的进行id
- $? 上个命令的退出状态, 或函数的返回值
变量默认值
value=${somevalues:othervalues}
#如果somevalues被定义了,则value=somevalues,否则values=othervalues
环境变量
- HOME 用户主目录
- PATH: 系统环境变量 PATH
- TERM: 当前终端
- PWD: 当前工作目录, 绝对路径
shell 常用关键字
- echo
- exec: 执行另一个 shell 脚本
- read: 读标准输入
- expr: 对整数型变量进行算数运算
- test: 用于测试变量是否相等, 是否为空, 文件类型等
- exit: 退出
- who: 显示当前登录系统用户名
- tee: 读取标准输入文件, 并将其内容输出为文件
let命令
linux let
命令是 bash 中用于计算的工具, 用于执行一个或多个表达式, 变量计算中不需要加上 $ 来表示变量, 如果表达式中包含了空格或其他特殊字符, 则必须引起来
如:
i=0
let i++
getopts获取参数+case语句解析
#!/bin/bash
func()
{
while getopts "a:b:" opt
do
case ${opt} in
a) echo ${OPTARG}
;;
b) echo ${OPTARG}
;;
?) echo "wrong parameter"
esac
done
}
main()
{
func ${@}
}
main ${@}
- 当有不认识的选项时为
?)
;;
相当于 break, 必须加esac
就是 case 反回来- 每个参数后跟一个冒号表示其需要一个参数
- 可以在参数前加一个冒号,如:
while getopts ":a:b:" opt
,这个:
表示getopts不返回错误 OPTARG
变量保存当前参数,OPTIND
为argv
的当前索引值
解析命令行参数工具
getopt
和getopts
用于 shell 脚本中分析脚本参数
- geopts 是 shell 内置命令, getopt 是一个独立外部工具
- getopts 使用语法简单, getopt 使用语法较复杂
- getopts 不支持长参数(如: --option), getopt 支持
- getopts 不会重拍所有参数顺序, getopt 会重拍参数顺序
- getopts 出现的目的是为了替代 getopt 较快捷的执行参数分析工作
getopt 所设置的全局变量:
- optarg 指向当前选项参数(如果有)的指针
- optind 再次调用 getopt() 时的下一个 argv 指针的索引
- optopt 最后一个位置选项
调用main函数
定义完main函数, 使用 main "$@"
进行对main的调用
获得执行脚本的当前绝对路径
basepath=$(cd "$(dirname $0)"; pwd)
cut命令
cut 命令用来从文件或者标准输入中读内容并截取特定部分送到标准输出
- -b : 输入每行的第 n 个字符(有中文就乱码)
- -c : 输入每行的第 n 个字符(适用中文)
- -d: 自定义分隔符
- -f : 与 -d 一起使用, 指定显示哪个区域
- -n : 取消分割多字节字符(例如中文), 仅与 -b 一起使用
例如:
$ echo long ago test befor | cut -f 2,3 -d ' '
ago test
test 命令
用于检查某个条件是否成立, 可以进行数值, 字符串和文件三个方面的比较
[] 是test命令的一种形式
数值测试
-
eq
(equal的缩写),表示等于为真 -
ne
(not equal的缩写),表示不等于为真 -
gt
(greater than的缩写),表示大于为真 -
ge
(greater&equal的缩写),表示大于等于为真 -
lt
(lower than的缩写),表示小于为真 -
le
(lower&equal的缩写),表示小于等于为真
num1=100
num2=100
if test $[num1] -eq $[num2]
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi
# 可以写为 if [ ${num1} -eq ${num2} ]
字符串测试
=
等于则为真!=
不相等则为真-z
字符串 字符串的长度为零则为真-n
字符串 字符串的长度不为零则为真
文件测试
- -e 文件名 如果文件存在则为真
- -r 文件名 如果文件存在且可读则为真
- -w 文件名 如果文件存在且可写则为真
- -x 文件名 如果文件存在且可执行则为真
- -s 文件名 如果文件存在且至少有一个字符则为真
- -d 文件名 如果文件存在且为目录则为真
- -f 文件名 如果文件存在且为普通文件则为真
- -c 文件名 如果文件存在且为字符型特殊文件则为真
- -b 文件名 如果文件存在且为块特殊文件则为真
例:
cd /bin
if test -e ./bash
then
echo '文件已存在!'
else
echo '文件不存在!'
fi
(),(()),[],[[]],{}
- ()
展开一个新的shell子程序, 所以括号中的变量为局部变量
a="123"
(a="456";echo "a=$a")
echo "a=$a
打印
456
123
- (())
只支持整数运算,浮点数shell当做字符串处理
- 进行运算扩展
例:
a=$((4+5))
echo "a=$a"
$[]作用相同
- 做数值运算
a=5
((a++))
echo "a=$a"
- 做算数比较
if ((1+1>1));then
echo "1+1>1"
fi
-
[]
test 命令的另一种形式, 其中使用的命令见上一节 -
[[]]
是一个关键字, 比 []
更加通用,其中可以使用正则
例如:
if [[ 1.1 > 1.1 ]] || [[ 1.1 == 1.1 ]]; then
echo "ok"
fi
if [[ "123" == 12* ]]; then #右边是正则不需要引号
echo "ok"
fi
if [[ 2.1 > 1.1 ]]; then #支持浮点型
echo "ok"
fi
- {}
用于通配扩展
循环
循环中 continue命令与break作用和其他语言中类似
for 循环
语法为:
for a in "item1" "item2" "item3"
do
echo $a
done
例:
#输出当前目录下所有.sh结尾的文件
for a in `ls ./`
do
if [[ $a == *.sh ]]
then
echo $a
fi
done
while 循环
语法:
while condition
do
command
done
例如:
#输出1~10000
int=1;
while(($int<=10000))do
echo $int
((int++))
done
函数
shell也可以用户定义函数,然后在shell脚本中可以随便调用
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。 语法格式如下:
[function] funname()
{
cmd....
[return int]
}
函数传参
调用函数时可以向其传递参数。 在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数… 调用的时候 ,函数名,参数直接用空格分割开
output()
{
echo "$1"
echo "$2"
}
output 1 2 #调用output
输出:
1
2
字典
使用示例如下:
#!/bin/bash
#声明
declare -A dic
#赋值
dic=(
[node1]="value1"
[noid2]="value2"
)
# 打印元素个数
echo ${#dic[@]}
#遍历value
echo ${dic[*]}
#遍历key
echo ${!dic[*]}
#使用for循环遍历key
for key in ${!dic[*]}
do
echo "$key : ${dic[$key]}"
done
#字典添加一个新元素
dic+=([key4]="value4")
输出为
2
value1 value2
node1 noid2
node1 : value1
noid2 : value2
解释器要用
bsah
,sh
不支持declare
关键字及其语法
数组
语法如下:
#数组
list=("value1" "value2" "value3")
#打印指定元素
echo ${list[2]}
#打印所有下标
echo ${!list[*]}
#打印所有数组元素
echo ${list[*]}
#数组增加一个元素
1. list+=("value4")
2. 下标添加
#for循环
for key in ${list[@]}
do
echo ${key}
done
输出如下:
value3
0 1 2
value1 value2 value3
value1 value2 value3 value4
value1
value2
value3
value4
截取字符串
注意,字符串长度从第0位开始计数
#!/bstrn/bash
str="123456789"
echo ${#str} #字符串长度
echo ${str:5} #从左边第五个字符开始到结束
echo ${str:2:4} #左边第二个字符开始四个字符
echo ${str:(-3):1} #倒数第三位
echo ${str:(-3)} #倒数三位
echo ${str:(-4):2} #倒数第四位开始两位
输出
9
6789
34
3456
7
789
67
以上方法应该基本能囊括所有的基本需求了
去除行首空格
sed 's/^[ \t]*//g'
去除行尾空格
sed 's/[ \t]*$//g'
删除所有空格
sed s/[[:space:]]//g
时间
echo $(date +“%Y%m%d%H%M%S") #年月日时分秒
echo $(date +"%F %T) #年月日时分秒
输出格式不同
20190718135605
2019-07-18 13:56:05
获取昨天/明天…的日期
使用格式为
获取今天之后的n天: date +"%Y%m%d" -d "+ n days"
获取今天之前的n天: date +"%Y%m%d" -d "- n days"
#!/bin/bash
today=$(date +"%Y%m%d")
yday=$(date +"%Y%m%d" -d "yesterday")
day=$(date +"%Y%m%d" -d "-2 days")
echo ${today}
echo ${yday}
echo ${day}
输出为
20190816
20190815
20190814
执行sql查询保存结果
假如有一张student
表, 有id name score
等字段
1.
例:
HOST="127.0.0.1"
USER="root"
PASSWORD="..."
DBNAME="black_db"
TABNAME="student"
QUERY_SQL="SELECT id, name FROM ${TABNAME}"
while read -a row
do
echo ${row[0]} ${row[1]}
done< <(echo ${QUERY_SQL} | mysql -h${HOST} -u${USER} -p${PASSWORD} ${DBNAME})
输出
id name
1 lzj
2 balcklv
3 whitelv
4 lv
字段保存在对应的数组中打印
当然也可以赋值给变量保存供后续使用
BLACKDB="mysql -uroot -p123456lzj -h127.0.0.1"
SQL="select id, if (name = '', -1, name), money from blackDB.bankDB"
result=$(${BLACKDB} -N -e "${SQL}") #-N去掉字段名 -e接sql命令
echo ${result}
输出
1 lzj 2 blacklv 3 whitelv 4 lv
推荐第二种方法,第一种效率太低,速度太慢
shell编程风格
可以参考Google开源项目风格指南
我的风格和其中的推荐也不是完全一样,不过有些还是应该听取其建议的,比如对于变量和函数的命名风格,对于变量的扩展格式等,说的就很对,应该吸取其建议
浮点数的计算和比较
- 使用
bc
命令,scale
指明精度
res=$(echo "scale = 2; ${x} / ${y}" | bc) #保留两位小数,计算x / y的结果
if [ $(echo "${res} < 0.6" | bc) -eq 1 ]; then #比较res的值和0.6的大小
echo "hello world"
fi
但是bc
命令计算在计算0.xxx的时候,不会显示0
, 就加入算出来是0.1
, 值会是.1
, 虽然不妨碍计算和比较:
echo $(echo "${res} + 0.5" | bc) #不会妨碍结果,值为1.00
- 使用
awk
#!/bin/bash
a=7
b=9
c=$(echo $a $b | awk '{print $1 / $2}')
echo $c
d=$(echo $a $b | awk '{printf("%.2f", $1/$2)}') #使用printf可以控制精度
echo $d
输出
0.777778
0.78
if [] 中的逻辑的判断、比较,参数的使用
1 字符串判断
str1 = str2 当两个串有相同内容、长度时为真
str1 != str2 当串str1和str2不等时为真
-n str1 当串的长度大于0时为真(串非空)
-z str1 当串的长度为0时为真(空串)
str1 当串str1为非空时为真
2 数字的判断
int1 -eq int2 两数相等为真
int1 -ne int2 两数不等为真
int1 -gt int2 int1大于int2为真
int1 -ge int2 int1大于等于int2为真
int1 -lt int2 int1小于int2为真
int1 -le int2 int1小于等于int2为真
3 文件的判断
-r file 用户可读为真
-w file 用户可写为真
-x file 用户可执行为真
-f file 文件为正规文件为真
-d file 文件为目录为真
-c file 文件为字符特殊文件为真
-b file 文件为块特殊文件为真
-s file 文件大小非0时为真
-t file 当文件描述符(默认为1)指定的设备为终端时为真
3 复杂逻辑判断
-a 与
-o 或
! 非
该条收集于博客
坑点
shell 脚本对格式的要求比较严格,有时候很容易踩坑
win和linux格式
第一次win下写完同步到linux后运行报了一堆莫名其妙的错误,意识到可能是文件存储格式不对,转一下就好了
date+%F的使用
date
和+
之间要有空格, 而+
和%F
之间不能有空格
echo $(date +%F)
echo $(date +"%F %T") #%F %T相当于后面跟了个字符串记得加引号
if [] 的使用
我们现在进行如下的判断
i=0
if [ ${i} -ne 0 ]; then
需要注意的是[]
中收尾要加空格
if [${i} -ne 0]; then #这样不加空格就会报错
declare 在函数内声明赋值
字典的 declare 声明式如果是在函数内部,那么在函数内 / 外再为其赋值,都不对,echo
打印一下会发现其元素内容总是不对的,
但是如果·declare -A`的声明式在函数外部,那么一切正常
这个问题我还没明白为什么,按理说应该都是全局变量
日后明白了再更
欢迎解惑···
赋值时空格的问题
要注意必须是:a=b
不能是:a = b
=左右不能有空格
效率问题
在 shell 中循环次数过多效率极低
一般将结果存入文件, 再使用 awk 读出
将数组作为参数传递
假设有数组array
#!/bin/bash
function ()
{
arr=$1
for i in ${arr[*]}; do #arr保存的是array中所有元素,以空格区分,并不是直接arr就是一个数组
echo $i
done
}
showArr "${array[*]}" #必须这样传递,不然只会传数组第一个元素
数据库查询结果替换分隔符
data=$(${MYSQL} -s -B -N -e "${SQL}")
if [ "x${data}" != "x" ]; then
echo "${data}" >> "file.tmp"
fi
awk 'BEGIN{FS=" "}{print $1,$2 ...$n "|" }' OFS="," file.tmp${i} >> file${i}
$1...$n
代表 n 个字段,FS=“ ”代表将结果中的空格分隔换为OFS=","的逗号分隔,并以 “|”结尾(当然也可以不要)
先将查询结果存入tmp
文件在使用awk
处理,不存入文件直接处理会丢失数据,多条结果只会处理第一条 (理所当然了,你的参数只有第一条的)
将Query结果写入文件不换行?
将 DB 的查询结果直接 echo 重定向写入文件却没有换行?
是因为没有加 ""
应该是 echo "${query_result}" > file