编程基础

解释器:

Linux 提供 shell 解析器 | 注:Centos 默认的解析器是 bash

1
2
3
4
5
6
7
$ cat /etc/shells
1. /bin/sh
2. /bin/bash
3. /sbin/nologin
4. /bin/dash
5. /bin/tcsh
6. /bin/csh

Shell 脚本第一行指定解释器必须写 #!/bin/bash, 如果没有会默认使用 #!/bin/sh 作为解释程序

注释:

Shell 注释,单行 #, 多行

1
2
3
4
5
<<COMMENT
comment line 1
comment line 2
comment line n
COMMENT

设置执行权限

当前用户增加执行权限 chmod u+x ./脚本名.sh
所有用户增加执行权限 chmod +x ./脚本名.sh

将脚本路径添加到环境变量中,可以实现任意位置调用,不用使用全路径

Bash 中的参数扩展

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#参数名后面还紧连着其他字符,必须使用{}
WORD=car
echo ${WORD}s
cars
#对于使用$9之后的参数也需要使用大括号
echo "Argument  1 is: $1"
echo "Argument 10 is: ${10}"
#注意:参数名是大小写敏感的
#间接参数扩展
${!PARAMETER}
#上述语句中,被引用的参数不是PARAMETER自身,而是PARAMETER的值
#如:数PARAMETER的值是 TEMP ,则${!PARAMETER}将扩展为参数TEMP的值
PARAMETER=TEMP
TEMP="It's indirect"
TEMP="It's indirect"
> It s indirect
#大小写修改(Bash 4.0的新特性):
#操作符“^”将参数值的第一个字符改为大写,操作符“,”将参数值的第一个字符改为小写。当使用双重模式(^^和,,)时,参数值的所有字符都将被转换。下面实例中,将当前目录下的所有后缀为txt的文件名转换为小写:
mv "$file" "${file,,}"
#变量名扩展:
${!PREFIX*}
${!PREFIX@}
#这种参数扩展将列出以字符串PREFIX开头的所有变量名。

