shell 是 Linux 操作系统的用户接口, 我们经常需要编写脚本让操作系统自动执行一系列指令的需求, 本文将简单介绍开发 shell 脚本的所需的语言特性
shell 脚本是指令序列, 其指令可以直接在终端中执行同样地, 终端中的指令也可以写到脚本中
脚本文件通常以. sh 作为后缀名, 第一行以 #! 开头指定执行脚本的程序:
#!/usr/bin/bash
以 #! 开头的第一行被称为 Hashbang 或 Shebang 而 #是 shell 脚本中的行注释符
通常有三种执行脚本的方式:
sh start.sh: 在终端中创建一个 sh 子进程执行脚本, 执行者需要拥有脚本的读权限
该方式实际上是将脚本路径作为参数传递给了 sh 命令
source start.sh: 在终端中执行脚本, 相当于将脚本中的指令逐条复制到终端执行
脚本中局部变量将保留在终端环境变量中, 脚本的 pid 和工作目录等环境也与终端一致
./start.sh: 根据 HashBang 指定的程序, 在子进程中执行脚本
sh 命令有一些有用的选项帮助我们开发和调试脚本:
sh -n start.sh: 对脚本进行语法检查, 不实际执行脚本
sh -x start.sh: 把将要执行的命令输出到 stderr 便于进行调试
变量
shell 中变量是弱类型的, 变量名只能包含字母数字或下划线 "_", 首字符只能为字母
shell 主要面向文本处理而非数据计算, 因此变量默认类型为字符串型
A=abc echo $A
变量在使用前无需声明, 在为变量赋值时 = 左右不能添加空格
A = abc 会被 shell 解释为执行指令 A, 参数为 = 和 abc
$ 为变量标志符, echo $A 指令将显示变量 A 的内容 abc, 为了明确指定变量名也可以写作 ${A}
- A=a
- AB=ab
- echo ${A}B
$(cmd)可以把命令的输出作为返回值, 如:
PWD=$(pwd)
变量 $PWD 存储了当前的工作目录路径
在 shell 中可以直接书写字符串, 但仍建议用单引号或双引号标识字符串
在单引号标识的字符串中 $ 不被作为变量标识符, 而双引号则会将 $ 替换为变量内容
- A="abc"
- echo '$A' # $A
- echo "$A" # abc
字符串拼接不需要任何运算符, 只需要将它们写在一起即可:
- A="abc"
- B="123"
- echo "$A+$B" # abc+123
- echo "$A$B" # abc123
- echo "$Adef" # abcdef
全局变量
变量按照作用域可以分为局部变量和全局变量, 上文示例中定义的变量都是局部变量, 作用域仅限执行脚本的进程
子进程可以继承父进程的全局变量, export 指令用于定义全局变量:
export A=abc
整型变量
shell 仅支持整型计算, declare 命令可以声明整型变量, let 指令用于算术运算:
- declare -i a=1
- let a=a+1
- echo $a # 2
- let a+=1
- echo $a # 3
let 指令支持算术运算符包括:
+: 加法
-: 减法
*: 乘法
/: 除法
**: 乘方
%: 取余
let 指令也支持算术运算符对应的算术赋值运算符, 如 +=
数组
bash 中可以使用圆括号定义数组, 元素之间用空格分割, 数组下标从 1 开始:
- arr=(1 'a' "abc")
- echo ${arr[1]}
也可以直接使用下标定义数组:
- arr2[1]=1
- arr2[2]=2
该方法同样可以用于修改已存在的数组
特殊变量
shell 中预定义了一些特殊变量, 通过这些变量可以获得环境信息:
$$: 执行脚本的进程 ID(pid)
$?: 上一条命令的返回值
$!: 上一条后台指令的执行进程的 ID
上述变量在交互式终端中同样有效
还有一些变量可以获得执行脚本时传入的参数:
$0: 脚本的文件名
$1~$n: 传给脚本的第 n 个参数
$#: 传入参数的个数
$@: 参数列表
$*: 单个字符串形式的参数列表
流程控制
- if
- declare -i a=90
- if [ $a -gt 80 ]; then
- echo "A"
- elif [ $a -lt 60 ]; then
- echo "C"
- else
- echo "D"
- fi
结束标志 fi 即是 if 反写, 我们还将在其它地方遇到 bash 的这种命名风格
注意,[]旁边的空格不可省略
-lt, -gt 用于进行整型的大小比较:
-eq: 等于(equal)
-ne: 不等于(not equal)
-gt: 大于(greater)
-ge: 大于等于(greater-equal)
-lt: 小于(less)
-le: 小于等于(less-equal)
进行复合逻辑判断也很简单:
- if [ $a -gt 60 -a ( ! $a -gt 90 -o $a eq 91 ) ]; then
- echo "make no sense"
- fi
!: 非
-a: 且 and
-o: 或 - o
逻辑运算遵循短路计算原则
<, >等运算符在 [] 中只能用于字符串的比较, 而在 [[]] 中<, >可以用于整型和字符串的大小比较, 也可以使用 && 和 || 来书写逻辑表达式
if 的条件判断不一定使用 [] 或[[]]表达式, 它可以是任何一个命令命令的返回值为 0 则 if 判断为真, 非 0 判断为假
[]和 [[]] 转义表达式也可以像普通指令一样执行, 判断为真则返回 0, 假则返回非 0 值
[2 - gt 1 - a 3 - lt 4] && echo 'ok'
除此之外, if 还可以进行更多种类的条件判断:
判断字符串相等
- if [ ${NAME} = 'tmp' ]; then
- echo "name is tmp"
- fi
判断文件是否存在
- if [ -e tmp ]; then
- echo "tmp exists"
- fi
判断 tmp 是否存在, tmp 可以是目录或文件
判断是否为普通文件
- if [ -f tmp ]; then
- echo "file tmp exists"
- fi
判断 tmp 是否为文件, tmp 不能是目录
判断是否为目录
- if [ -d tmp ]; then
- echo "directory tmp exists"
- fi
判断是否具有执行权限
- if [ -x tmp ]; then
- echo "tmp is executable"
- fi
不判断文件是否可执行, 只判断是否拥有 x 权限 因此, tmp 为有 x 权限的目录时也会判断为真
类似的还有,-w 判断是否拥有写入权限, -r 判断是否拥有读取权限
判断是否为空文件
- if [ -s tmp ]; then
- echo "file is not empty"
- fi
- case
case 类似于其它语言中的 switch 语句:
- case ${NAME} in
- 'a')
- echo "name is a"
- ;;
- 'b')
- echo "name is b"
- ;;
- *)
- echo "other names"
- ;;
- esac
从第一个匹配的标签开始执行, 两个标签之间必须有;;,*)是其它标签都不匹配时的默认标签
for
for 循环可以遍历一个序列:
- for i in $(seq 1 10); do
- echo $i
- done
- # echo: 1 2 3 ... 10
一些命令的输出也可以作为序列:
- for i in $(ls); do
- echo $i
- done
遍历所有参数:
- for arg in "$@"; do
- echo $arg
- done
另一种形式的 for 循环:
- for (( i=0; i<100; i++)); do
- echo $i
- done
- while
- declare -i i=0
- while [ $i -lt 10 ]; do
- echo $i
- i=$i+1
- done
while(true)这样的死循环也很容易:
- declare -i i=0
- while ; do
- echo $i
- [ ! $i -lt 10 ] && break
- i=$i+1
- done
函数
shell 提供了定义函数的功能, 函数就像是脚本中的子脚本:
- range() {
- for (( i=0; i<${1}; i++)); do
- echo $i
- done
- return ${1}
- }
- range 100
函数同样使用位置参数 $1~$n 来访问参数,$0 为函数的名称, $@, $# 等变量的含义不变
进程间通信
管道
管道用于将上一条指令的输出作为下一条指令的输入:
- ls | grep ".zip"
- xargs
有一些指令不支持使用管道传递参数, 因此需要 xargs 命令
find ~ | xargs ls
xargs 会以空格为分隔符将输入分隔为参数, 然后将参数传给 ls
重定向
重定向用于将命令的输入输出从标准流重定向到文件标准流包括:
stdin: 标准输入流, 文件描述符为 0
stdout: 标准输出流, 文件描述符 1
stderr: 标准错误流, 文件描述符 2
输出到文件, 覆盖原有内容:
echo "hello" > 1.txt
输出到文件, 追加到文件尾:
echo "hello" >> 1.txt
从文件输入:
wc -l < 1.txt
重定向标准错误输出流:
cmd 2> 2.txt
将标准错误输出追加到文件:
cmd 2>> 2.txt
将标准错误和标准输出一同重定向到文件:
cmd > 1.log 2>&1
2>&1 是将 stderr 重定向到 stdout
后台执行
shell 可以执行一行指令后立即返回, 返回后可以通过 $? 变量获得执行进程的 ID:
- $ sleep 10 &
- [1] 79403
- $ echo $!
- 79403
来源: https://www.cnblogs.com/Finley/p/8442628.html