3 shell语法

3.1 概论

终端可以看做逐条执行的shell脚本,Linux默认使用bash,脚本文件第一行必须为

1
#! /bin/bash

可通过两种方式执行shell脚本

  • 解释器执行:bash xxx.sh
  • 作为可执行文件执行
    • 添加执行权限chmod +x xxx.sh
    • 执行./xxx.sh

3.2 注释

单行注释:类似python,用#注释

多行注释:

1
2
3
:<<EOF
...
EOF

其中EOF可替换成任意字符串,例如abc

3.3 变量

定义变量

定义不需要$,而且=两边不能有空格。字符串可用单引号、双引号或不用引号描述,例如下边三种变量声明都是正确的。

1
2
3
name1='abc'
name2="abc"
name3=abc

引用变量

需要用$,可用{}限定变量名边界,实现字符串与变量混合表示

1
echo ${name1}defg  # 输出abcdefg

只读变量

声明前加上readonlydeclare -r

1
2
readonly name1
declare -r name2

删除变量

unset删除,此时变量为空串

1
2
unset name
echo name # 输出空串和一个换行符

变量类型

根据使用范围,可分为全局变量和局部变量。

全局变量又称环境变量,其它子进程(其它bash终端)可访问,可用以下两种方式声明:

1
2
export name
declare -x name

局部变量又称自定义变量,只有当前进程能访问,可通过declare +x把全局变量转成局部变量

1
declare +x name

单引号字符串与双引号字符串的区别

单引号的内容原样输出,不支持变量引用嵌套$name;而双引号的内容中可以嵌套变量引用,不加引号与双引号效果一致。

1
2
3
name=abc
echo '$name defg' # 输出$name defg
echo "$name defg" # 输出abc defg

获取字符串的长度

1
2
name="abc"
echo ${#name} # 输出3

提取子串F’x

第1个数为起始地址(从0开始),第2个数为长度

1
2
name="abcdefg"
echo ${name:0:5} # 输出abcde

3.4 默认变量

$0表示文件路径,例如./xxx.sh$1$2等依次表示脚本的第1个参数、第2个参数。

参数 说明
$# 参数个数
$* 等价于"$1 $2 $3 ..."
$@ 等价于"$1" "$2" "$3" ..."
$$ 当前进程ID
$? 上一条命令的退出状态,例如0表示正常退出,其它为异常退出
$(command) 返回command的标准输出stdout,可嵌套
`command` 返回command的标准输出stdout,不可嵌套

3.4 数组

只支持一维数组,但数组元素类型可以不同,而且不需要指名数组大小,下标从0开始。数组用小括号()表示,元素用空格隔开

1
2
array=(1 abc 'defg')  # 整个定义
array[3] = "hijk" # 逐个定义

$读取数组元素,可用@*表示整个数组

1
2
3
echo $(array[0])  # 输出索引为0的元素
echo $(array[@]) # 输出所有元素
echo $(array[*]) # 输出所有元素

读取数组长度,有如下两种方式,返回的都是数组实际长度

1
2
echo $(#array[@])
echo $(#array[*])

3.5 expr命令

可用于表达式求值,格式为echo 表达式。表达式用空格隔开每一项,个别符号需要用\转义,例如乘号*。如果表达式包含空格以及其它特殊字符,需要用引号括起。

表达式同时在stdoutexit code输出结果。如果表达式是逻辑表达式,且为真时,则stdout返回1exit code返回0。如果表达式是非逻辑表达式,则一律返回0

3.5.1 字符串表达式

可用length strstr的长度

可用index string charsetcharset中任意单个字符首次在string中出现的位置,下标从1开始

可用substr string position lengthstringposition起,长度为length的子串

1
2
3
4
str="HelloWorld"
echo `expr length "$str"`
echo `expr index "$str" abc`
echo `expr substr "$str" 2 3`

3.5.2 整数表达式

+-\*/取余%括号\( \),注意乘号和括号需要用反斜杠\转义。

1
echo `expr \( $a + $b \) \* $c`  # 输出(a+b)*c

3.5.3 逻辑表达式

|:如果左边参数非空且非0,则返回左边参数;否则如果右边参数非空且非0,返回右边参数;都不满足返回0。

&:如果左边参数和右边参数非空且非0,则返回左边参数;否则返回0。

|&都具有短路特性,如果计算左边参数足以确定返回值,则不会计算右边参数。

<<=>>===!=都与python一致,除了=也表示相等关系,即与==同义

3.6 read命令

read类似cin,后边接变量名,例如read name

read有两个参数-p,接提示信息;-t,接超时时间,单位为秒,超过时间则忽略该命令

1
read -p "What's your name?" -t 30 name

3.7 echo命令

echo主要用于输出字符串,字符串可不加引号表示,也可加单引号表示原生字符串,或加双引号实现嵌套变量的字符串

双引号需要用\转义,单引号不需要

参数-e可开启转义,从而能在字符串使用转义字符\n

1
echo -e "Hi\n"  # 额外多输出一个换行符

若想取消echo默认输出的换行符,可用\c

1
echo -e "Hi\c"  

重定向

1
echo "Hello world" > output.txt

显示执行结果

1
echo `ls`

3.8 printf命令

类似C++printf,其格式为

1
2
printf format-string [arguments...]
printf "%10d.\n" 100

3.9 test命令与判断符号[]

3.9.1 test用法

test命令常用于判断文件类型,以及比较变量,它的返回结果是exit code,需要用$?读取。输入man test可查看test的用法。

exit code中,0表示真,其它为假。

逻辑运算符&&||都具有短路原则,只是这是真的逻辑表达式,返回逻辑值10,而不像&|只执行,不返回exit code

1
2
test 2 -lt 3
echo $? # 逻辑表达式为真,因此exit code为0

3.9.2 文件类型判断

常用参数有

  • -e:文件是否存在
  • -f:是否为文件
  • -d:是否为目录

为了提高代码可读性,常常这样编写代码判断文件是否存在:

1
test -e filename && echo "Exist" || echo "Not exist"

3.9.3 文件权限判断

常用参数有

  • -r:文件是否可读
  • -w:文件是否可写
  • -x:文件是否可执行
  • -s:文件是否为空文件

3.9.4 整数间比较

类似Latex,test命令的比较符号有-eq-ne-gt-lt-ge-le

3.9.5 字符串比较

常用参数有

  • -z:字符串是否为空,如果为空返回True
  • -n:字符串是否非空,如果非空返回True
  • ==:判断字符串是否相同
  • !=:判断字符串是否不同

3.9.6 多重条件判定

常用参数有:与-a、或-o、非!

1
test ! -x file  # 文件不可执行时返回True

3.9.7 判断符号[]

[]用法几乎与test一样,可看做简化版的test,常用于if语句中。注意中括号前后的空格,变量和常量尽量用双引号括起。

1
2
3
4
test 2 -lt 3
[ 2 -lt 3 ] # 与上一条语句等价

[ "$name" == "abc" ] # 用双引号括起

3.10 判断语句

3.10.1 if语句

bash中的if语句类似C++中的if语句块,其语法格式如下

1
2
3
4
5
6
7
8
9
if condition
then
statement
elif condition
then
statement
else
statement
fi

其中condition可用expr逻辑表达式或test表达式,例如[ "$a" -eq 1 ]表示变量a是否等于1,满足则执行then后边的语句。

3.10.1 case语句

bash中的case语句类似C++中switch语句块,其语法格式如下

1
2
3
4
5
6
7
8
9
10
11
case $Name in
value1)
statement
;;
value2)
statement
;;
*)
statement
;;
esac

