0%

shell脚本

介绍编写shell脚本的常用技巧,以及一些实用的脚本片段。

参数

变量 说明
$# 参数的个数
$* / $@ 所有参数
$0 第0个参数,一般是脚本文件名
$1 第1个参数,其余参数依次类推
$$ 当前进程ID

$*$@没被双引号括起来的时候,是没区别的,将每个参数看作一份数据,用空格分隔。如果用双引号括起来,"$*"将所有参数看作一个整体,"$@"依旧将每个参数看作一份数据。可以用for循环体现出差别。

脚本将所有的特殊变量都打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

echo "param number = $#"
echo "all param(\$*) = $*"
echo "all param(\$@) = $@"
echo "\$0 = $0"
echo "\$1 = $1"
echo "current PID: $$"

echo "print each param from \"\$*\""
for i in "$*"
do
echo $i
done

echo "print each param from \"\$@\""
for i in "$@"
do
echo $i
done

执行的输出示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@pkserver test]# ./special_variable.sh 1 2 3 4 5 6
param number = 6
all param($*) = 1 2 3 4 5 6
all param($@) = 1 2 3 4 5 6
$0 = ./special_variable.sh
$1 = 1
current PID: 21009
print each param from "$*"
1 2 3 4 5 6
print each param from "$@"
1
2
3
4
5
6

返回值

exit命令用于结束脚本的运行。可以接一个参数,表示脚本的放回值。一般用0表示正常结束,非0表示异常。通过遍历$?可以获取脚本返回值。如下是示例。

1
2
3
4
#!/bin/bash

echo "exit with 1"
exit 1

示例输出

1
2
3
4
[root@pkserver test]# ./exit.sh
exit with 1
[root@pkserver test]# echo $?
1

流程控制语句

条件语句if

if语句的基本格式如下。[的两边一定要有空格,];的两边一定也要有空格,否则会有语法错误。

1
2
3
if [ condition ]; then

fi

if语句的关键在于条件condition怎么写。下面给出常用的示例。

[和[[的区别

TODO

判断是否为空

一定要用双引号将变量括起来。否则,如果变量的值包含空格,则会有语法错误。

1
2
3
4
5
if [ ! "$var" ]; then
echo "is null"
else
echo "not null"
fi

判断是否相等

在变量前加上一个附加前缀x(任意字符串均可)。否则,如果$var1为空,则会有语法错误。

1
2
3
4
5
if [ x"$var1" = x"$var2" ]; then
echo "equal"
else
echo "not equal"
fi

多条件

  • 或:-o / ||
  • 与:-a / &&
1
2
3
4
5
6
7
8
# 或逻辑用 -o
if [ condition1 -a condition2 ]; then

fi
# 或逻辑用 ||
if [ condiditon1 ] && [ condition2 ]; then

fi

数字比较

TODO

判断文件

运算符 描述
-e filename 如果文件存在则为真
-d filename 如果filename是目录则为真
-f filename 如果filename是常规文件则为真
-L filename 符号链接
-c filename 字符文件
-b filename 块文件
-r filenmae 文件可读
-w filenmae 文件可写
-x filename 文件可执行

循环语句for

for语句的基本语法如下。range变量包含一系列的值,从range中依次取出值并赋给item。可以在range的位置使用任何命令构造一系列的值。循环体可以使用continuebreak关键字。

1
2
3
4
for item in range
do
echo $item
done

例如,遍历当前目录的所有文件。

1
2
3
4
5
# 如果文件名包含空格,则下面的代码无法正常工作
for file in $(ls)
do
echo "$file"
done

循环语句while

基本语法如下。循环体可以使用continuebreak关键字。

1
2
3
4
5
6
while condition
do

done

# TODO: 补充do while

分支语句case

case语句的基本语法如下。

1
2
3
4
5
6
7
8
9
10
11
case $var in
匹配模式1)
# do something
;;
匹配模式2)
# do other something
;;
*)
# 未匹配上述任何模式
;;
esca

函数

定义函数的基本语法如下。在脚本中使用funcname即可调用函数。

1
2
3
4
function funcname()
{

}

函数参数

