[关闭]
@yangfch3 2017-01-14T19:01:31.000000Z 字数 5501 阅读 5288

Shell 编程 - 01

shell


学习任何一门语言的基础都是:

  1. 语法
  2. 关键字
  3. 数据类型
  4. 运算符
  5. 变量
  6. 函数
  7. 基本算法结构
  8. 支持数据结构

所以,当新接触 Shell 编程,无非就是把这些在其他语言里学到的东西迁移与本土化。


sh/bash/zsh Shell 编程的局限

所以我们平时基本只使用 sh/bash 这些 Shell 语言 脚本来做一些轻量、自动化、琐碎的工作。


前言

  1. 注意:为变量赋值时,= 前后不能空格

终端、tty、控制台、shell

FullSizeRender.jpg-1265.9kB

Shell 负责与接收我们输入的命令,翻译成内核能接受的形式,帮助我们与内核(通过)Shell 命令/工具交流。

语句与注释

  1. Shell 的语句以 ; 分隔或者换行以区分!

  2. 引号

    1. 无引号
    2. 单引号
    3. 双引号
  3. 注释以 # 为特征字符,但注释有两种用途:

    1. 放在 shell 文件头部指引解释器优先级

      1. #!/bin/bash
      2. #!/bin/sh
    2. 普通注释

      1. # Author: yangfch3
      2. # Copyright (c) http://yangfch3.com/

变量

定义变量和使用变量存在着一定的规则

定义变量

注意:变量名和等号之间不能有空格

变量命名规则:

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

使用变量

  1. $ 后接变量名
  2. ${} 包住变量名

推荐采用第二种写法,确保解析正确(帮助解释器识别变量的边界)。

  1. name="yangfch3"
  2. echo "${name}"

使用未定义的变量,则变量值为空

  1. echo "${name}"

复写变量

与定义变量的方法一致,支持变量的复写

  1. name="yangfch3"
  2. name="${name}@github"
  3. echo "${name}"

复写的变量的值就进行了更新

只读变量 - readonly

使用 readonly 关键字可以将一个变量设为只读

  1. name="yangfch3"
  2. readonly name

或者直接放到变量前

  1. readonly name="yangfch3"

如果你对只读的变量进行了复写,脚本会运行出错:

./main.sh: line 8: var1: readonly variable

删除变量 - unset

使用 unset 可以删除变量

  1. unset variable_name

变量类型

1. 局部变量
在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

2. 环境变量
所有的程序,包括 shell 启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候 shell 脚本也可以定义环境变量。

3. shell 变量
shell 变量是由 shell 程序设置的特殊变量(见特殊变量一节)。shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 shell 的正常运行

特殊变量

特殊变量列表
image_1b5hs2epr108lmc01643sn9g0j9.png-92.2kB

命令行参数

命令行参数用 $n 表示,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。

  1. echo "$1$2"
$ ./main.sh var1
var1

$n 中 n 的值小于参数个数时,$n 为空 ""

$* 和 $@ 的区别

1. 不被双引号包含时
$*$@ 都表示 传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。

2. 被双引号包含时
当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2$n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。

  1. echo "print each param from \$*"
  2. for var in $*
  3. do
  4. echo "$var"
  5. done
  6. echo "print each param from \$@"
  7. for var in $@
  8. do
  9. echo "$var"
  10. done
  11. echo "print each param from \"\$*\""
  12. for var in "$*"
  13. do
  14. echo "$var"
  15. done
  16. echo "print each param from \"\$@\""
  17. for var in "$@"
  18. do
  19. echo "$var"
  20. done

输出:

  1. print each param from $*
  2. a
  3. b
  4. c
  5. print each param from $@
  6. a
  7. b
  8. c
  9. print each param from "$*"
  10. a b c
  11. print each param from "$@"
  12. a
  13. b
  14. c

退出状态变量

$? 可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。
退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1
不过,也有一些命令返回其他值,表示不同类型的错误。

