1. 简介

​ 深入了解一些代码之后,发现bash老是阻挡自己的脚步,尤其是一些项目跑在Linux环境下,Coders便会选择写一个shell脚本,想自己改又不是很懂,还有像是Github的action脚本也会用到这些。Shell即贝壳的意思,它就像套在Linux内核的一种抽象接口,对内连接内核,对外为用户提供命令接口,bash便是其中一种 ^1

Shell

Shell 接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell 会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。这种特殊字符的扩展,称为模式扩展(globbing)。

2. 内容

2.1 Shebang 行

​ 脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。#!后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh/bin/bash

1
#!/bin/bash(sh)

2.2 脚本参数

​ 调用脚本的时候,脚本文件名后面可以带有参数:

1
./script.sh word1 word2 word3

script.sh是一个脚本文件,word1word2word3是三个参数。而脚本文件内部,可以使用特殊变量,引用这些参数:

参数 意义
$0 脚本文件名,即script.sh
$1~$9 对应脚本的第一个参数到第九个参数。
$# 参数的总数。
$@ 全部的参数,参数之间使用空格分隔。
$* 全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。

如果脚本的参数多于9个,那么第10个参数可以用${10}的形式引用,以此类推。注意,如果命令是command -o foo bar,那么-o$1foo$2bar$3

2.3 条件判断

if是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下。

1
2
3
4
5
6
7
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi

fi结尾,if结构的判断条件,一般使用test命令:

1
2
3
4
5
# 写法一
test expression

# 写法二
[ expression ]

写法1太繁琐,方便起见使用第二种形式:

命令 解释
[ -a file ] 如果 file 存在,则为true
[ -b file ] 如果 file 存在并且是一个块(设备)文件,则为true
[ -c file ] 如果 file 存在并且是一个字符(设备)文件,则为true
[ -d file ] 如果 file 存在并且是一个目录,则为true
[ -e file ] 如果 file 存在,则为true
[ -f file ] 如果 file 存在并且是一个普通文件,则为true
[ -g file ] 如果 file 存在并且设置了组 ID,则为true
[ -s file ] 如果 file 存在且其长度大于零,则为true
命令 解释
[ -n string ] or [ string ] 如果string不为空(长度大于0),则判断为真
[ -z string ] 如果字符串string的长度为零,则判断为真
[ string1 == string2 ] 如果string1string2相同,则判断为真
[ string1 != string2 ] 如果string1string2不相同,则判断为真
[ string1 '>' string2 ] [^2] 如果按照字典顺序string1排列在string2之后,则判断为真。
[ string1 '<' string2 ] 如果按照字典顺序string1排列在string2之前,则判断
命令 解释
[ integer1 -eq integer2 ] 如果integer1等于integer2,则为true
[ integer1 -ne integer2 ] 如果integer1不等于integer2,则为true
[ integer1 -le integer2 ] 如果integer1小于或等于integer2,则为true
[ integer1 -lt integer2 ] 如果integer1小于integer2,则为true
[ integer1 -ge integer2 ] 如果integer1大于或等于integer2,则为true
[ integer1 -gt integer2 ] 如果integer1大于integer2,则为true

2.4 数组

​ bash数组的定义:

1
2
# 变量定义的`=`前后不要有空格,否则会被解释为命令,而数组元素定义用空格分割
array=(elem1 elem2 ... elemn)

数组元素选取:

  • ${array[i]}:取第i个元素
  • ${array[@]}:读取所有元素
  • array+=(d e f):追加元素d、e、f

还有一种数组在bash中叫关联数组,其实就是(key,value)键值对集合即字典:

1
2
3
4
declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"
  • ${colors["blue"]}:字典读取

2.5 循环

​ 这里只介绍一种格式,其余都是类似的:

1
2
3
4
5
number=0
while [ "$number" -lt 10 ]; do
echo "Number = $number"
number=$((number + 1))
done

和其他语言一样,亦可以使用continuebreak,另外还有一种特别的循环类型:

1
2
3
4
select name in list
do
commands
done

这种方式是bash中特有的,运行后会出现一个单选框,只能选择其中一个:

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

select brand in Samsung Sony iphone symphony Walton
do
echo "You have chosen $brand"
done

# --> outpuut
# $ ./select.sh
# 1) Samsung
# 2) Sony
# 3) iphone
# 4) symphony
# 5) Walton

2.6 函数

​ 函数的定义和其他语言一样:

1
2
3
4
5
6
7
8
function fn() {
# codes
local var
return
}

# call fn
fn

函数定义的定义也与其他语言一样,function关键字可写可不写,在函数体内定义局部变量则用local关键字,如果不使用local直接定义,则为全局变量(尽管在函数体内)。

3. Reference

[^2]: test命令内部的><,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符