#字符串移除:
${PARAMETER#PATTERN}
${PARAMETER##PATTERN}
${PARAMETER%PATTERN}
${PARAMETER%%PATTERN}
#上述的语法格式中,前两个语句用于移除从参数值的开头匹配指定模式的字符串,而后两个语句与之相反,用于从参数值的末尾匹配指定模式的字符串。操作符“#”和“%”表示将移除匹配指定模式的最短文本,而操作符“##”和“%%”表示移除匹配指定模式的最长文本。
#可能这种参数扩展最常用的用途是提取文件名的一部分

#字符串搜索与替换
${PARAMETER/PATTERN/STRING}
${PARAMETER//PATTERN/STRING}
${PARAMETER/PATTERN}
${PARAMETER//PATTERN}
#/表示只替换一个匹配的字符串
#// 表示替换所有匹配的字符串
#如果没有指定替换字符串,将被删除

#求字符串长度:
${#PARAMETER}
MYSTRING="Hello World"
echo ${#MYSTRING}
> 11
#子字符串扩展
${PARAMETER:OFFSET}
${PARAMETER:OFFSET:LENGTH}

从指定开始位置截取指定长度字符出啊,省略LENGTH将截取到末尾
MYSTRING="This is used for substring expansion."
echo ${MYSTRING:8}
>used for substring expansion.
#指定LENGTH
echo ${MYSTRING:8:10}
used for s
#使用默认值
${PARAMETER:-WORD}
${PARAMETER-WORD}
#如果参数PARAMAETER未定义或为null时,这种模式会扩展为WORD,否则时参数PARAMAETER
#如果省略“:”,只有参数时未定义时才会使用WORD
#指定默认值:
${PARAMETER:=WORD}
${PARAMETER=WORD}
#使用替代值:
${PARAMETER:+WORD}
${PARAMETER+WORD}
#如果参数PARAMETER是未定义的,或其值为空时,这种模式将不扩展任何内容。如果参数PARAMETER是定义的,且其值不为空,这种模式将扩展WORD,而不是扩展为参数PARAMETER的值。

Bash 的内部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
些常见的 Bash 内部变量

#用于引用 Bash 实例的全路径
echo $BASH
> /bin/bash
#当前用户的 home 目录
echo $BASH
> /root
#IFS 是内部字段分隔符的缩写。此变量决定当 Bash 解析字符串时将怎样识别字段,或单词分界线。变量$IFS 的默认值是空格(空格、制表符和换行),但可以被修改。请看如下实例:
set x y z        #使用 set 命令,将 x,y,z 赋予位置 参数 1,2,3
IFS=":;-"        #指定 Bash 的内部字段分隔符
echo "$*"        #扩展特殊参数*
x:y:z
#操作系统的类型
echo $OSTYPE
linux-gnu
#$SECONDS 变量 脚本已经运行的秒数
#$TMOUT 变量 $TMOUT 变量被指定了一个非零的值,此值就会被 Bash 的内部命令 read 作为默认的超时秒数
#$UID 变量 当前用户的账号标识码(ID 号)只读变量,不允许修改

Bash 中的位置参数和特殊参数

  • Bash 中的位置参数是由除 0 以外的一个或多个数字表示的参数。

多于一个数字的位置参数在扩展时必须放在大括号中。比如,位置参数 10 在扩展时使用${10}。

  • Bash 对一些参数的处理比较特殊。这些参数只能被引用,但不能修改它们的值。这些特殊参数分别是 *@#-$!0_

  • 特殊参数@,也将扩展为从 1 开始的所有位置参数。但当它的扩展发生在双引号内时,每个参数都扩展为分隔的单词。也就是说,“$@”等价于“$ 1”、“$2”…。参数@与*之间的区别将在 for 循环的调用中显现出来。

  • 特殊参数#,将扩展为位置参数的个数,用十进制表示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    set one two three # 设置位置参数
    echo $#








    3
  • 特殊参数 ?,将扩展为最近一个在前台执行的命令的退出状态。以使用它来 检查 Shell 脚本是否已成功地执行,通常退出状态 0 表示命令已经没有任何错误地结束运行。

  • 特殊参数 -,将扩展为当前的选项标志。这些选项是在调用时,或由内部命令 set 指定,或由 Shell 自身指定。

  • 特殊参数 $,将扩展为当前 Shell 的进程号。在一个子 Shell 中,它扩展为调用 Shell 的进程号,而不是子 Shell 的进程号。

  • 特殊参数 !,将扩展为最近一次执行的后台命令的进程号

  • 特殊参数 0,将扩展为 Shell 或 Shell 脚本的名称。它是在 Shell 初始化时设置。

  • 特殊参数 _,在 Shell 启动时,它被设为开始运行的 Shell 或 Shell 脚本的路径。

用 declare 指定变量的类型

  • declare 命令是 Bash 的内部命令,用于声明变量和修改变量的属性。它与 Bash 的另一个内部命令 typeset 的用法和用途完全相同。

    1
    2
    3
    4
    5
    6
    7
    #如果直接使用declare命令,不指定变量名,将显示所有变量的值
    declare
    #使用-r选项,declare命令将把指定的变量定义为只读变量,这些变量将不能再被赋予新值或被清除
    declare -r var=1
    使用-i选项,declare命令将把指定的变量定义为整数型变量,赋予整数型变量的任何类型的值都将被转换成整数
    使用-x选项,declare命令将把指定的变量通过环境输出到后续命令
    使用-p选项,declare命令将显示指定变量的属性和值

Bash 中的数组变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
接声明一个数组变量的语法如下所示:
ARRAYNAME[INDEX]=value
#INDEX 是一个正数,或是一个值为正数的算术表达式
#显式声明一个数组变量是使用 Bash 的内部命令 declare
declare -a ARRAYNAME
#数组变量还可以使用复合赋值的格式:
ARRAYNAME=(value1 value2 … valueN)
若要引用数组中某一项的内容,必须要使用花括号“{}”。如果索引编号是“@”或“*”,那么数组的所有成员都将被引用
echo ${linux[@]}
> Debian Redhat Suse Fedora
arr1=(one two three)
echo ${arr1[0]} ${arr1[1]} ${arr1[2]}
使用unset命令可以消除一个数组或数组的成员变量
unset arr1[2]
echo ${arr1[@]}

Bash 的算术运算符

  • Bash 中的算术运算符以及它们的优先级、结合性和值都与 C 语言相同。
    ![](3-Shell 编程基础/b81907185151424d331c9a8474db9e64.png)
    ![](3-Shell 编程基础/9b645f167ce2cf7afca44434d68c3af6.png)

    1
    2
    3
    4
    5
    6
    7
    8
    #求幂运算符**
    let var=5**2
    echo $var
    > 25
    #逗号运算符将两个或更多的算术运算连接在一起,所有的运算都被求值,但只有最后一个运算的值被返回。
    let var=(2+3, 10-5, 20-6)
    echo $var
    14

数字常量

  • 默认情况下,Shell 算术表达式都是使用十进制数,除非这个数字有特定的前缀或标记。以 0 开头的常量将被当作八进制数解释,而以“0x”或“0X”开头的数值将被解释为十六进制数。此外,如果数值的格式是 BASE#NUMBER,BASE 是介于 2~64 之间的十进制数,表示算术进制基数,比如,BASE 是数字 12,那么 12#NUMBER 就表示十二进制数,NUMBER 即为此进制中的数值。

    1
    2
    3
    4
    5
    let dec=20            #默认为十进制数
    let oct=020           #以0 开头的八进制数
    let hex=0x20          #以0x 开头的十六进制数
    let bin=2#111         #符号“#”之前的数字 2 表示此数值为二进制
    let base32=32#20      #三十二进制数,数值为 20

使用算术扩展和 let 进行算术运算

算术扩展中的运算数只能是整数,算术扩展不能对浮点数进行算术运算。

1
2
3
4
5
6
7
8
9
10
```
# 变量允许作为运算数
var = $(( $ var + 8))
var =$(( var + 8))
echo $var
z =$(( x%y ))          #求余运算
echo $(( 10 > 3 ))      #符号“>”为比较运算符,运算结果返回 1,0
let " i <<= 3 "       #左移 3 位并赋值
let " i = i < 6 ? i : 6 " #问号?前的表达式值为真,取 i 的值,否则取 6
```

使用 expr 命令

  • expr 命令是一个用于对表达式进行求值并输出相应结果的命令行工具。它同样也只支持整数运算数,不支持浮点运算数的运算。

  • let 命令相反,使用 expr 命令时,表达式中的运算符左右必须包含空格,如果没有空格,而是将运算符与运算数直接相连,expr 命令将不会对表达式进行求值,而直接输出算术表达式。

  • 使用 expr 命令时,对于某些运算符,还需要使用符号“\”进行转义,否则提示语法错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    expr 6 + 8
    14
    expr 6+8     #运算符左右没有包含空格
    6+8
    expr 6 * 8    #乘法符号需要使用符号“\”进行转义
    expr: syntax error
    expr 6 \* 8
    48
    expr 1 \< 2   #运算符“<”同样需要转义
    1
    expr 2 \> 5   #运算符“>”同样需要转义,还有运算符“<=”、“> =”、“|”和“&”
    a=15
    b=35
    expr $a \* $b
    525
    c='expr $a \* $b' #使用命令替换对变量进行赋值

脚本常识

退出脚本

  • 当它运行完成时,应当返回一个 退出状态,用于标识脚本是否成功运行。
  • 退出状态码,每一个命令都会返回一个退出状态。一个运行成功的命令会 返回一个 0。不成功返回一个 错误状态码

使用 exit 命令

  • exit N exit 命令语句用于从 Shell 脚本中退出并返回指定的退出状态码 N,来指示 Shell 脚本是否成功结束。
  • 如果退出状态码 N 被省略,则将把最后一条运行的命令, 的退出状态作为脚本的退出状态码

强烈建议,在你的 Shell 脚本中对调用的程序进行退出状态检查,并根据退出状态做出相应的处理,当脚本退出运行时,明确地返回一个退出状态码,这对一个完善的 Shell 脚本来说,是不可或缺的。

调试脚本

  • Shell 脚本调试的主要工作是发现引发脚本错误的原因,以及在脚本中定位发生错误的行。

  • 常用方法,使用 Bash 的 -x 选项启动一个 Shell,Shell 在执行脚本的过程中把实际执行的每一个命令行显示出来,并且在命令行的行首显示一个 “+” 号,“+” 号后面显示的是经过了参数扩展之后的命令行的内容,有助于分析实际执行的是什么命令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
     bash -x param_underscore.sh
    echo 'The $_is /bin/bash'
    The $_ is /bin/bash
    uname -a
    Linux localhost 2.6.18-238.9.1.el5PAE #1 SMP Tue 
    Apr 12 19:28:32 EDT 2011 i686 i686 i386 GNU/Linux
    echo -a
    -a
    #上面的输出结果中,前面有“+”号的行是 Shell 脚本实际执行的命令,其他行则是 Shell 脚本的打印输出信息。

这一调试功能在自 3.0 以后的 Bash 大多数现代版本中可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
```
#Shell 脚本中使用“set -x”和“set +x”命令来调试脚本中的某一段代码。
#不确定脚本 param_underscore.sh 中的“uname -a”命令将做什么操作,我们可以对脚本中的这段内容做类似如下的调整,只调试这段代码:
set -x
uname –a
set +x
#此时,脚本运行后的内容将类似如下所示:
$ ./param_underscore.sh
The $_ is ./param_underscore.sh
uname -a
Linux localhost 2.6.18238.9.1.el5PAE #1 SMP Tue Apr 12 19: 28: 32 EDT 2011 i686 i686 i386 GNU/Linux
set +x
+x
```
  • Bash 中还有一个 “-v” 选项,该选项将激活详细输出模式,在这一模式中,由 Bash 读入的脚本的每一个命令行都将在执行前被打印输出。比如使用 -v 选项运行脚本 param_ underscore.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    bash -v param_underscore.sh
    #通常,将-v 选项和-x 选项同时使用,可以得到更为详细的脚本调试信息
    #x选项虽然使用起来比较方便,但它输出的调试信息仅限于参数扩展之后的每一条实际执行的命令以及行首的一个“+”
    # -x 选项输出的信息只限于参数扩展之后的每一条实际执行的命令以及行首的一个+号,没有代码行号
    #用的 Bash 内部环境变量
    $LINENO:表示Shell脚本的当前行号。
    $FUNCNAME:它是一个包含了当前在执行调用堆栈中的所有Shell函数名称的数组变量。
    ${FUNCNAME[0]}代表当前正在执行的Shell函数的名称,${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字
    $PS4:我们在前面已经讲到,使用Bash的-x选项时,每一条实际执行的命令的行首会显示一个“+”号,而这个“+”号其实就是变量$PS4的默认值。
    #增强调试:通过重新定义变量$PS4,就可以增强-x 选项的输出信息。例如我们先在命令行提示符下执行如下语句:
    export PS4='{$LINENO:${FUNCNAME[0]}}'
    # 然后再使用 Bash 的-xv 选项来调试脚本 param_underscore.sh:
    bash -xv param_underscore.sh
    # Bash 中还有一个执行选项-n,它可用于测试 Shell 脚本中是否存在语法错误,它会读取脚本中的命令但不会执行它们。在编写完 Shell 脚本后,实际执行之前,最好首先使用-n 选项来测试脚本中是否存在语法错误,这是一个好的习惯。因为
    bash -n 脚本名.sh #








本编程风格

  • 一行不超过 80 字符
  • 保持一致的缩进深度。
  • 每个脚本文件都要有注释
  • 自定义的变量名或函数名使用小写字母,使用下划线“_”分隔单词

自定义变量

基本语法

1
2
3
4
定义变量:变量=值
撤销变量:unset 变量
声明静态变量:readonly 变量
变量名称:可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写

注:静态变量不能 unset

等号两侧不能有空格
在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算
变量的值如果有空格,需要使用双引号或单引号括起来

1
2
3
4
5
A=5 //定义
echo $A //输出变量
unset A //撤销变量
readonly B=2 //静态变量
export B //声明全局变量

特殊变量

  1. $n:n为数字,$ 0 代表该脚本名称,$1-$ 9 代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10})
  2. $#:获取所有输入参数个数,常用于循环
  3. $ * :这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体
  4. $ @:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待
  5. $?: 最后一次执行的命令的返回状态。如果这个变量的值为 0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。

运算符

基本语法

1
2
$((运算式)) $[运算式]
expr + , - , \*, /, % 加,减,乘,除,取余

| 注:expr 运算符之间要有空格

1
2
3
4
5
6
7
expr 2 + 3               //简单计算

expr `expr 2 + 3` \* 4 //复合运算

S=$[(2+3)*4] //$运算方式
echo $S

条件判断

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. [ condition ]
条件非空即为true 如 [ hello ] 返回true [] 返回false
2. 常用判断条件
1. 两个整数之间比较
= 字符串比较
-lt 小于(less than) -le 小于等于(less equal
-eq 等于(equal) -gt 大于(greater than)
-ge 大于等于(greater equal)-ne 不等于(Not equal
2. 按照文件权限进行判断
-r 有读的权限(read) -w 有写的权限(write)
-x 有执行的权限(execute)
3. 按照文件类型进行判断
-f 文件存在并且是一个常规的文件(file
-e 文件存在(existence)
-d 文件存在并是一个目录(directory)

实例

1
2
3
4
5
6
7
8
9
10
11
12
[ 23 -ge 22 ]    //条件
echo $? //判断是否执行成功 成功为0 不成功 任意非零数

[ -w helloworld.sh ] //判断是否具有写权限
echo $? //判断是否成功

[ -e /home/cls.txt ] //判断文件是否成功
echo $? //目录中的文件是否存在


[ condition ] && echo OK || echo notok //多条件判断
[ condition ] && [ ] || echo notok

流程控制

if 判断

1
2
3
4
5
6
7
8
9
10
11
if [ 条件判断式 ];then
程序
fi

//或者

if [ 条件判断式 ]
then
程序
fi

注意事项:
(1)[ 条件判断式 ],中括号和条件判断式之间必须有空格
(2)if 后要有空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//前面$表示用户权限 不是命令内容
$ touch if.sh
$ vim if.sh

//vim编写脚本
#!/bin/bash

if [ $1 -eq "1" ]
then
echo "sucess"
elif [ $1 -eq "2" ]
then
echo "fales~~"
fi

//调用shell脚本 脚本后加参数
$ bash if.sh 1
$ bash if.sh 2

case

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
case $变量名 in
"值1"
如果变量的值等于值1,则执行程序1
;;
"值2"
如果变量的值等于值2,则执行程序2
;;
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac

注意事项:

  1. case 行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。
  2. 双分号“;;”表示命令序列结束,相当于 java 中的 break。
  3. 最后的“*)”表示默认模式,相当于 java 中的 default。

for

基本语法

1
2
3
4
for (( 初始值;循环控制条件;变量变化 ))
do
程序
done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ touch for1.sh     //创建脚本
$ vim for1.sh //vim 编辑

//开始编辑
#!/bin/bash

s=0
for((i=0;i<=100;i++))
do
s=$[$s+$i]
done
echo $s

//执行脚本
$ ./for1.sh
//结果
5050

第二种 for 循环

1
2
3
4
for 变量 in 值1 值2 值3…
do
程序
done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$  touch for2.sh     //看不懂别学了
$ vim for2.sh

#!/bin/bash
#打印数字

for i in $*
do
echo "打印参数 $i "
done

//执行
$ bash for2.sh str1 str2 str3

  1. $*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以 $1 $ 2 …$n 的形式输出所有参数
  2. 当它们被双引号“”包含时,“$*”会将所有的参数作为一个整体,以“$ 1 $2 …$ n”的形式输出所有参数;“$@”会将各个参数分开,以“$ 1” “$2”…”$ n”的形式输出所有参数

while 循环

基本语法

1
2
3
4
while [ 条件判断式 ]
do
程序
done

无需实例

第一个 Shell 脚本

第一种执行方法,本质是 bash 解析器帮你执行脚本,所以脚本本身不需要执行权限。第二种执行方法,本质是脚本需要自己执行,所以需要执行权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 创建一个sh脚本文件
touch helloworld.sh
2. vi编写脚本内容
vi helloworld.sh
3. 内容
#!/bin/bash
echo "helloworld"
4. 执行 (相当于解析器调用 所以不用权限)
sh helloworld.sh
sh /home/[**自己的存放路径**]/helloworld.sh
bash helloworld.sh
bash /home/[**自己的存放路径**/helloworld.sh
5. 赋予权限再执行 (相当于自己调用)
赋予777权限 最高权限
chmod 777 helloworld.sh
**./** 直接执行脚本
./helloworld.sh
或者
/home[**自己的存放路径**]/helloworld.sh