Shell编程

1. shell简明教程

1.1 基本命令

1
2
#!/bin/bash   
echo "Hello World !" //输出文本

#!是约定标记,表示使用什么解释器执行。

注释

以#开头的行就是注释,会被解释器忽略。sh里没有多行注释,只能每一行加一个#号。

1
2
3
# --------
# 这是注释块
# --------

打印输出

1
2
3
echo arg
echo -e arg #执行arg里的转义字符,echo 加e会默认换行
echo arg > myfile #结果重定向到文件

echo 后单引号不能转义里面的字符,双引号可有可无。
printf:用于格式化输出,echo的增强版。不默认换行

1
2
printf  format-string  [arguments...]
#format-string 为格式控制字符串,arguments 为参数列表。功能和用法与c语言的 printf 命令类似。

C语言printf()和shell的printf的不同

  • shell printf的不用括号。
  • format-string 可以没有引号,但最好加上。
  • 如果arguments比格式化参数更多,格式化参数可以重用。
  • arguments使用空格隔开。

read: 命令行从设备读入内容

1
2
3
4
5
#!/bin/bash

echo "what is your name"
read NAME #输入到NAME
echo "Hello, $NAME"

变量

定义变量

1
variableName="value"  #变量名和=之间不能有空格

首个字符必须为字母(a-z,A-Z)。
中间不能有空格,可以使用下划线(_)。
不能使用标点符号。
不能使用bash里的关键字(可用help命令查看保留关键字)。

注意:变量中间不能有空格,如果手误写错(例如 var = test),刚好要使用rm -rf $var/删除这个目录,实际删除的是/!

使用变量

1
2
3
4
for skill in C PHP Python Java
do
echo "I am good at ${skill}Script" #这里需要加上花括号
done

可以把变量变为只读或者删除

1
2
3
4
url="http://www.baidu.com"
readonly url #变量为只读
url2="http://www.netease.com"
unset url2 #删除变量,但是不能删除只读变量

变量类型

  1. 局部变量:仅在当前shell实例中有效,其他shell启动程序无法访问局部变量
  2. 环境变量:所有的程序,包括shell启动的程序都能访问环境变量,必要时shell也可以访问环境变量
  3. shell变量:shell变量有shell设置的特殊变量。一部分是环境变量,一部分是局部变量。

特殊变量

变量 含义
$0 当前脚本文件名
$n 传递给脚本的参数
$# 传递给脚本的参数个数
$* 传递给脚本的所有参数
$@ 传递给脚本或函数的所有参数。被双引号(“”)包含时,与$*不同
$? 上个命令的退出状态或函数的返回值
$$ 当前shell进程的ID。对于Shell脚本,就是这些脚本所在进程的id

$*

1.2 运算符

Bash 支持很多运算符,包括算数运算符关系运算符布尔运算符字符串运算符文件测试运算符

算数运算符

原生bash不支持简单的数学运算,但是可以运用awk和expr,expr比较常用
expr是一款表达式计算工具,完成求值工作。

1
2
3
4
5
#!/bin/bash
a=10
b=20
var=`expr $a + $b` #不是单引号,是ESC下面的
echo $var

注意:

  1. 表达式和运算符之间要有空格
  2. *替代*
  3. 注意引号

算数运算符列表

1
2
3
4
5
6
7
8
9
运算符	说明	    举例
+ 加法 `expr $a + $b` 结果为 30。
- 减法 `expr $a - $b` 结果为 10。
* 乘法 `expr $a \* $b` 结果为 200。
/ 除法 `expr $b / $a` 结果为 2。
% 取余 `expr $b % $a` 结果为 0。
= 赋值 a=$b 将把变量 b 的值赋给 a。
== 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。
!= 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。

关系运算符

关系运算符只支持数组,不支持字符串,除非字符串值为数字。

1
2
3
4
5
6
if [ $a -eq $b ]
then
echo "$a eq $b :a is equal to b"
else
echo "$a -eq $b :a is not equal to b"
fi

  • if后面直到直到then前面的分号结束都有空格。

