一、  Shell

1.  Shell 简介

1. Shell 是一个用 C 语言编写的程序,它既是一种命令语言,又是一种程序设计语言,它是用户使用 Linux 的桥梁
2. Shell 脚本(Shell Script),是一种为 Shell 编写的脚本程序
3. 用户开发的 Shell 脚本可以驻留在命令搜索路径的目录之下(通常是 /bin、/usr/bin 等),像普通命令一样使用。如果打算反复使用编好的 Shell 脚本,可以开发出自己的新命令

4. Linux 的 Shell 种类众多,常见的有:
   1)Bourne Shell(/usr/bin/sh 或 /bin/sh)
   2)Bourne Again Shell(/bin/bash
   3)Shell for Root(/sbin/sh)
....

5. 本文关注的是 Bash,也就是 Bourne Again Shell
1)由于易用和免费,Bash 在日常工作中被广泛使用
2)Bash 也是大多数 Linux 系统默认的 Shell
3)一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以 #!/bin/sh 也可以写为 #!/bin/bash#! 是一个约定的标记,其路径告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell

6. Shell 脚本的编写
1)使用 vi/vim 命令来创建文件,新建一个文件 test.sh,扩展名为 sh( sh 代表 Shell,扩展名并不影响脚本执行,见名知意就好 )
2)第一行一般为:#!/bin/bash
3)如:
#!/bin/bash
echo "Hello World !"

7. Shell 脚本有两种执行方法:
1)作为可执行程序,如:
# chmod a+x ./test.sh
# ./test.sh
注意:这个要求在 Shell 脚本的开头必须指明执行该脚本的具体 Shell,即 #!/bin/bash

2)作为解释器参数,这种运行方式是直接运行解释器,其参数就是 Shell 脚本的文件名,如:
# /bin/bash test.sh
# /bin/php test.php
注意:这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没意义

8. Shell 注释,在脚本文件中以 "#" 开头的行就是注释,会被解释器忽略。sh 里没有多行注释,只能每一行开头加一个 # 号
1)每一行开头加个 # 符号太费力了,可以把这一段要注释的代码,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果

2.  echo、read、printf 命令

1. echo 命令,自带换行功能
1)显示普通字符串,如:
echo "It is a test" # 双引号可以省略
2)显示转义字符,如:
echo "\"It is a test\"" # 结果:"It is a test"
3)显示不换行,如:
  echo -e "OK!\c" # -e 开启转义,\c 不换行
  echo "It it a test" # 结果:OK! It is a test
4)显示命令执行结果,如:
echo `date` # 结果将显示当前日期
5)显示变量值,如:
echo ${your_name}
6)显示结果重定向至文件,如:
echo "It is a test" > myfile # > 表示覆盖重定向, >> 表示追加重定向

2. read 命令
1)read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 Shell 变量,如:
#!/bin/bash
read -p "请输入:" str
echo "${str} It is a test"
# 结果
请输入:hello
hello It is a test

3. printf 命令
1)默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n
2)printf 命令模仿 C 程序库(library)里的 printf() 程序,因此使用 printf 的脚本比使用 echo 移植性好
3)printf 命令的语法:printf 格式控制 参数列表 ,如:
printf "%-6s %-6s %-4s\n" 姓名 性别 体重kg
printf "%-6s %-7s %-4.2f\n" 小明 男 66.1234
# 结果
姓名 性别 体重kg
小明 男 66.12

a. %s %c %d %f 都是格式替代符,格式只指定了一个参数,如果没有 arguments,那么 %s 用 NULL 代替,%d 用 0 代替
b. %-6s 指一个宽度为 6 个字符( - 表示左对齐,没有则表示右对齐),任何字符都会被显示在6个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来
c. %-4.2f 指格式化为小数,其中 .2 指保留 2 位小数

3.  Shell 变量

1. 定义变量,变量名前面不加美元符号,如:
your_name="tom"
your_company="alibaba"
注意:变量名和等号之间不能有空格

2. 使用一个定义过的变量,只要在变量名前面加美元符号即可,如:
echo $your_name
echo ${your_company}hello
注意:花括号是可选的,为了帮助解释器识别变量的边界。如果不给 your_company 变量加花括号解释器就会把 your_companyhello 当成一个变量。推荐给所有变量加上花括号,这是个好的编程习惯

3. 可以将一个命令的执行结果赋值给变量。有两种形式:
1)`commands` ,"`" 是反单引号,如:
dirPath=`pwd`
2)$(commands),如:
dirPath=$(pwd)

4. 设置一个变量为只读属性
1)有些重要的 Shell 变量,赋值后不应该修改,那么可设置它为 readonly ,如:
oracle_home=/usr/local/oracle/bin
readonly oracle_home