$ echo $?
0

$? 也可以表示函数的返回值(同脚本执行完毕相似)。

其他特殊变量

$0:当前脚本的文件名
$$:当前 shell 进程 ID
$#:传入的参数的个数

$ ./main.sh a b c
$0= ./main.sh
$#= 3
$$= 3333

shell 替换

转义字符

类似于 ES6 里的模板字符串,shell 里的字符串内可以包含变量、转义字符……

  1. echo -e "a\nb\nc"

注意:转义字符要想转换成功,需要为 echo 添加 -e 参数

image_1b5i6pt5mijh13tc1h72as8dm.png-34.6kB

命令替换(运行赋值)

命令替换是指 Shell 可以先执行命令,将输出结果暂时保存在变量中,在适当的地方输出或使用。

作用类似 ES6 里的模板字符串。需要将命令执行标准输出作为变量值得就是用 ``。

命令替换有两种模式:

  1. $(put your command)推荐
  2. ``
  1. echo "There are $(ls | wc -l) items here."
  2. UP=`date ; uptime`
  3. echo "Uptime is $UP"

变量替换

变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值
image_1b5q4c5fb1jrc15v1gsh1bsrkmk9.png-99.5kB

  1. echo ${var:-"Variable is not set"}
  2. echo "1 - Value of var is ${var}"
  3. echo ${var:="Variable is not set"}
  4. echo "2 - Value of var is ${var}"
  5. unset var # 删除变量 var
  6. echo ${var:+"This is default value"}
  7. echo "3 - Value of var is $var"
  8. var="Prefix"
  9. echo ${var:+"This is default value"}
  10. echo "4 - Value of var is $var"
  11. echo ${var:?"Print this message"}
  12. echo "5 - Value of var is ${var}"

输出:

Variable is not set
1 - Value of var is
Variable is not set
2 - Value of var is Variable is not set

3 - Value of var is
This is default value
4 - Value of var is Prefix
Prefix
5 - Value of var is Prefix

变量操作

我们可以在读取变量时对变量内部进行字符串代换:

  1. echo ${Variable/Some/A}
  2. # 会把 Variable 中首次出现的 "some" 替换成 “A”

我们也可以在读取变量时对变量字符串进行截取

  1. # 变量的截取
  2. Length=7
  3. echo ${Variable:0:Length}
  4. # 这样会仅返回变量值的前7个字符

数据类型

字符串

字符串的形式有如下几种:

  1. 单引号
  2. 双引号
  3. 无引号

区别其实比较容易区分,根据形式的特点决定使用何种方式。

无引号

无引号

单引号

单引号字符串的特点:

  1. 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,也没法在单引号内进行表达式、命令执行等操作
  2. 单引号字串中不能出现单引号(对单引号使用转义符后也不行)。
  1. name='yangfch3'
  2. echo '\n${name}';

输出

  1. \n${name}

双引号

双引号的特点:

  1. 双引号里内可以有 变量

    双引号内变量可以再用双引号包住,也可以不包。

  2. 双引号里可以出现 转义字符

  3. 双引号内可以方便地进行 命令替换(见命令替换一节)

字符串拼接

注意:shell 编程里没有所谓的字符串 + 的概念,直接在双引号内进行字符串拼接。

  1. name="yangfch3"
  2. # 双引号递归匹配,内部双引号不会输出
  3. greeting="hello, "${name}" !"
  4. greeting_1="hello, ${name} !"
  5. echo $greeting $greeting_1

输出

  1. hello, yangfch3 ! hello, yangfch3 !