关系运算符列表

1
2
3
4
5
6
7
运算符	说明
-eq 检测两个数是否相等,相等返回 true。同算数运算符`==`
-ne 检测两个数是否相等,不相等返回 true
-gt 检测左边的数是否大于右边的,如果是,则返回 true
-lt 检测左边的数是否小于右边的,如果是,则返回 true
-ge 检测左边的数是否大等于右边的,如果是,则返回 true
-le 检测左边的数是否小于等于右边的,如果是,则返回 true

布尔运算符

1
2
3
4
运算符	说明
! 非运算,表达式为 true 则返回 false,否则返回 true
-o 或运算(or),有一个表达式为 true 则返回 true
-a 与运算(and),两个表达式都为 true 才返回 true
1
2
3
4
5
if [ 3 -gt 2 -a 4 -lt 5 ]
then
echo 'true'

fi

字符串运算符列表

1
2
3
4
5
6
运算符	说明	举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true

文件测试运算符

1
2
3
4
5
6
7
8
9
#!/bin/sh
file="/tmp/test.sh"

if [ -e $file ]
then
echo "File exists"
else
echo "File does not exist"
fi

文件测试运算符列表

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
27
操作符	说明	举例

-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false

-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false

-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false

-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true

-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false

-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false

-p file 检测文件是否是具名管道,如果是,则返回 true。 [ -p $file ] 返回 false

-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false

-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true

-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true

-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true

-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true

-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true

1.3 字符串和数组

字符串

单引号和双引号的区别

  • 双引号里有变量,单引号里原样输出
  • 双引号有转义字符,单引号原样输出
  • 单引号中不能有单引号

获取字符串长度

