shell 脚本

参考:Shell Scripts, Advanced Bash-Scripting Guide, nixCraft, Command Line Fu,



引言

shell既可以交互式地执行用户输入的命令,也可以编写成脚本,shell按照一定的规则自动执行脚本中的命令。

shell脚本文件一般是下面的这种形式:

$ cat sample.sh
#!/bin/bash
echo "hello world!"
exit 0

注意:exit 0表示该脚本的exit status。

使用sh调用该脚本:

$ sh sample.sh

也可以先赋予脚本可执行权限,然后直接执行:

$ chmod +x sample.sh
$ ./sample.sh

*nix上的shell有很多种,本文仅基于bash和sh,列出基本的语法,重点关注容易出错的细节。

[TOP]


变量

本地变量

本地变量指用户自定义变量。

变量赋值:

$ name="Bill Bu"

变量赋值时的等号左右不能有空格。变量默认为字符串(不能进行算术计算),如果变量值包含空格,需要使用单引号或双引号。

命令的结果可以作为一个值赋予变量,这时应使用反引号或$()包含命令;变量值中可以使用转义符\

变量叠加:

$ name="$name"ad

或者

$ name=${name}ad

命令代换:

$ VAR_DATE=`date`

注意:这里‘是反引号,不是单引号。

更常用的是

$ VAR_DATE=$(date)

表示将括号中命令的结果输出,赋值给VAR_DATE。

算术代换:

$ VAR=101
$ echo $(($VAR-1))

上文提到,变量赋值时默认为字符串,如果需要进行算术运算,可以将算术表达式用$(())扩起来。$(())中只能进行整数运算,可选的运算符只有+-*/。注意:括号、变量和运算符之间均没有空格。

变量调用:

$ echo $name

变量查看:

$ set

变量删除:

$ unset name

本地变量可导出为环境变量:

$ export VARNAME

setunset也适用于环境变量。

变量替换运算符:

${VAR:-word} # 如果VAR存在且非null,返回$VAR;否则,返回word。
${VAR:=word} # 如果VAR存在且非null,返回$VAR;否则,VAR=word,并返回$VAR。
${VAR:?word} # 如果VAR存在且非null,返回$VAR;否则,输出 VAR:message,并退出当前命令或脚本。
${VAR:+word} # 如果VAR存在且非null,返回word;否则,返回null。

如果省略:,则表示“如果VAR存在”。

环境变量

查询:

$ env

临时添加PATH变量:

$ PATH="$PATH":/home/name/sh
PATH 可执行文件的搜索路径
SHELL 当前shell
TERM 当前终端类型
LANG 语言和locale,决定编码,时间、货币等显示格式
HOME 当前用户主目录


[TOP]


条件判断

常用的条件判断形式为:

[ EXPRESSION ]

如果表达式成立,返回0,否则返回非0。注意:括号与表达式之间、表达式中各命令、参数之间都必须加空格。

$ [ 1 = 2 ]
$ echo $?
1
$ [ 1 = 1 ]
$ echo $?
0

这里,$?表示上一命令的执行结果,若命令成功则为0,否则非0。

数值比较

n1 -eq n2 n1等于n2
n1 -ge n2 n1大于等于n2
n1 -gt n2 n1大于n2
n1 -le n2 n1小于等于n2
n1 -lt n2 n1小于n2
n1 -ne n2 n1不等于n2


这些表达式中,e-equalg-greaterl-lesst-than

字符串比较

str1 = str2 字符串相同
str1 != str2 字符串不同
-n str 字符串长度不为0
-z str 字符串长度为0


注意:=!=前后都要有空格。

文件判断

-e file file存在
-d file file是文件夹
-f file file存在,且是一个文件
-s file file存在,且size大于0
-r file file存在,且有可读权限
-w file file存在,且有可写权限
-x file file存在,且有可执行权限
-O file file存在,且当前用户为其所有者
-G file file存在,且与当前用户的所属组相同
file1 -nt file2 file1的修改时间晚于file2
file1 -ot file2 file1的修改时间早于file2


布尔运算

EXPRESSION1 -a EXPRESSION2 逻辑与
EXPRESSION1 -o EXPRESSION2 逻辑或
! EXPRESSION 逻辑非


在命令行下逻辑与、逻辑或的常用形式为:

$ COMMAND1 && COMMAND2
$ COMMAND1 || COMMAND2

注意:&&相当于”if…then…“,||相当于“if not…then…”,都用于连接两个命令。上面的-a-o用于连接两个测试条件,因此,实际使用时语法是有区别的。

可以看到&&||可实现简单的分支流程功能,更复杂的流程控制则需要下面的“流程控制语句”。

[TOP]


流程控制

if-then-else