例子

1
2
3
4
5
6
7
8
9
10
11
case $a in
1)
echo "a = 1"
;;
2)
echo "a = 2"
;;
*)
echo "a = other"
;;
esac

其中;;类似break

3.11 循环语句

3.11.1 for语句

(1)for...in

遍历指定值

1
2
3
4
for var in val1 val2 val3
do
statement
done

遍历数组

1
2
3
4
for var in `ls`
do
statement
done

lsstdout作为数组进行遍历

可用$(seq 1 10){1..10}表示数组1 2 ... 10,也可用{a..z}表示数组a b ... z

(2)for((expression; condition; expression))

类似C++中的for语句块,只是需要用双括号括起,此时允许括号内的=号两侧用空格隔开(通常不允许)

例如:

1
2
3
4
for ((i = 1; i <= 10; i++))
do
echo $i
done

3.11.2 while语句

bash中的whileC++类似,语法结构如下

1
2
3
4
while condition
do
statement
done

例子:

1
2
3
4
while read name
do
echo $name
done

若要终止读取,可按ctrl+d,或直接结束程序ctrl+c

3.11.3 until语句

until语句与while相反,当condition为假时才进入循环,为真是结束循环,其语法结构如下:

1
2
3
4
until condition
do
statement
done

例子

1
2
3
4
until [ "$(word)" == "yes" ] || [ "$(word)" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " word
done

3.11.4 循环控制

类似C++bash也有continuebreak语句,功能一样,只是bash中的break语句不能用于case语句块中。

如果遇到死循环,可用top找到进程PID,然后用命令kill -9 PID结束进程。

3.12 函数

bash中的函数类似C++中的函数,但return不同。在bash中,return只能返回0~`255之间的exit code,若想获取返回值,可用echo输出到stdout中,然后再通过$(function_name)获取其stdout结果。exit code可通过$?`获取。

函数语法结构

1
2
3
[function] func_name() {
statement
}

其中function关键字可省略。

当不写return时,函数默认返回0

函数参数

$1表示函数的第一个输入参数,以此类推。但$0不表示函数名,而是文件名。

局部变量

local关键字声明,范围为函数体内。

1
2
3
4
func() {
local name=abc
echo $name
}

3.13 exit命令

exit用于结束当前进程,并返回一个退出状态,状态值为0~255之间的整数,其中0表示成功,其余表示失败。

3.14 文件重定向

每个进程默认打开3个文件描述符

  • 标准输入stdin:从命令行读取数据,文件描述符为0
  • 标准输出stdout:向命令行输出数据,文件描述符为1
  • 标准错误输出stderr:向命令行输出错误,文件描述符为2

重定向语法

命令 说明
command > file stdout重定向到file中,即把command的结果输出到file中,会覆盖file中的内容
command < file stdin重定向到file中,即commandfile中读入数据
command >> file >类似,只是是以追加的方式输出到file中,不会覆盖file的原始内容
command n> file 将文件描述符n重定向到file
command n>> file 将文件描述符n以追加的方式重定向到file

例子

1
./test.sh < input.txt > output.txt  # test.sh从input.txt读取数据,结果输出到output.txt中

3.15 引入外部脚本

类似C++引入头文件,有如下两种方式引用

1
2
. filename  # 注意空格
source filename