获取字符串长度

  1. string="abcd"
  2. echo ${#string} # 输出 4

提取子字符串

  1. string="xxx is a yyy"
  2. echo ${string:1:4} # 输出 xx i

查找字符串

  1. string="xxx is a yyy"
  2. echo `expr index "$string" is` # 输出 4

数组

bash 支持一维数组(不支持多维数组),并且没有限定数组的大小。

  1. # 形式1
  2. arr1=("0" "1" "``expr 2 + 2")
  3. # 形式2
  4. array_name=(
  5. 0
  6. 1
  7. `expr 2 + 2`
  8. )
  9. # 形式3:单独定义数组分量
  10. array_name[0]=0
  11. array_name[1]=1
  12. array_name[2]=`expr 2 + 2`

数组的读取

读取数组单项:${array_name[index]}

读取数组所有项:${array_name[*]} ${array_name[@]}

读取数组的长度:${#array_name[@]} length=${#array_name[*]}

读取数组单个元素的长度:lengthn=${#array_name[n]}

shell 运算符

原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如 awkexprexpr 最常用。

  1. 使用 expr 进行运算时需要使用上面提到的命令替换

    1. `expr 2 + 2`
  2. [] 在变量声明与分支判断时,内部也可以使用运算符:

    1. num1=$[2*3] # 变量声明 [] 内部使用算术运算符
    2. if [num1 -eq 6] # 分支判断 [] 内使用运算符
    3. then
    4. echo num1
    5. fi
  3. () 内部也可以包裹运算 

算术运算符

两点注意:

  1. 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
  2. 需要使用命令替换的特征字符 `` 来包住运算语句
  3. 除了 `,我们还可以使用()来包裹运算,如:(2 + 2)`
  1. a=10
  2. b=20
  3. val=`expr $a + $b`
  4. echo "a + b : $val"
  5. val=`expr $a \* $b`
  6. echo "a * b : $val"
  7. if [ $a == $b ]
  8. then
  9. echo "a is equal to b"
  10. fi

image_1b5secelk10mdsgto701vt44cc9.png-51kB

  1. 乘号 * 前边必须加反斜杠 \ 才能实现乘法运算;
  2. if...then...fi 是条件语句
  3. 注意:expr 一般是不支持小数运算的,结果为小数的话会被变为 0

关系运算符

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

Shell 里对数值和字符串不做区分,或者说数值一般情况下视为字符串,再运算时才被当做数值。

image_1b5sejfa2142g9e19l21rvu1p1hm.png-57.4kB

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

布尔运算符

image_1b5semc861jta1vs7dch1s4q1kql13.png-32.5kB

  1. a=10
  2. b=20
  3. if [ $a -lt 100 -a $b -gt 15 ]
  4. then
  5. echo "$a -lt 100 -a $b -gt 15 : returns true"
  6. else
  7. echo "$a -lt 100 -a $b -gt 15 : returns false"
  8. fi

逻辑运算符

在分支条件那里,我们可以使用 &&|| 来进行 短路的逻辑运算

  1. # 在 if 语句中使用 && 和 || 需要多对方括号
  2. if [ $Name == "Steve" ] && [ $Age -eq 15 ]
  3. then
  4. echo "This will run if $Name is Steve AND $Age is 15."
  5. fi
  6. if [ $Name == "Daniya" ] || [ $Name == "Zach" ]
  7. then
  8. echo "This will run if $Name is Daniya OR Zach."
  9. fi

字符串运算符

image_1b5seoigf14bk18j919p1iaa1pu61g.png-43.1kB

  1. a="abc"
  2. b="efg"
  3. if [ -z $a ]
  4. then
  5. ...
  6. fi

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。依赖文件 path 字符串。

  1. file="./test.sh"
  2. if [ -r $file ]
  3. then
  4. echo "File has read access"
  5. else
  6. echo "File does not have read access"
  7. fi

image_1b5sett85c501raem731iq0d321t.png-124.3kB


附:参考资料

xx 分钟系列:

  1. http://c.biancheng.net/cpp/shell/
  2. http://www.runoob.com/w3cnote/shell-quick-start.html
  3. https://github.com/qinjx/30min_guides/blob/master/shell.md

手册系列:

  1. http://manual.51yip.com/shell/
  2. https://ghui.me/post/2016/06/shell-handbook/
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注