if COMMAND1
then
  commands1
elif COMMAND2
then
  commands2
else
  commands3
fi

ifelif后可以是条件判断,也可以是一个命令,这个命令返回0时,就运行then之后相应的命令。

case

case var in
pattern1)
  commands1;;
pattern2)
  commands2;;
*)
  default commands;;
esac

casepattern可以匹配整型、字符型常量表达式,也可以匹配字符串,并且支持通配符。

for

for VAR in LIST
do
  commands
done

VAR是一个循环变量,依次取出LIST中的值,执行commands;LIST可以是”1 2 3 4 5”,也可以是“{1..5}”,还可以是“{0..10..2}”,表示“0 2 4 6 8 10”,当然也可以是”$(seq 1 2 20)“。

$ for FILENAME in file?; do mv $FILENAME $FILENAME~; done

这是把file0、file1、file2…重命名为file0、file1、file2…,可以看到LIST支持通配符。

for ((EXP1; EXP2; EXP3))
do
  commands
done

这种格式类似于c语言,如((i=1; i<=5; i++)),甚至可以用(( ; ; ))表示无限循环。

until

until COMMAND
  commands
done

while

while COMMAND
do
  commands
done

while可连接到管道:

ls *.mp3 |
while read line
do
    newname="[uuspider]$line"
    mv $line $newname
done

或使用重定向:

while read line
do
    newname="[uuspider]$line"
    mv $line $newname
done < <(ls *.mp3)

break和continue

break 跳出循环
continue 跳过本次循环


[TOP]


特殊变量和位置参数

$0 shell或shell脚本的名字
$1、$2...${10}... 位置参数,大于9时使用{}
$# 位置参数的个数
$@ 所有位置参数,以列表形式输出$1,$2
$* 所有位置参数,整体作为单个字符串
$? 上条命令的exit status
$$ 当前shell的进程编号


位置参数可以使用shift命令左移,如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2…,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

[TOP]


数组

数组定义

$ array=(11 22 33 44 55)

数组操作

查看数组长度:

$ echo ${#array[@]}

读取:

$ echo ${array[0]}
11
$ echo ${array[@]}
11 22 33 44 55
$ echo ${array[*]}
11 22 33 44 55

赋值:

$ array[0]=00
$ array[5]=11

如果某一项不存在,赋值时会自动添加新的数组元素。

删除:

$ unset array[1]
$ unset array

截取:

$ echo ${array[@]:0:3}
$ newarray=(${array[@]:1:4})

替换:

$ array=(11 22 33 44 55)
$ echo ${array[@]/33/00}
11 22 00 44 55
$ echo ${array[@]}
11 22 33 44 55

[TOP]


交互

$ read

从标准输入读取输入,并赋值给内置变量REPLY。

$ read VAR

从标准输入读取输入并赋值给VAR。

$ read VAR1 VAR2 VAR3

从标准输入读取,第一个字符串赋值给VAR1,第二个字符串赋值给VAR2,其余字符串赋值给VAR3,字符串之间以空格或换行为界。

$ read -p "plese type something: " VAR

输入提示,并将输入赋值给VAR。

$ read -r VAR

输入时可使用反斜杠换行。

$ read -t 10

指定输入等待时间为10秒。

$ read -d ":" VAR

指定定界符为”:”,输入”:”时结束输入。

$ read -s VAR

输入时不显示输入内容。

$ read -p "input 3 letters: " -n 3 VAR

将输入的前3个字符赋值给VAR。

$ read -a array

将输入存储为数组array[@]。

$ read -dq -p "Input some words end with q: " VAR

这行代码表示以“q”为定界符,当输入q时退出输入模式。

[TOP]


函数

函数定义:

function FUN() {
...
}

注意:左花括号与函数体之间必须有空格或换行,函数体中最后一条命令与右花括号写在同一行时,末尾必须有;

函数调用:

FUN

函数只有调用时候才会执行,shell脚本中的函数必须先定义再调用。注意:函数调用时,只写函数名称,不写括号。

函数返回值:

return 返回值

函数可以用return命令返回脚本,后面跟一个数字则表示函数的Exit Status。

[TOP]


脚本调试

$ sh -x ./script.sh

跟踪脚本的执行信息,将执行的每一条命令和结果依次打印出来。

-n 检查脚本中的语法错误
-v 执行脚本,同时打印到标准错误输出
-x 跟踪执行信息


脚本中可以使用set启用或禁用上述调试参数,如:

#! /bin/sh
if [ -z "$1" ]; then
  set -x
  echo "ERROR: Insufficient Args."
  exit 1
  set +x
fi

[TOP]


通过curlgrepsedawk,shell可以实现简单的爬虫功能,使用python,可以实现更复杂的爬虫功能