5. Shell 字符串
1)字符串是 Shell 编程中最常用最有用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号,如:
str='this is a string'
str="this is a string"
str=this is a string
2)单引号字符串的限制
a. 单引号里的任何字符都会原样输出,即单引号字符串中的变量是无效的
b. 单引号字串中不能出现单引号,对单引号使用转义符后也不行
3)双引号的优点
a. 双引号里可以有变量
b. 双引号里可以出现转义字符
4)获取字符串长度:${#str},如:
string="abcd"
echo ${#string} #输出 4
5)提取子串,如:
string="alibaba is a great company"
echo ${string:1:4} #输出 liba
6)查询子串位置,以下脚本中 "`" 是反单引号,而不是单引号 "'"
string="alibaba is a great company"
echo `expr index "$string" is`

6. Shell 数组
1)bash 支持一维数组(不支持多维数组),并且没有限定数组的大小,数组元素的下标从 0 开始编号
2)定义数组,用括号来表示数组,数组元素用 "空格" 符号分割,如:
array_name=(value0 value1 value2 value3)
# 还可以单独定义数组的各个分量:
array_name[0]=value0 array_name[i]=valuei
# 也可以用 declare 命令显式声明一个数组,一般形式是:declare -a 数组名
3)读取数组元素值,如:
valuen=${array_name[n]}
4)使用 * @ 符号可以获取数组中的所有元素,如:
echo ${array_name[*]}
echo ${array_name[@]}
5)获取数组长度的方法与获取字符串长度的方法相同,如:
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthi=${#array_name[i]}

4.  Shell 传递参数

1. 在执行 Shell 脚本时,向脚本传递参数,多个参数用空格分割,脚本内获取参数的格式为:$n,n 是一个数字。其中 $0 为执行的文件名,$1 为执行脚本的第一个参数,$2 为执行脚本的第二个参数,.... 如:test.sh
#!/bin/bash
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
# 测试结果
# chmod a+x ./test.sh
# ./test.sh hello world
执行的文件名:test.sh
第一个参数为:hello
第二个参数为:world

2. 另外,还有几个特殊字符用来处理参数
1)$# 传递到脚本的参数个数
2)$* 以一个字符串形式显示所有向脚本传递的参数。一般在使用时加引号,即 "$*" ,表示以 "$1 $2 ... $n" 的形式输出所有参数,输出结果是一个字符串
3)$@ 以多个字符串形式显示所有向脚本传递的参数。一般在使用时加引号,即 "$@" ,表示以 "$1" "$2" ... "$n" 的形式输出所有参数,输出结果是多个字符串
4)$$ 脚本运行的当前进程 ID 号
5)$! 后台运行的最后一个进程的 ID 号
6)$? 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误

5.  Shell 函数定义

1. Linux Shell 允许用户定义函数,然后可以在 Shell 脚本中随便调用
2. Shell 中函数的定义格式如下:
[ function ] funname [()]{
    action;
    [return int;]
}
1)可以带 function fun() 定义,也可以直接 fun() 定义,不带任何参数
2)return 函数返回值,如果不加 return,将以最后一条命令的运行结果作为返回值。return 后跟数值
3)函数返回值在调用该函数后通过 $? 来获得

3. 在 Shell 中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,同 Shell 传递参数,如:
#!/bin/bash
funWithParam(){
    echo "第一个参数为 ${1}"
    echo "第十个参数为 ${10}"
    echo "参数总数有 $# 个"
    echo "作为一个字符串输出所有参数 $*"
}
funWithParam 1 2 3 4 5 6 7 8 9 10 11
# 结果
第一个参数为 1
第十个参数为 10
参数总数有 11 个
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 10 11

6.  Shell 运算符

1. 注意
1)表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2
2)完整的运算表达式要被反单引号 ` ` 包含,如:`expr $a + $b`
3)条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]
4)条件测试命令 test ,语法:test 表达式 ,效果等同于方括号 []。如:test $a == $b 等价于 [ $a == $b ]

2. 算术运算符
假定变量 a 为 10,变量 b 为 20
1)+ 加法,如:`expr $a + $b`
2)- 减法,如:`expr $a - $b`
3)* 乘法,如:`expr $a \* $b`,在 MAC 中 Shell 的 expr 语法是:$((表达式)),此处表达式中的 "*" 不需要转义符号 "\"
4)/ 除法,如:`expr $b / $a`
5)% 取余,如:`expr $b % $a`
6)= 赋值,如:a=$b
7)== 相等,如:[ $a == $b ] 返回 false
8)!= 不相等,如:[ $a != $b ] 返回 true

3. 关系运算符,关系运算符只支持数字,不支持字符串,除非字符串的值是数字
假定变量 a 为 10,变量 b 为 20
1)-eq 相等,如:[ $a -eq $b ] 返回 false
2)-ne  不相等,如:[ $a -ne $b ] 返回 true
3)-gt  大于,如:[ $a -gt $b ] 返回 false
4)-lt  小于,如:[ $a -lt $b ] 返回 true
5)-ge  大于等于,如:[ $a -ge $b ] 返回 false
6)-le  小于等于,如:[ $a -le $b ] 返回 true

