Shell脚本简明教程
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shell脚本简明教程相关的知识,希望对你有一定的参考价值。
1. 简介
Shell脚本是一种简单的脚本语言,运行在Unix-like的操作系统上,像Linux,mac, unix等。Shell脚本的解析器是shell,Unix-like系统很多,所以出现了很多不同的shell,像tcsh, csh, ash, bash, dash等。
像Ubuntu默认使用的Shell是dash,其特点是解析执行速度快,缺点是支持的语法特性少。如果要查看自己系统上使用的是何种解释器,可以使用如下命令:
ls /bin/sh -al
# or
echo $SHELL
bash使用更为便捷,此文主要以bash解释器为基础来进行讲解。Shell有一些老的语法形式,不推荐使用,此文不讲解。
因为系统内置了shell解释器,所以可以直接在控制台窗口上输入Shell脚本来解释执行。也可以将Shell脚本编写到以.sh为后缀的脚本文件当中。然后通过sh/bash调用执行,也可以将脚本文件修改为可执行模式,直接运行。
2. 语法
2.1. 注明解释器
因为Shell脚本的解释器有很多版本,不同版本之间的功能略有差异,为了防止编写的脚本应用时使用的解释器不同而导致脚本功能异常,系统在调用解释器时会先检查脚本文本的第一行,如果有指定解释器路径,则以此解释器来执行当前脚本。
#!/bin/bash
2.2. 注释
在代码中添加注释,可以提高代码的可读性,降低开发维护难度。
- 单行注释
# 此行是注释
echo start
- 多行注释
多行注释可以利用单行注释重复多次来完成,也可以使用特殊形式
# 此行是注释
# 此行是注释
# 此行是注释
:<<!
Copyright 2022, Ys Co. Ltd.
All rights erserved.
Revision: 1.0.0
!
2.3. 变量
- 变量定义
语法形式:variable_name=“content”
需要注意,=两边都不能有空格,变量内容可以不加双引号,也可以加双引号(可以防止有空格将内容截断)。
变量名须满足以下要求: - 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线 _。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
Shell脚本的变量,默认都是字符串类型,只是在某些需要整形的语法形式中,编译器会将字符串转换为对应的整形来进行解析,此时如果字符串中有非数字字符,解释器会报错”value too great for base“。Shell中的数字只支持10进制整形。 - 变量修饰
– 局部变量,只能用在函数中,且局部变量的定义要与赋值分开。local var
– 只读变量,防止他人修改的变量,readonly MAX_SIZE=1024
– 环境变量,用来控制程序启动时的参数。 - 变量使用
语法形式:
$variable_name
$variable_name
”$variable_name“
”$variable_name“
加双引号是为了防止引用变量中有空格时,作为形参传递给第三方应用程序时被截断。所以推荐第4种用法,但是一些常见的$1,$2等也遵循惯例。关于语法形式,推荐使用shellcheck来校验。
- 变量删除
当不想使用变量时,可以删除变量
unset variable_name
2.4. 字符串
在非数字操作的场景中,所有变量都被当作字符串,字符串可以加单引号,也可以加双引号,也可以不加引号。
- 字符串长度
string="abcd"
echo $#string # 输出 4
- 切割子字符串
字符串切片
str="abcdefg"
# 从第1个到第4个(从0开始)
echo $str:1:4
# 从第1个到倒数第2个(从0开始)
echo $str:1:-2
- 替换字符串
ip="172.16.36.40"
# 只替换第1个.:172-16.36.40
echo $ip/./-
# 替换所有:172-16-36-40
echo $ip//./-
- 删除字符串
[root@payqa1 work]# echo $ip
172.16.36.40
# 从头开始删除到第1个.
[root@payqa1 work]# echo $ip#*.
16.36.40
# 从头删除到最后1个.
[root@payqa1 work]# echo $ip##*.
40
# 从尾部开始删除
[root@payqa1 work]# echo $ip%.*
172.16.36
[root@payqa1 work]# echo $ip%%.*
172
- 转义字符
如果字符串中有转义字符,必须使用双引号。
name="new line\\n"
echo -e $name
2.5. 数组
- 数组定义
Shell脚本只支持1维数组。数组以中括号加空格分割构成,其语法形式:
array_name=(val1 val2 … valn) - 数组访问
数组以中括号加下标访问
# 访问第0个元素
echo $array_name[0]
# 访问所有元素
echo $array_name[@]
- 数组大小
# 取得数组元素的个数
length=$#array_name[@]
# 取得数组单个元素的长度
lengthn=$#array_name[n]
- 关联数组
关联数组,也即字典,通过key和value进行映射。
declare -A high=(["mike"]=178 ["will"]=190 ["jack"]=172)
high["lily"]=165
echo "$high[@]"
echo "$high["mike"]"
- 删除数组元素
arr=(1 2 3)
unset arr[1]
echo $arr[@]
2.6. 参数
- 参数调用
Shell脚本的参数传递方法都是一样的,无论是函数参数、命令行参数还是脚本参数,都是以空格加参数内容构成,如果有多个参数,则多个空格多个参数。
echo "param1" "param2"
echo $p1 $2
./run.sh "$p1" "$p2"
- 参数获取
如在run.sh脚本中获取调用者传入的参数:
# $1是脚本名字
echo "param1:" $1
echo "param2:" $2
echo "param count:" $#
echo "All param:" $@
2.7. 算术运算
Shell脚本只支持整形计算,包括负数,其范围为64bit大小。
Shell脚本支持加、减、乘、除和取余操作,其语法主要有两形式。
let a=4*4
let a=a*8
b=8
# 推荐
(( b=b*8 ))
echo $a
echo $b
2.8. 逻辑判断
逻辑判断语法形式:
if expr; then
# do something
elif expr
# do something
else
# do something
fi
逻辑表达式,针对不同的情况,有不同的表达式
2.8.1. 数字比较
a=10
b=11
if [ $a -eq $b ]; then
echo "$a -eq $b : a 等于 b"
fi
if [ $a -ne $b ]; then
echo "$a -ne $b: a 不等于 b"
fi
if [ $a -gt $b ];then
echo "$a -gt $b: a 大于 b"
fi
if [ $a -lt $b ]; then
echo "$a -lt $b: a 小于 b"
fi
if [ $a -ge $b ]; then
echo "$a -ge $b: a 大于或等于 b"
fi
if [ $a -le $b ]; then
echo "$a -le $b: a 小于或等于 b"
fi
# 另外一种方式直接符号比较
if (( $a <= $b )); then
echo "$a -le $b: a 小于或等于 b"
fi
if (( $a != $b )); then
echo "$a -ne $b: a 不等于 b"
fi
2.8.2. 字符串比较
字符串比较有两种形式使用[],[[]],后者功能更强大,推荐后者。
if [[ $a == z* ]]; then
# $a只要以z开头即为true
fi
if [[ $a == "z*" ]]; then
# $a为”z*“时才为true
fi
if [[ "$a" > "$b" ]]; then
echo "a greater than b"
fi
if [[ -z "$a" ]]; then
echo "$a length is 0"
fi
if [[ -n "$a" ]]; then
echo "$a length is not 0"
fi
if [[ "$a" ]]; then
echo "$a length is not null"
fi
2.8.3. 文件判断
file="/var/test.sh"
if [ -r $file ]; then
echo "文件可读"
fi
if [ -w $file ]; then
echo "文件可写"
fi
if [ -x $file ]; then
echo "文件可执行"
fi
if [ -f $file ]; then
echo "文件为普通文件"
fi
if [ -d $file ]; then
echo "文件是个目录"
fi
if [ -s $file ]; then
echo "文件不为空"
fi
if [ -e $file ]; then
echo "文件存在"
fi
2.8.4. 逻辑运算
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
# 3个命令都会执行
cmd && cmd2 && cmd3
# 只要有一个失败,后面的就不执行
cmd || cmd2 || cmd3
2.9. 循环
2.9.1. for
# 按空格分割遍历in后面的内容
for loop in 1 2 3 4 5; do
echo "The value is: $loop"
done
for str in This is a string; do
echo $str
done
#$#array[@]获取数组长度用于循环
for(( i=0;i<$#array[@];i++ )); do
echo $array[i];
done;
# 不带数组下标
for element in $array[@]; do
echo $element
done
# 带数组下标
for i in "$!array[@]"; do
printf "%s\\t%s\\n" "$i" "$array[$i]"
done
2.9.2. while
while 循环用于不断执行一系列命令,也用于从输入文件中读取数据。
int=1
while (( int <= 5 )); do
echo $int
(( int++ ))
done
# 读取用户键盘输出
while read -r line; do
echo "$line"
done
# 读取文件util.sh,按行输出
while read -r line; do
echo "$line"
done < util.sh
2.9.3. until
until循环到满足条件
a=0
# 输出0到9
until [ ! $a -lt 10 ]; do
echo $a
(( a++ ))
done
2.9.4. case
read aNum
case $aNum in
1) echo '1'
;;
2) echo ' 2'
;;
3) echo '3'
;;
4) echo '4'
;;
*) echo 'default'
;;
esac
name="mike"
case "$name" in
# ”mi"*匹配任何以mi开头的字符串
"mi"*) echo "mike"
;;
"jack") echo "jack"
;;
"sam") echo "sam"
;;
esac
2.9.5. break
跳出所有循环。
for(( i=0;i<$#array[@];i++ )); do
if (( i -eq 2 )); then
echo $array[i]
break
fi
done;
2.9.6. continue
退出当前循环,继续下一次循环
for(( i=0;i<$#array[@];i++ )); do
if (( i -eq 2 )); then
continue
echo $array[i]
fi
done;
2.9.7 select
select in 循环用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。
通常select是配合case来使用。
echo "What is your favourite OS?"
select name in "Linux" "Windows" "Mac OS" "UNIX" "android"; do
echo $name
done
echo "You have selected $name"
2.10. 输入输出
2.10.1. 输入
2.10.1.1. read
read [-optins] [variables]
选项 | 说明 |
---|---|
-a | array 把读取的数据赋值给数组 array,从下标 0 开始。 |
-d | delimiter 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。 |
-e | 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。 |
-n | num 读取 num 个字符,而不是整行字符。 |
-p | prompt 显示提示信息,提示内容为 prompt。 |
-r | 原样读取(Raw mode),不把反斜杠字符解释为转义字符。 |
-s | 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。 |
-t | seconds 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 |
-u | fd 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。 |
read -r -t 10 -s -p "Enter password in 20 seconds(once) : " pass1 && printf "\\n"
read -r -t 10 -s -p "Enter password in 20 seconds(again): " pass2 && printf "\\n"
if [ "$pass1" == "$pass2" ]; then
echo "Valid password"
else
echo "Invalid password"
fi
2.10.1.2. 命令替换
variable=$(cmd)
shell运行命令替换符号中的命令,并将其输出赋值给变量variable。
cmd可以是函数,可以是命令,也可以是可执行程序。
var=$(echo "abcd")
var=$(bc "1+2")
a=$(echo "1+2" | bc)
2.10.2. 输出
2.10.2.1. echo
echo string,默认将字符串输出到标准终端控制台。
# 普通输出
echo "abcd"
# 不换行输出
echo "abc\\r"
# 输出变量
echo $1
# 默认转义
echo "\\"abc\\""
# 关闭转义
echo "abc\\n"
# 覆盖输入到文件
echo "abc">file
# 附加输出到文件
echo "abc">>file
2.10.2.2. printf
printf,格式化输出。
- %s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。
- %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
- %-4.2f 指格式化为小数,其中 .2 指保留2位小数。
# printf默认不带换行
printf "abcd\\n"
# format-string为双引号
printf "%d %s\\n" 1 "abc"
# 保留位数
printf "%-10s %-8s %-4.2f\\n" lily 女 47.9876
2.11. 函数
将代码封装成函数,提升代码复用性。
[ function ] funname [()]
action;
[return int;]
# 无参数无返回,推荐写function标识
test()
echo "This is function"
# 执行函数
test
# 有参数有返回值
add()
(( ret = $1 + $2 ))
return $ret
# 函数return值通过$?获取
add 2 3
echo $?
2.12. 输入/输出重定向
大多数命令的输出默认是输出到终端的标准输出(STDOUT),错误信息则输出到标准错误输出(STDERR)中。
- 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
- 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
- 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
# 重定位到文件file
echo abc 1>file
# 重定位到文件file,文件描述符1可以省略
echo abc>file
# 重定向文件输入
command1 < file1
# 不显示在屏幕上
command > /dev/null
屏蔽 stdout 和 stderr
command > /dev/null 2>&1
2.13. 引用文件
一些公共代码可以封装到一个文件中,供不同项目使用
# .和文件中必须要有空格
. base.sh
# 推荐
source base.sh
2.14. 执行文件
2.14.1. 执行应用程序
- 直接运行,如:fio
- exec执行,执行完之后立即退出,不再执行下面的代码。如:exec fio
2.14.2. 执行shell脚本
- exec执行,创建子进程执行,执行完之后立即退出,不再执行下面的代码。如:exec base.sh
- source base.sh,在当前进程中执行脚本,变量可以共用,阻塞式执行。
- ./base.sh, 直接运行,子进程调用,阻塞式执行。
- bash base.sh,命令参数调用,子进程调用,阻塞式执行。
# base.sh
# 输出进程号
echo $$
# test.sh
# 输出进程号
echo $$
source base.sh
./base.sh
bash base.sh
# 打印退出码
echo $?
exec ./base.sh
2.15. 组命令和子进程
2.15.1. 组命令
组命令,就是将多个命令划分为一组,或者看成一个整体。
Shell 组命令的写法有两种:
command1; command2;. . .;
( command1; command2;. . . )
使用花括号时,花括号与命令之间必须要有一个空格,并且最后一个命令必须用一个分号或一个换行符结束组命令可以将多条命令的输出结果合并在一起,在使用重定向和管道时会特别方便。
两种写法的重要不同:由包围的组命令在当前 Shell 进程中执行,由()包围的组命令会创建一个子Shell,所有命令都会在这个子 Shell 中执行。
在子 Shell 中执行意味着,运行环境被复制给了一个新的 shell 进程,当这个子 Shell 退出时,新的进程也会被销毁,环境副本也会消失,
所以在子 Shell 环境中的任何更改都会消失(包括给变量赋值)。因此,在大多数情况下,除非脚本要求一个子 Shell,
否则使用比使用()更受欢迎,并且的进行速度更快,占用的内存更少。
2.15.2. 子进程
子进程的概念是由父进程的概念引申而来的。在 Linux 系统中,系统运行的应用程序几乎都是从 init(pid为 1 的进程)进程派生而来的,所有这些应用程序都可以视为 init 进程的子进程,而 init 则为它们的父进程。
Shell 脚本是从上至下、从左至右依次执行的,即执行完一个命令之后再执行下一个。如果在 Shell 脚本中遇到子脚本(即脚本嵌套,但是必须以新进程的方式运行)或者外部命令,就会向系统内核申请创建一个新的进程,以便在该进程中执行子脚本或者外部命令,这个新的进程就是子进程。子进程执行完毕后才能回到父进程,才能继续执行父脚本中后续的命令及语句。
使用pstree -p命令就可以看到 init 及系统中其他进程的进程树信息(包括 pid):
systemd(1)─┬─ModemManager(796)─┬─ModemManager(821)
│ └─ModemManager(882)
├─NetworkManager(975)─┬─NetworkManager(1061)
│ └─NetworkManager(1077)
├─abrt-watch-log(774)
├─abrt-watch-log(776)
├─abrtd(773)
├─accounts-daemon(806)─┬─accounts-daemon(839)
│ └─accounts-daemon(883)
├─alsactl(768)
├─at-spi-bus-laun(1954)─┬─dbus-daemon(1958)───dbus-daemon(1960)
│ ├─at-spi-bus-laun(1955)
│ ├─at-spi-bus-laun(1957)
│ └─at-spi-bus-laun(1959)
├─at-spi2-registr(1962)───at-spi2-registr(1965)
├─atd(842)
├─auditd(739)─┬─audispd(753)─┬─sedispatch(757)
│ │ └─audispd(759)
│ └─auditd(752)
2.15.3. 如何检测子shell与子进程
echo $$输出当前进程ID,echo $PPID输出父shell ID。
除了 $,Bash 还提供了另外两个环境变量——SHLVL 和 BASH_SUBSHELL,用它们来检测子 Shell 非常方便。
SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,每次进入一层普通的子进程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(sub shell)嵌套深度的累加器,每次进入一层子 Shell,BASH_SUBSHELL 的值就加 1。
2.16. 管道
管道的主要是作用是将一个命令的输出作为另一个命令的输入。管道符号为"|"。
echo "1+3" | bc
dmesg | grep "inth"
2.17. 后台执行
后台执行,也就是异步执行,非阻塞式的。
- 针对单条命令,可以使用nohup加&
nohup cmd &
- 针对一组命令可以使用
(cmd1; cmd2)&
- 针对一段代码,可以使用
cmd1
cmd2
sleep
cmd3
&
- 等待所有进程结束
wait
2.18. 信号与捕获
信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行相应的处理函数(即捕获信号)。
一种是标准信号,编号1-31,称为非可靠信号(非实时),不支持队列,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,如果第一个信号没有处理完,第二个信号将会丢弃。其中比较典型的有一种是内核检测到系统事件,比如键盘输入CTRL+C会发送SIGINT信号。
另一种是通过系统调用kill命令来向一个进程发送信号。
2.18.1. 设置信号捕获
设置信号号为50的信息捕获
trap “echo catch signal” 50
2.18.2. 发送信息
发送信息使用kill指令
给当前进程发送50号信息
kill -n 50 $$
2.18.3. 示例
custom_sig=50
trap "echo abc" $custom_sig
sleep 2
kill -n $custom_sig $$
# 屏蔽Ctrl+C功能,响应必须为空
trap "" INT
2.19. 退出码
脚本退出码通过exit函数设置,可以通过$?获取退出码。
$?也可以获取第三方可执行程序的退出码。
退出码,默认0为正常,非0为异常。
2.20. 常用的命令行工具
unix-like哲学是以进程来作为模块完成功能,所以unix-like系统中有大量的功能都是小工具来完成的。
- sleep, 以秒延时时间,支持小数
- bc, 计算模块
- grep,正则匹配模块
- awk, 文本操作模块
- sed, 文本遍历替换
- at, 定时任务
- cat,head,tail,文本读取模块
- gzip,解压缩模块
- cp,scp,拷贝,远程拷贝模块
- curl,网络通信模块
- chmod,文本模式修改模块
- wget,下载模块
- touch,创建文件
- mv, 移动文件
- rm, 删除文件
- find, 查找功能
- date,时间功能
- ln, 软链接
- ssh, ssh通信
2.21. 内置环境变量
变 量 | 功 能 |
---|---|
BASH | 调用 Shell 的完整文件名 |
BASHOPTS | 启用 Bash Shell 的选项列表 |
BASHPID | 当前 Bash Shell 的进程 ID |
BASH_ALIASES | 含有当前所用别名的关联数组 |
BASH_ARGC | 当前子函数或 Shell 脚本中的参数总数的数组变量 |
BASH_ARCV | 当前子函数或 Shell 脚本中的参数的数组变量 |
BASHCMDS | 关联数组,包括 Shell 执行过的命令的所在位置 |
BASH_COMMAND | 当前正在被执行的命令名 |
BASH_ENV | 如果设置,每个 Bash 脚本都会尝试在运行前执行由该变量定义的起始文件 |
BASH_EXECUTION_STRING | 在 Bash -c 命令行选项中用到的命令 |
BASH_LINENO | 含有脚本中每个命令的行号的数组变量 |
BASH_REMATCH | 只读数组,含有与指定的正则表达式匹配的文本元素的数组 |
BASH_SOURCE | 含有 Shell 中已声明函数所在源文件名的数组变量 |
BASH_SUBSHELL | 当前 Shell 生成的子 Shell 数目 |
BASH_VERS INFO | 含有当前运行的 Bash Shell 的主版本号和次版本号的数组变量 |
BASH_VERS ION | 当前运行的 Bash Shell 的版本号 |
BASH_XTRACEFD | 当设置一个有效的文件描述符整数时,跟踪输出生成,并与诊断和错误信息分离开文件描述符必须设置 -x 启动 |
COLUMNS | 当前 Bash Shell 实例使用的终端的宽度 |
COMP_CWORD | 变量 COMP_WORDS 的索引值,COMP_WORDS 包含当前光标所在的位置 |
COMP_KEY | 调用 Shell 函数补全功能的按键 |
COMP_LINE | 当前命令行 |
COMP POINT | 当前光标位置相对于当前命令起始位置的索引 |
以上是关于Shell脚本简明教程的主要内容,如果未能解决你的问题,请参考以下文章 linux shell编程bash编程shell教程bash教程shell文档bash文档shell脚本bash脚本教程第一部分:绪论 |