如何书写shell脚本

关于Linux基础命令,可以查看另一篇博文 Linux Shell脚本攻略笔记

以下内容,主要是,了解书写shell脚本所需要的大部分知识,主要内容来自于书籍和网络

目的是,能快速书写出需要的shell脚本

开始

version 0.1 2014-01-12 基本内容, 完成度30%


资源

google shell style guide

Linux Shell脚本攻略笔记

Linux Shell编程实战技巧

Bash编程易犯的错误 1234

关于shell脚本编程的10个最佳实践

Bash Pitfalls


第一部分 一些概念

标准IO

文件描述符
0 标准输入 默认键盘
1 标准输出 默认终端
2 标准错误 默认终端

重定向

> 输出重定向
>> 追加到输出重定向
< 输入重定向
<< 追加到输入重定向

ls -l > /tmp/a

cmd >/dev/null 2>&1 #输出到垃圾桶

管道

前后连接两个命令

ls -l | grep test

引号

双引号:可以除了字符$`\外地任何字符或字符串
单引号:忽略任何引用值,将引号里的所有字符作为一个字符串 $var 不能被解析
反引号:设置系统命令输出到变量

shell脚本识别三种基本命令:内建命令,shell函数和外部命令

基本的命令查找:shell会沿着查找路径$PATH来寻找命令

echo $PATH

可以在.profile文件中修改
export PATH=$PATH:$HOME/bin

and/or

expression1 && expression2 && expression3
只有前面一条命令执行成功,才执行下一条
expression1执行成功,才执行expression2
串联的

expression1 || expression2 || expression3
执行命令,直到有一条成功为止

第二部分 shell脚本

首行声明使用bash(声明脚本执行解释器)

#!/bin/bash

# do something

exit 0/n

运行

sh xx.sh
bash xx.sh #大部分情况下两个一样,某些命令只有bash有,只能用这个

or

chmod u+x xx.sh
./xx.sh

调试

#查看运行时,每个命令回显,执行之后回显
sh -x xx.sh

#执行之前回显
sh -v xx.sh

#检查语法错误,不执行
sh -n xx.sh

#如果使用了未定义的变量,给出错误信息
sh -u xx.sh

#调试部分脚本
echo "Hello $USER,"
set -x
echo "Today is $(date %Y-%m-%d)"
set +x

判断执行结果

N=$?  #0 <= N <= 255

0 无错误,正常执行结束
非0 异常
    1-125命令不成功退出
    126命令成功,但文件无法执行
    127命令找不到
    >128命令因收到信号而死亡

获取目录名和文件名

# To find base directory
APP_ROOT=`dirname "$0"`

# To find the file name
filename=`basename "$filepath"`

# To find the file name without extension
filename=`basename "$filepath" .html`

e.g.
BASEDIR=$(dirname $0)
cd $BASEDIR
CURRENT_DIR=`pwd`

日期

TODAY=`date +%Y%m%d`
DAY_1_AGO=`date -d "$TODAY 1 days ago" +%Y%m%d`

常用接受日期/使用默认日期处理

if [ -n "$1" ]
then
    TODAY="$1"
else
    TODAY=`date +%Y%m%d`
fi

crontab调度

查看
crontab -l
编辑
crontab -e

格式
* * * * * command_path

字段      含义     范围
1        分钟         0-59
2        小时         0-23
3        日期         1-31
4        月份         1-12
5        星期几,0=周日   0-6
6        具体命令,可以是调用脚本

*任意时刻
n1,n2  分割,n1和n2
*/n  每隔n单位
n1-n2   时段,一个时段内

0 */2 * * * sh run.sh     每隔两小时
20 7 * * * sh run.sh 每天7:20
0 1,5 * * * sh run.sh 每天1点和5点
* * * * * sh run.sh 每分钟执行一次

第三部分 变量

1.变量赋值

varname="value"
varname=`expression`

注意,等号两边必须不能包含空格

2.分类

四种变量:环境变量、本地变量、位置变量、特定变量参数

环境变量可作用于所有子进程
本地变量在用户现在的shell 生命期的脚本中使用,仅存在于当前进程
位置变量:作为程序参数
特定变量:特殊作用

3.环境变量

设置
MYVAR="test"
expirt MYVAR
or
export MYVAR="test"

只读
MYVAR="test"
readonly MYVAR
or
readonly MYVAR="test"

显示
export -p
env #查看所有环境变量
$MYVAR #获取

消除
unset MYVAR

4.本地变量

设置
LOCAL_VAR="test"
or
LOCAL_VAR="test"
readonly LOCAL_VAR #设置只读

还可以使用declare命令定义

5.位置变量

$0 脚本名称
$# 传递到脚本参数个数
$$ shell脚本运行当前进程ID
$? 退出状态
$N N>=1,第n个参数

6.字符串处理

长度

${#VARIABLE_NAME} 可以给出字符串的长度。

if [ ${#authy_api_key} != 32 ]
then
    return $FAIL
fi

拼接字符串

echo "$x$y"

字符串切片

${变量名:起始:长度}得到子字符串

$ test='I love china'
$ echo ${test:5}
e china
$ echo ${test:5:10}
e china

str="hello world"
echo ${str:6}  # ${var:offset:length}

字符串替换

${变量/查找/替换值} 一个“/”表示替换第一个,”//”表示替换所有,当查找中出现了:”/”请加转义符”\/”表示
echo ${str/foo/bar} #首个
echo ${str//foo/bar} #所有

正则匹配

if [[ $str =~ [0-9]+\.[0-9]+ ]]

7.数值处理

自增

a=1
a=`expr a + 1`

or

a=1
let a++
let a+=2

let

no1=4
no2=5
let result=no1+no2

expr

result=`expr 3 + 4`
result=$(expr $no1 + 5)

其他

result=$[ no1 + no2 ]
result=$[ $no + 5 ]

result=$(( no1 + 5 ))

浮点数

echo "4 * 0.56" | bc
设定精度
echo "scale=2;3/8" | bc
进制转换
echo "obase=2;100" | bc
平方
echo "sqrt(100)" | bc

数组和map


第四部分 控制流

1.条件测试

语法

test condition
[ condition ] #注意两边加空格

$? #获取判断结果,0表示condition=true

条件测试中的逻辑

-a 与
-o 或
!  非
&&
||

if [ -n "$str" -a -f "$file" ]
if [ -n "$str" ] && [ -f "$file" ]

字符串测试

=   两字符串相等
!=  两字符串不等
-z  空串 [zero]
-n  非空串 [nozero]

[ -z "$EDITOR" ]
[ "$EDITOR" = "vi" ]

数值测试

-eq  数值相等(equal)
-ne  不等(not equal)
-gt  A>B(greater than)
-lt  A<B(less than)
-le  A<=B(less、equal)
-ge  A>=B(greater、equal)

N=130
[ "$N" -eq 130 ]

文件测试

-d目录
-f 普通文件(Regular file)

-e 文件存在
-z 文件长度=0
-s 文件长度大于0,非空

-b 块专用文件
-c 字符专用文件
-L 符号链接

-r Readable(文件、目录可读)
-w Writable(文件、目录可写)
-x Executable(文件可执行、目录可浏览)

-g 如果文件的set-group-id位被设置则结果为真
-u 文件有suid位设置

2.分支if-else/case

if-else语法

if condition1
then
	//do thing a
elif condition2
then
	//do thing b
else
	//do thing c
fi

or

if condition; then
# do something
fi

case语法

case $VAR in
	1)
		echo "abc"
		;;
	2|3|4)
		echo "def"
		;;
	*)
		echo "last"
		;;
esac

3.循环for/while/until

for语法

for VARIABLE in 1 2 3 4 5 .. N
do
    //commands
done

for OUTPUT in $(Linux-Or-Unix-Command-Here)
do
    //commands on $OUTPUT
done

#bash
for (( EXP1; EXP2; EXP3 ))
do
    //commands
done

例子

for i in 1 2 3 4 5; do
	echo $i
done

for i in `seq 1 5`; do
	echo $i
done

#!/bin/bash
echo "Bash version"
for i in $(seq 1 2 20)
do
   echo "Welcome $i times"
done

for i in {1..5}; do
	echo $i
done

#!/bin/bash
echo "Bash version"
for i in {0..10..2}
do
    echo "Welcome $i times"
done

for ((i=1; i<=10; i++)); do
	echo $i
done

#无限循环
#!/bin/bash
for (( ; ; ))
do
   echo "infinite loops [ hit CTRL+C to stop]"
done

while

while condition
do
	//do something
done

COUNTER=0
while [ $COUNTER -lt 5 ]
do
	COUNTER=`expr $COUNTER + 1`
	echo $COUNTER
done


无限循环
while [ 1 ]
do
	//
done

until

#执行命令,直到条件为真,至少执行一次,可以用来做监控,condition每次都回去检查
until condition
do
	//do something
done

break/continue

break
允许跳出循环,通常在进行一些列处理后退出循环或case语句
若多重循环,可指定跳出的循环个数,如跳出两重循环  break 2

continue
不会跳出循环,只是跳过此循环步
命令是程序在本循体内忽略下面的语句,从循环头开始执行

第五部分 函数

1.函数定义

function func_name() {

}

func_name() {
	//do some thing
}

注意

函数名,在脚本中必须唯一
函数必须,先定义,后使用

return

function equal() {
    return 1
}

equal
echo $? #got 1

2.参数传递

#位置参数
function copyfile() {
	cp $1 $2
	return $?
}

调用

copyfile /tmp/a /tmp/b
or获取返回值
result=`copyfile /tmp/a /tmp/b`

位置参数

$1 - $9,当参数超过10个时,需要使用${10}
$# 参数个数
$* 将所有参数视为一个字符串="$1 $2 ..."
$@ 将所有参数视为个体="$1" "$2" "$3"

3.返回值和退出状态

#返回值
function func_a() {
	return 1
}

result=`func_a`
if [ result != 0 ]
then
	echo "Error"
fi

#退出状态
function func_b() {
	//do something
}

func_b
if [ $? -eq 0 ]
then
	echo "Success"
else
    echo "Error"
fi

#更简洁
if func_b; then
    echo "Success"
else
    echo "Error"
fi

func_b && echo "Success" || echo "Error"

第四部分 高级

bash中参数展开-展开运算符

${varname:-word} 如果变量未定义,返回默认值.  ${noexist:-0}返回0
${varname:=word} 如果变量未定义,设置变量为默认值  ${noexists:=0}; echo ${noexists}; 得到0

${varname:?message} 若未定义,显示varname:message并退出当前的命令或脚本
${varname:+word} 若存在且非null,返回word,否则返回null

模式匹配

${variable##pattern}
${variable%pattern}

第五部分 其他

读文件

while read -r line; do
    echo $line
done < file

保留首尾字符
while IFS= reaad -r line; do
    echo $line
done

一些内置命令

:
空命令,类似python的pass

.
相当于source

\
用于跨行命令

echo
输出,类似println

exec

exit n
脚本以n作为退出码退出

export
设置或显示环境变量

expr
简单计算
x=`expr $x + 1`
x=$(expr $x + 1)

let
d=111
let d=$d+1; echo $d
112

printf
格式化输出

return
函数返回

set

shift
所有参数变量左移一个位置

unset
从环境变量中删除变量或函数

BP:

使用$() 代替反引号``
$(()) 代替expr运算符

bash

GNU Bash 主页
http://www.gnu.org/software/bash/
GNU Bash 手册
http://www.gnu.org/software/bash/manual/

更多的特性

$((3 + 4))          而不需要 expr 3 + 4, 算术展开
/usr/{bin,local/bin}  而不需要 /usr/bin /usr/local/bin
${str/src/dst}       而不需要 echo $str | sed ”s/$src/$dst/”

更方便的语法

for (( expr1; expr2; expr3 )); do
        commands
done
for (( i = 0; i < 100; i++ )); do … done
echo a{b,c,d}e  ==> abe ace ade

表达式求值

$[]    []$中间可以加表达式  eg: echo $[$a+$b]
$(())   (())中间可以加表达式。Eg: total=$(($a*$b))

正则

bash的正则表达式
str='hello, world'
if [[ $str =~ '\s+world$' ]]; then
    echo match!
fi
if echo "$str" | grep -E '[ ]+world$'; then
    echo match!
fi

获取软连接指向的真实文件名

#注:有些系统没有这个命令
readlink /usr/bin/python

增加debug

function debug() {
    if [[ $DEBUG ]]
    then
        echo ">>> $*"
    fi
}

# For any debug message
debug "Trying to find config file"

还有来自于一些很酷的Geeks的单行debug函数:

function debug() { ((DEBUG)) && echo ">>> $*"; }
function debug() { [ "$DEBUG" ] && echo ">>> $*"; }

将执行日志全部写到某个文件

exec >>"$LOGPATH"/xx.log.$TODAY 2>&1
#begin of code


linux

3192 Words

2014-01-12 00:00 +0000