shell脚本学习总结及坑点记录


shell脚本是一种为 shell 编写的脚本程序, 一般文件后缀为 .sh
shell 的解释器种类众多:

  • sh: 即 Bourne Shell, sh 是 Unix 标准默认的 shell
  • bash: 即 Bourne Again Shell, bash 是 linux 标准默认的 shell

shell 脚本中开头第一句的#!告诉系统其后路径所指的程序即是解释此脚本文件的
shell 解释器

shell 变量

shell 变量命名:

  1. 只能使用英文字母, 数字和下划线, 首个字符不能以数字开头
  2. 中间不能有空格可以使用下划线
  3. 不能使用标点符号
  4. 不能使用 bash 里的关键字

局部变量和全部变量

shell 脚本中的变量默认都是全局的, 即其作用于从声明定义处一直到程序结束,如:

#!/bin/bash

func() 
{
    i="hello world"  #变量定义是=左右不能有空格
}

func
echo ${i}

输出为:

hello world

想要声明局部变量使用local关键字

预定义变量

  1. $0~9, $0 是脚本文件名, $1~9 是命令行参数
  2. $# 命令行参数个数
  3. $@ 所有命令行参数
  4. $$ 执行的进行id
  5. $? 上个命令的退出状态, 或函数的返回值

变量默认值

value=${somevalues:othervalues}
#如果somevalues被定义了,则value=somevalues,否则values=othervalues

环境变量

  1. HOME 用户主目录
  2. PATH: 系统环境变量 PATH
  3. TERM: 当前终端
  4. PWD: 当前工作目录, 绝对路径

shell 常用关键字

  1. echo
  2. exec: 执行另一个 shell 脚本
  3. read: 读标准输入
  4. expr: 对整数型变量进行算数运算
  5. test: 用于测试变量是否相等, 是否为空, 文件类型等
  6. exit: 退出
  7. who: 显示当前登录系统用户名
  8. 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 ${@}
  1. 当有不认识的选项时为 ?)
  2. ;; 相当于 break, 必须加
  3. esac就是 case 反回来
  4. 每个参数后跟一个冒号表示其需要一个参数
  5. 可以在参数前加一个冒号,如:while getopts ":a:b:" opt,这个:表示getopts不返回错误
  6. OPTARG变量保存当前参数,OPTINDargv的当前索引值

解析命令行参数工具

getoptgetopts用于 shell 脚本中分析脚本参数

  1. geopts 是 shell 内置命令, getopt 是一个独立外部工具
  2. getopts 使用语法简单, getopt 使用语法较复杂
  3. getopts 不支持长参数(如: --option), getopt 支持
  4. getopts 不会重拍所有参数顺序, getopt 会重拍参数顺序
  5. getopts 出现的目的是为了替代 getopt 较快捷的执行参数分析工作

getopt 所设置的全局变量:

  1. optarg 指向当前选项参数(如果有)的指针
  2. optind 再次调用 getopt() 时的下一个 argv 指针的索引
  3. optopt 最后一个位置选项

调用main函数

定义完main函数, 使用 main "$@"进行对main的调用

获得执行脚本的当前绝对路径

basepath=$(cd "$(dirname $0)"; pwd)

cut命令

cut 命令用来从文件或者标准输入中读内容并截取特定部分送到标准输出

  1. -b : 输入每行的第 n 个字符(有中文就乱码)
  2. -c : 输入每行的第 n 个字符(适用中文)
  3. -d: 自定义分隔符
  4. -f : 与 -d 一起使用, 指定显示哪个区域
  5. -n : 取消分割多字节字符(例如中文), 仅与 -b 一起使用

例如:

$ echo long ago test befor | cut -f 2,3 -d ' '
ago test

test 命令

用于检查某个条件是否成立, 可以进行数值, 字符串和文件三个方面的比较

[] 是test命令的一种形式

数值测试

  1. eq (equal的缩写),表示等于为真

  2. ne (not equal的缩写),表示不等于为真

  3. gt (greater than的缩写),表示大于为真

  4. ge (greater&equal的缩写),表示大于等于为真

  5. lt (lower than的缩写),表示小于为真

  6. le (lower&equal的缩写),表示小于等于为真

num1=100
num2=100
if test $[num1] -eq $[num2]
then
    echo '两个数相等!'
else
    echo '两个数不相等!'
fi

# 可以写为 if [ ${num1} -eq ${num2} ]

字符串测试

  1. = 等于则为真
  2. != 不相等则为真
  3. -z 字符串 字符串的长度为零则为真
  4. -n 字符串 字符串的长度不为零则为真

文件测试

  1. -e 文件名 如果文件存在则为真
  2. -r 文件名 如果文件存在且可读则为真
  3. -w 文件名 如果文件存在且可写则为真
  4. -x 文件名 如果文件存在且可执行则为真
  5. -s 文件名 如果文件存在且至少有一个字符则为真
  6. -d 文件名 如果文件存在且为目录则为真
  7. -f 文件名 如果文件存在且为普通文件则为真
  8. -c 文件名 如果文件存在且为字符型特殊文件则为真
  9. -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当做字符串处理
  1. 进行运算扩展
    例:
a=$((4+5)) 
echo "a=$a"

$[]作用相同

  1. 做数值运算
a=5
((a++))
echo "a=$a"
  1. 做算数比较
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开源项目风格指南
我的风格和其中的推荐也不是完全一样,不过有些还是应该听取其建议的,比如对于变量和函数的命名风格,对于变量的扩展格式等,说的就很对,应该吸取其建议

浮点数的计算和比较

  1. 使用 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
  1. 使用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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值