1
2
str='i love you'
echo ${#str}

截取字符串

1
2
3
4
5
6
#!/bin/bash/
str='i love you'
echo ${str:1} # 从第1个截取到末尾。注意从0开始。
echo ${str:2:2} # 从第2个截取2个。
echo ${str:0} # 全部截取。
echo ${str:-3} # 负数无效,视为0。

查找字符串

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

str="i love you"

echo `expr index "$str" l`
echo `expr index "$str" love` #最后一个参数是字符,字符串只保留首字母
echo `expr index "$str" o`
echo `expr length "$str"` #字符串长度
echo `expr substr "$str" 1 6` #从字符串中位置1开始截取6个字符。索引是从0开始的。
# 注意字符串变量需要加双引号。

expr 更多用法
STRING : REGEXP anchored pattern match of REGEXP in STRING
match STRING REGEXP same as STRING : REGEXP
substr STRING POS LENGTH #从STRING中POS位置开始截取LENGTH个字符。POS索引是从1开始的。
index STRING CHARS #在STRING中查找字符CHARS首次出现的位置,没有找到返回0
length STRING #字符串长度

数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。在Shell中,用括号来表示数组,数组元素用空格符号分割开。定义数组的一般形式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
array_name=(value1 value2 value3)
或者
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2

echo ${array_name[2]} #读取下标为2的元素
echo ${array_name[*]} #读取所有元素
echo ${array_name[@]} #读取所有元素
echo ${#array_name[*]} #获取数组长度
echo ${#array_name[@]} #获取数组长度
echo ${#array_name[1]} #获取数组中单个元素的长度

对比数组和字符串

1
2
3
4
5
6
7
8
9
10
11
# 字符串
str="hello"
${#str} # 读取字符串长度
echo ${str} # 读取字符串全部
echo ${str:1} # 截取字符串
# 数组
arr=(a1,a2,a3)
${#arr[*]} # 读取数组长度
${#arr[1]} # 读取数组某个元素长度
echo ${arr[*]} # 读取数组全部
echo ${arr[1]} # 读取数组某个元素

1.4 条件控制

if 语句

语法

1
2
3
4
if [ expression ]
then
Statement(s) to be executed if expression is true
fi

其他两种if

1
2
if...elif...else...fi
if...else...fi

test命令

1
2
3
4
5
6
7
8
9
#!/bin/bash/
a=10
b=20
if test $a == $b
then
echo "a is equal to b"
else
echo "a is not equal to b"
fi

分支控制:case语句

case…esac与switch…case相同

1
2
3
4
5
6
7
grade="B"
case $grade in
"A") echo "very good";;
"B") echo "Good!";;
*)
echo "sorry";;
esac

注意:取值后面必须有 in,每个模式以 ) 结束。取值可以为变量或常数。;;等同于break;
*)等同于default:

for循环

1
2
3
4
5
6
for 变量 in 列表
do
command1
command2
...
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

# 遍历数组
for value in 1 2 3 4 5
do
echo "count $value"
done
# 遍历字符串
for str in 'This is a string'
do
echo $str
done
# 遍历文件
for FILE in *
do
echo $FILE
done
# 遍历文件内容
citys=`cat city.txt`
for city in $citys
echo $city
done

while循环

1
2
3
4
while command
do
Statement(s) to be executed if command is true
done
1
2
3
4
5
6
7
8
#!/bin/bash

c=0;
while [ $c -lt 3 ]
do
echo "Value c is $c"
c=`expr $c + 1`
done

until循环

1
2
3
4
5
6
c=0
until [ $c -eq 3 ]
do
echo "Value c is $c"
c=`expr $c + 1`
done

跳出循环

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
i=0
while [ $i -lt 5 ]
do
i=`expr $i + 1`

if [ $i == 3 ]
then
break
fi
echo -e $i
done

continue

与break语法一样,和c的continue功能一样

1.5 函数

函数定义

1
2
3
4
5
function function_name () {
list of commands
[ return value ]
}
# function 关键字可选
1
2
3
4
5
#!/bin/bash
hello(){
echo 'hello'
}
hello

Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:numeric argument required。

函数参数

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…这就是前面讲的特殊变量。

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

function sum(){
case $# in
0) echo "no param";;
1) echo $1;;
2) echo `expr $1 + $2`;;
3) echo `expr $1 + $2 + $3`;;
*) echo "$# params! It's too much!";;
esac
}

sum 1 3 5 6

注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

如何获取函数返回值

1
2
3
4
5
6
#!/bin/bash
function sum()
{
echo `expr 1+2+3`
}
num=$(sum)

1.6 其他

输出重定向

1
2
3
command > file  # 从头写
command >> file # 追加写
wc -l users # 计算users行数

输入重定向

1
commad < file

重定向深入讲解

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的fd =0 ,unix程序默认从stdin读数据
  • 标准输出文件(stdout):stdout的fd=1,unix程序默认向stdout输出数据
  • 标准错误文件(stderr):stderr的fd=2,unix向stderr写入错误信息

全部可用的重定向命令列表:

1
2
3
4
5
6
7
8
9
10
命令	说明
command > file #将输出重定向到 file。
command < file #将输入重定向到 file。
command >> file #将输出以追加的方式重定向到 file。
n > file #将文件描述符为 n 的文件重定向到 file。
n >> file
#将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m #将输出文件 m 和 n 合并。
n <& m #将输入文件 m 和 n 合并。
<< tag #将开始标记 tag 和结束标记 tag 之间的内容作为输入。

Here Document

1
2
3
4
5
6
7
8
9
10
command << delimiter
document
delimiter

# 例子
wc -l <<EOF
This is a simple lookup program
for good restaurants
in Cape Town
EOF

/dev/null 文件

1
command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到禁止输出的效果

Shell文件包含

1
2
. filename # 或者
source filename

获取当前正在执行脚本的绝对路径

1
basepath=$(pwd)
1
2
3
4
5
str=/www/html/php/myapp/
b=($(echo $str|sed 's#/# #g'))
b_len=`expr ${#b[*]} - 1`
app_name=${b[$b_len]}
echo $app_name