与shell脚本的参数一致,具体请参考参数一节。注意$0不是函数名,依旧还是脚本的文件名。

函数返回值

在函数中,可以通过return关键字(提前)返回。return关键字可以携带一个参数,表示函数的返回值。通过变量$?可以获取函数的返回值。

示例代码:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

function fun()
{
echo "retutn with 2"
return 2
}

fun
echo "fun return value is $?"

示例输出:

1
2
retutn with 2
fun return value is 2

函数返回字符串

return只能返回一个数字,不能返回字符串。可以在函数中使用echo输出字符串,然后将输出赋值给一个变量。如下是示例代码。注意函数的调用方式。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

function func()
{
echo func
return 2
}

var=$(func) # 使用 $() 调用函数
ret=$?
echo var=$var
echo ret=$ret

示例输出:

1
2
3
[root@pkserver test]# ./function_return_string.sh
var=func
ret=2

eval

在命令前加上eval,会对命令扫描两遍。第一遍扫描会进行替换,第二遍执行指令。一般用于实现一遍扫描无法实现的功能。考虑如下指令,第一遍扫描会替换成echo 1,第二遍扫描执行指令,输出1。

1
2
var=1
eval echo $var

下面给出eval的两种应用场景。

变量名中包含变量

有些时候不同的变量包含了类似的数据,只是在不同的情况下选择不同的数据。如果使用if语句,if语句可能会写的很长。如果增加了一种类别,改动的地方也比较多。可以用eval解决这个问题。

假如有一个图书管理系统,现在有三种书,分别是math、computer、novel。现在要查询每种类别下有哪些书籍,可以使用eval完成。假如用户输入的是matheval第一遍扫描会替换成echo $BOOK_math,第二遍正常执行指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

BOOK_math="高等数学 立体几何 复变函数 高等代数"
BOOK_computer="C语言程序设计 汇编语言程序设计 C++语言程序设计"
BOOK_novel="教父 亮剑 巴黎圣母院"

while read -p "请输入类别: " choice
do
BOOK=$(eval echo \$BOOK_$choice)
if [ ! "$BOOK" ]; then
echo "糟糕,没有对应的书籍"
else
echo "现在有的书籍是:$BOOK"
fi
done

下面是示例输出。只需要输入对应的类别,就能输出对应的书籍。如果要增加新的类别,只需要新定义一个变量即可,后面的处理代码不需要更改。

1
2
3
4
5
6
7
请输入类别: math
现在有的书籍是:高等数学 立体几何 复变函数 高等代数
请输入类别: computer
现在有的书籍是:C语言程序设计 汇编语言程序设计 C++语言程序设计
请输入类别: nomath
糟糕,没有对应的书籍
请输入类别:

向函数传递变量

通过函数参数只能传递字符串。但有些时候需要对全局变量进行操作,如果写死,则函数变得非常专一,无法通用。可以通过eval解决这个问题。

考虑下面对变量进行排序和去重的函数。将变量名作为函数参数,函数中通过变量名获取变量的值、通过变量名对变量进行赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash

list1="3 8 5 2 9 1 3 5 8"
list2="9 45 6 32 48 75 5 25 45 9"

# $1 var name
function sort_and_uniq()
{
local var_name=$1
if [ ! "$var_name" ]; then
return 0
fi
local var=$(eval echo \$$var_name) # 通过变量名获取变量的值
if [ ! "$var" ]; then
echo "$var_name is null"
return 1
fi
var=$(echo $var | tr ' ' '\n' | sort -n -u | tr '\n' ' ')
eval $var_name=\$var # 通过变量名进行赋值
}

sort_and_uniq "list1"
sort_and_uniq "list2"
sort_and_uniq "list3"
echo "list1=$list1"
echo "list2=$list2"

下面示例代码的输出。可以看出,函数正确实现了其功能,并且能识别出值为空的变量。

1
2
3
4
[root@pkserver test]# ./eval_function.sh
list3 is null
list1=1 2 3 5 8 9
list2=5 6 9 25 32 45 48 75

判断命令是否有效

1
2
3
4
if ! type -t command > /dev/null; then
echo "command not exits, please check"
exit
fi

数组

数学运算

log