4. 布尔运算符
假定变量 a 为 10,变量 b 为 20
1)-a 与运算,如:[ $a -lt 20 -a $b -gt 100 ] 返回 false
2)-o 或运算,如:[ $a -lt 20 -o $b -gt 100 ] 返回 true
3) ! 非运算,如:[ ! false ] 返回 true

5. 逻辑运算符
假定变量 a 为 10,变量 b 为 20
1)&&  逻辑的 AND ,如:[[ $a -lt 100 && $b -gt 100 ]] 返回 false
2)||  逻辑的 OR ,如:[[ $a -lt 100 || $b -gt 100 ]] 返回 true

6. 字符串运算符
假定变量 a 为 "abc",变量 b 为 "efg"
1) =  相等,如:[ $a = $b ] 返回 false
2)!= 不相等,如:[ $a != $b ] 返回 true
3)-z  检测字符串长度是否为 0,如:[ -z $a ] 返回 false
4)-n  检测字符串长度是否不为 0,如:[ -n $a ] 返回 true

7. 文件测试运算符用于检测 Linux 文件的各种属性
假定 file="/usr/local/temp/test.sh",它是大小为 100 字节,具有 rwx 权限的文件
1)-d 检测文件是否是目录,如:[ -d $file ] 返回 false
2)-f 检测文件是否是普通文件,如:[ -f $file ] 返回 true
3)-p 检测文件是否是有名管道,如:[ -p $file ] 返回 false
4)-r 检测文件是否可读,如:[ -r $file ] 返回 true
5)-w 检测文件是否可写,如:[ -w $file ] 返回 true
6)-x 检测文件是否可执行,如:[ -x $file ] 返回 true
7)-s 检测文件大小是否大于 0,如:[ -s $file ] 返回 true
8)-e 检测文件或目录是否存在,如:[ -e $file ] 返回 true

7.  Shell 流程控制

1. if 语法格式:
   if condition1
   then
      command1
   elif condition2
   then
      command2
   else
      commandN
   fi

2. for 语法格式:
   for var in items
   do
     commands
   done

3. while 语法格式:
   while condition
   do
     commands
   done

4. until 语法格式:
   until condition
   do
     commands
   done

5. 无限循环语法格式:
   1)while :
      do
        command
      done
   2)while true
      do
        command
      done
   3)for (( ; ; ))

5. case 语法格式:每个 case 分支用右圆括号,用两个分号表示 break
   #!/bin/bash
   while :
   do
       echo -n "输入 1 到 5 之间的数字:"
       read num
       case $num in
           1|2|3|4|5) echo "你输入的数字为 $num!"
           ;;
           *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
              break
           ;;
       esac
   done

6. continue 命令与 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环

8.  Shell 输入/输出重定向

1. 一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
   1)标准输入文件(stdin ):文件描述符为 0,Unix 程序默认从 stdin 读取数据
   2)标准输出文件(stdout):文件描述符为 1,Unix 程序默认向 stdout 输出数据
   3)标准错误文件(stderr):文件描述符为 2,Unix 程序会向 stderr 流中写入错误信息
   默认情况下,command > file 将 stdout 重定向到 file;command < file 将 stdin 重定向到 file;如果希望 stderr 重定向到 file,可以这样写:command 2 > file

2. 重定向命令
1)将输出以覆盖的方式重定向到 file
command > file

2)将输出以追加的方式重定向到 file
command >> file

3)将输入重定向到 file
command < file
 
4)将文件描述符为 n 的文件重定向到 file
n > file

5)将文件描述符为 n 的文件以追加的方式重定向到 file
n >> file

6)将输出文件 m 和 n 合并
n >& m

7)将输入文件 m 和 n 合并
n <& m

8)将开始标记 tag 和结束标记 tag 之间的内容(document) 作为输入传递给 command
command << tag
document
tag    
注意:结尾的 tag 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进

9)command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。即执行 command,从文件 file1 读取内容,然后将输出写入到 file2 中
command < file1 > file2

10)将 stdout 和 stderr 合并后重定向到 file
command > file 2>&1

11)/dev/null 文件是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果
a. 如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null :
command > /dev/null
b. 如果希望屏蔽 stdout 和 stderr :
command > /dev/null 2>&1

9.  Shell 文件包含

1. Shell 文件包含的两种语法格式:
   1). filename  注意点号(.)和文件名中间有一空格
   2)source filename

2. 实例
1)创建两个 Shell 脚本文件
定义 test1.sh 如下:
#!/bin/bash
url="http://www.baidu.com"

定义 test2.sh 如下:
#!/bin/bash
. ./test1.sh # 使用 . 号来引用 test1.sh 文件或者使用 source ./test1.sh 包含文件代码
echo "百度地址:$url"

2)测试结果
# chmod +x test2.sh
# ./ test2.sh
百度网址:http://www.baidu.com

3)注意:被包含的文件 test1.sh 不需要可执行权限

10.  参考资料

01-01 19:24