shell脚本编程

本文档内容:

  • 一、Shell 和Shell 脚本
  • 二、管道和重定向
  • 三、test 和printf 命令
  • 四、变量
  • 五、参数
  • 六、运算符
  • 七、流程控制
  • 八、函数
  • 九、读写文件
  • 十、并发执行
  • 十一、括号相关

一、Shell 和Shell 脚本

Shell 是一种命令行解释器,用于与操作系统进行交互和执行命令。是用户与操作系统内核之间的接口。
它提供了一种交互式的方式,让用户可以通过命令来操作计算机,执行各种任务和管理系统资源。
Shell 脚本是一种使用Shell 解释器执行的脚本文件,其中包含了一系列的命令和脚本语句。

二、管道和重定向

管道是一个进程与另一个进程之间通信的通道,通常用于将一个进程的标准输出连接到另一个进程的标准输入。Linux 使用竖线(|)作为管道符连接两个命令,其格式为:command1 | command2。
管道命令只能处理前一个命令正确输出,不处理错误输出。在管道符右边的命令,必须是能够接受标准输入的命令。

shell脚本编程_第1张图片

由管道串起的两个命令并不会依次执行。Linux 系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。数据传输不会用到任何中间文件或缓冲区。

重定向是指将从标准输入/输出设备(如键盘/显示器)输入/输出的数据改由其他文件或设备输入/输出。
Linux 系统在启动一个进程时会为其打开三个文件:标准输入(stdin)、标准输出(stdout)和标准错误(stderr),分别用文件标识符0、1和2来标识。Shell 中最多可以有9个打开的文件描述符。其他6个从3~8的文件描述符均可用作输入或输出重定向。默认情况下标准输入为键盘,标准输出和错误输出为显示器。

常见的重定向符号如下:

符号 含义 举例
> 标准输出覆盖重定向:将命令的标准输出写到重定向的文件中 ls > ls.txt(列举内容将其标准输出写入到ls.txt 文件中)
>> 标准输出追加重定向:将命令的标准输出追加重定向的文件中 ls >> ls.txt (列举内容将其标准输出追加到ls.txt 文件中)
&> 标准输出和错误覆盖重定向:将命令的标准输出和错误写到重定向的文件中 ls * abc &> ls.txt (列举内容将其标准输出和标准错误写到ls.txt 文件中)
&>> 标准输出和错误追加重定向:将命令的标准输出和错误追加重定向的文件中 ls * abc &>> ls.txt (列举内容将其标准输出和标准错误追加到ls.txt 文件中)
>& 标识输出重定向:将一个标识的输出重定向到另一个标识的输入 ls * abc > ls.txt 2>&1(列举内容将其标准输出和标准错误追加到ls.txt 文件中)
< 标准输入重定向:命令将从指定文件中读取输入而不是从键盘输入 cat < cat.txt(从文件cat.txt 获取内容作为cat 命令的标准输入)
| 管道符,从一个命令中读取输出并作为另一个命令的输入 ls | grep x(列举内容将其传递给grep 搜索)
<> 将文件打开并指定标识符 exec 3<>test.txt(打开文件test.txt并指定其文件标识符为3)
&- 关闭文件标识符 exec 3>&-(关闭文件标识符3)、0<&-或<&-(关闭标准输入)、1>&-或>&-(关闭
标准输出)

内联输入重定向是一种不使用文件进行输入重定向的方式,只需在命令行中或脚本里指定用于输入重定向的数据即可。其格式为:

command << EOF
content
EOF

它的作用是将两个EOF 之间的内容(content)作为输入传递给命令command。
注意:
• 开始的EOF 前后的空格会被忽略掉;
• 结尾的EOF 一定要顶格写,且其后不能有任何字符(包括空格和tab);
• 任何字符串都可作为文本标记(格式中的EOF),但在数据的开始和结尾文本标记必须一致。

三、test 和printf 命令

1,test 命令

用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。其格式为:

test 测试条件
a,数字测试: 
参数 说明
-eq 等于则为真
-ne 不等于则为真
-gt 大于则为真
-ge 大于等于则为真
-lt 小于则为真
-le 小于等于则为真

举例:

x=1
y=2
if test $x -eq $y; then
echo 'x 等于y'
else
echo 'x 不等于y'
fi

b,字符测试: 
参数 说明
= 等于则为真
!= 不等于则为真
-z 字符串 字符串的长度为零则为真
-n 字符串 字符串的长度不为零则为真

举例:

s=a
if test -z $s; then
echo 字符串为空
else
echo 字符串不为空
fi

 c,文件测试(测试条件请见“运算符——文件测试运算”部分)

2,printf 命令

用于格式化输出。其格式为:

printf 格式控制字符串参数列表

控制符

描述

c

将一个数作为ASCII字符显示

d

显示一个整数值

i

显示一个整数值(跟d一样)

e

用科学计数法显示一个数

f

显示一个浮点值

g

用科学计数法或浮点数显示(选择较短的格式)

o

显示一个八进制值

s

显示一个文本字符串

x

显示一个十六进制值

举例:

printf "%-10s %-8s %-4s\n" 姓名性别体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖男66.1234
printf "%-10s %-8s %-4.2f\n" 杨过男48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙女47.9876

%s %c %d %f都是格式替代符。

%-10s 指一个宽度为10个字符(-表示左对齐,没有则
表示右对齐),任何字符都会被显示在10个字符宽的字
符内,如果不足则自动以空格填充,超过也会将内容全
部显示出来。

%-4.2f 指格式化为小数,其中.2指保留2位小数。

四、变量

1,定义变量

变量名=变量值,如n=1。
注意:
• 变量名前不用美元符($);
• 等号前后不能有空格;
• 默认变量作用域为全局(在函数中,变量名前加关键字local 定义局部变量,如local n=1);
• 变量名前加关键字readonly 定义只读变量,如readonly n=1。


2,引用变量

在变量名前面加美元符号即可,如: $n 或${n}。
注意:
• 变量名外的花括号是可选的,花括号只是为了帮助解释器识别变量的边界,如echo "${n}23" ;
• 只有在引用变量的值时变量名前才加美元符,其他情况不加(如给变量重新赋值,另外在双圆括号中引用变量
也不用加)。


3,删除变量

unset 变量名
注意:readonly 修饰的变量不支持unset。 

4,数组变量说明

Shell 中用圆括号来表示数组,数组各元素用“空格”隔开。定义语法为:

数组名=(值1 值2 ... 值n)


可以使用下标单独定义数组的分量。下标可以不连续且范围没有限制。如:

arr[0]=A
arr[9]=B


• 通过${数组名[下标]} 方式读取数值的值,使用@ 或* 可以获取数组中的所有元素,如:

${arr[@]};


• 获取数组的长度:

${#arr[@]} 或${#arr[*]}


• 可以使用declare 命令来声明关联数组,以便使用任意字符串做下标来访问数组,如:

declare -A array=(["a"]=A ["b"]=B)



 

declare -A array
array["a"]=A
array["b"]=B

五、参数

1,参数列表

向脚本或函数传递参数,脚本或函数内获取参数的格式为:$n。n 代表一个数字。$1代表第一个参数值,$2代表第二个参数值,以此类推……

特殊字符参数说明:

参数 说明
$0 程序名
$# 传递的参数个数
$* 以单一字符串显示向脚本传递的参数。
如"$*" 用双引号引起来的情况,以"$1 $2 … $n" 的形式输出所有参数
$@ 与$* 相同,但在使用时加引号,并在引号中返回每个参数。
如"$*" 用双引号引起来的情况,以"$1" "$2" … "$n" 的形式输出所有参数
$$ 脚本运行的当前进程的ID 号
$! 后台运行的最后一个进程的ID 号
$- 显示Shell 当前使用的选项
$? 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误

2,参数移动

shift 命令用来根据参数的相对位置移动命令行参数。使用此命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(无法找回)。(注意,变量$0的值,也就是程序名,不会改变)。
将如下代码保存到test.sh:

count=1
while [ -n "$1" ]; do
echo "Parameter $count=$1"
count=$[$count + 1]
shift
done


在终端执行bash ./test.sh a b c 输出如下:
 

Parameter 1=a
Parameter 2=b
Parameter 3=c

3,参数解析

getopts 命令用来解析命令行参数。其格式为:

getopts optstring option


有效的选项字母会列在optstring中,如果选项字母要求有参数值,就在其后加一个冒号。若要去掉错误消息的话,可以在optstring之前加一个冒号。option变量保存当前正在处理的参数。
getopts会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。
OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。
每次调用时,getopts一次只处理检测到的一个参数。处理完所有参数后,它会退出并返回一个大于0的退出状态码。 

while getopts ab:c opt; do
case $opt in
a) echo "Found the -a option";;
b) echo "Found the -b option, with value $OPTARG";;
c) echo "Found the -c option";;
*) echo "Unknown option: $opt";;
esac
done

将左侧代码保存到test.sh文件中,在终端执行:

bash ./test.sh -a -bB -c -d


输出如下:

Found the -a option
Found the -b option, with value B
Found the -c option
./test.sh: illegal option -- d
Unknown option: ?


注意:getopts会将所有未定义的选项统一输出成问号。

六、运算符

1,用expr 命令执行算术运算

格式为expr ARG1 操作符ARG2 …,如expr 1 + 2 + 3。其支持的操作符如下:

操作符 说明
ARG1 | ARG2 如果ARG1既不是null也不是零值,返回ARG1;否则返回ARG2
ARG1 & ARG2 如果没有参数是null或零值,返回ARG1;否则返回0
ARG1 < ARG2 如果ARG1小于ARG2,返回1;否则返回0
ARG1 <= ARG2 如果ARG1小于或等于ARG2,返回1;否则返回0
ARG1 = ARG2 如果ARG1等于ARG2,返回1;否则返回0
ARG1 != ARG2 如果ARG1不等于ARG2,返回1;否则返回0
ARG1 >= ARG2 如果ARG1大于或等于ARG2,返回1;否则返回0
ARG1 > ARG2 如果ARG1大于ARG2,返回1;否则返回0
ARG1 + ARG2 返回ARG1和ARG2的算术运算和
ARG1 - ARG2 返回ARG1和ARG2的算术运算差
ARG1 * ARG2 返回ARG1和ARG2的算术乘积
ARG1 / ARG2 返回ARG1被ARG2除的算术商
ARG1 % ARG2 返回ARG1被ARG2除的算术余数
STRING : REGEXP 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配
match STRING REGEXP 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配
substr STRING POS LENGTH 返回起始位置为POS(从1开始计数)、长度为LENGTH个字符的子字符串
index STRING CHARS 返回在STRING中找到CHARS字符串的位置;否则,返回0
length STRING 返回字符串STRING的数值长度

注意:
a. 操作符前后要有空格;
b. 遇到特殊字符(如*),需要用反斜线转义,如:expr 2 \* 3;
c. 只支持整数运算。

2,用$[] 或$(()) 将数学表达式包在其中执行算术运算

如:

$[1 + 2] 或$((2 * 3))


注意:1. 只支持整型;2. 特殊符号不用转义。

3,使用内建的bc 计算器执行算术运算符(支持浮点运算)

如:

echo "2 == 3" | bc 或echo "scale=2; 1/3" | bc


bc << EOF
scale=2
x=2.2
x * x
x / 3


EOF # EOF必须顶格写,且其后不能有任何字符
说明:scale用于设置小数位位数。

4,关系运算

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

运算符 说明 举例(假设变量a=1、b=2)
-eq 检测两个数是否相等,相等返回true [ $a -eq $b ] 返回false
-ne 检测两个数是否不相等,不相等返回true [ $a -ne $b ] 返回true
-gt 检测左边的数是否大于右边的,如果是,则返回true [ $a -gt $b ] 返回false
-lt 检测左边的数是否小于右边的,如果是,则返回true [ $a -lt $b ] 返回true
-ge 检测左边的数是否大于等于右边的,如果是,则返回true [ $a -ge $b ] 返回false
-le 检测左边的数是否小于等于右边的,如果是,则返回true [ $a -le $b ] 返回true

5,布尔运算

运算符 说明 举例(假设变量a=1、b=2)
非运算,表达式为true 则返回false,否则返回true [ ! false ] 返回true
-o 或运算,有一个表达式为true 则返回true $a -lt 20 -o $b -gt 100 ] 返回true
-a 与运算,两个表达式都为true 才返回true [ $a -lt 20 -a $b -gt 100 ] 返回false
|| 或运算,有一个表达式为true 则返回true [[ $a -lt 100 && $b -gt 100 ]] 返回false
&& 与运算,两个表达式都为true 才返回true。 [[ $a -lt 100 || $b -gt 100 ]] 返回true

6,字符串运算

常用的字符串运算符如下表:

运算符 说明 举例(假设变量a=1、b=2)
= 检测两个字符串是否相等,相等返回true [ $a = $b ] 返回false
!= 检测两个字符串是否不相等,不相等返回true [ $a != $b ] 返回true
-z 检测字符串长度是否为0,为0返回true [ -z $a ] 返回false
-n 检测字符串长度是否不为0,不为0返回true [ -n "$a" ] 返回true
$ 检测字符串是否不为空,不为空返回true [ $a ] 返回true

7,文件测试

文件测试运算符用于检测Unix 文件的各种属性。

运算符 说明

-b file

检测文件是否是块设备文件,如果是,则返回true。

-c file

检测文件是否是字符设备文件,如果是,则返回true。

-d file

检测文件是否是目录,如果是,则返回true。

-f file

检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回true。

-g file

检测文件是否设置了SGID 位,如果是,则返回true。

-k file

检测文件是否设置了粘着位(Sticky Bit),如果是,则返回true。

-p file

检测文件是否是有名管道,如果是,则返回true。

-u file

检测文件是否设置了SUID 位,如果是,则返回true。

-r file

检测文件是否可读,如果是,则返回true。

-w file

检测文件是否可写,如果是,则返回true。

-x file

检测文件是否可执行,如果是,则返回true。

-s file

检测文件是否为空(文件大小是否大于0),不为空返回true。

-e file

检测文件(包括目录)是否存在,如果是,则返回true。

-S file

判断某文件是否socket。

-l file

检测文件是否存在并且是一个符号链接。

七、流程控制

1,if

如果if 后的那个命令的退出状态码是0(命令成功运行),位于then部分的命令就会被执行。

if command
then
commands
elif command
then
commands
fi

或者: 

if command; then
commands
elif command; then
commands
fi

举例: 

if [[ $s =~ ^[0-9]+$ ]]; then
echo number
elif [[ $s =~ ^[a-zA-Z]+$ ]]; then
echo letter
else
echo other
fi

2,case

case $var in
value1)
commands
;;
value2)
commands
;;
*)
commands
;;
esac

举例:

case $s in
a)
echo a
;;
b)
echo b
;;
*)
echo other
;;
esac

3,for

举例:

for file in ~/*; do
echo $file
done
for i in $(seq 1 100); do
echo $i
done
for (( a=1, b=10; a<=10; a++, b-- )); do
echo "$a - $b"
done

4,while 

var=10
while echo $var; [ $var -ge 0 ]; do
echo "This is inside the loop"
var=$[$var - 1]
done

5, until

var=100
until [ $var -eq 0 ]; do
echo $var
var=$[$var - 25]
done

6, break continue

break:跳出循环;

continue:提前中止某次循环中,但并不会完全终止整个循环。

举例:

for (( a = 1; a < 4; a++ )); do
echo "Outer loop: $a"
for (( b = 1; b < 10; b++ )); do
if [ $b -gt 4 ]; then
break 2
fi
echo "Inner loop: $b"
done
done

举例:

for (( a = 1; a < 4; a++ )); do
echo "Outer loop: $a"
for (( b = 1; b < 10; b++ )); do
if [ $b -gt 4 ]; then
break 2
fi
echo "Inner loop: $b"
done
done

八、函数

1,定义函数

function name {
commands
[return int]
}

 或者

name() {
commands
[return int]
}

 说明:
• 可用return 返回函数的返回值。如果不加,则以最后一条命令的运行结果作为返回值;
• 函数的执行结果通过标准输出返回(在函数中用echo 打印执行结果,用` 函数名参数列表` 或$(函数名参数列表)方式调用获取执行结果。

2,调用

调用函数:函数名[参数列表]。如定义如下函数:

function add {
echo $[$1 + $2]
}


可用add 1 2 调用。若要获得函数的输出可用`` 或$()来调用,如:

r=`add 1 2`

r=$(add 1 2)

3,返回值

函数返回值只能是整形,且范围为0~255。用来表示函数执行成功与否,0 表示成功,其他值表示失败。可用return 返回函数值,若返回值超过255 则从0 开始计算。若不存在return,则将函数最后一条命令的执行结果作为函数的返回值。可以在函数刚执行完后用$? 来获取函数的返回值。
备注:可以用echo 返回函数的输出。 

 4,参数定义和传递

Shell 脚本中定义函数时不能定义参数的参数名。在函数中直接用$1 取第一个参数的值,$2 取第二个参数的值,以此类推…

传递给函数参数时,直接将参数写在函数名后即可,多个参数用空格隔开;若参数值带有空格,需要用引号引起来;存在多个参数情况下传递数组参数,需将数组参数转成字符串传递,在函数中再将字符串转换成数组。

function print_arrays() {
while [ -n "$1" ]; do
local array=($1)
echo ${array[@]}
shift
done
}
array1=(1 2 3)
array2=(a b c)
print_arrays "${array1[*]}" "${array2[*]}"

5,使用变量

函数中存在两种变量:全局变量和局部变量。

全局变量是在脚本中任何地方都有效的变量。默认情况下,在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内访问,在函数内定义的变量也可在函数外访问。 

局部变量是在脚本局部有效的变量。函数内部使用的任何变量都可以被声明成局部变量。要实现这一点,只需在变量声明的前面加上local 关键字即可。

function aaa {
s1=a # 全局变量
local s2=b # 局部变量
}

6,创建库

Shell 中创建库分两步:
a. 编写库函数保存到文件;
b. 用source 命令导入库函数(source 命令有个快捷的别名,称作点操作符)。
如lib.sh 中定义了函数aa:

function aa {
echo "aa in lib.sh"
}


在test.sh 中使用如下方法调用函数:

source ./lib.sh # 导入lib.sh中的函数,可以简写为. ./lib.sh
aa # 调用函数aa

九、读写文件

通过echo 命令和输出重定向将内容写入文件;通过read 命令按行读取文件。示例如下:

echo -e "姓名,性别,年龄\n张三,男,20\n李四,女,30" > ./test.csv # 生成一个csv文件
while read line || [[ -n $line ]]; do
IFS=, # 设置分隔符为逗号
columns=($line) # 将行内容转换成数组
printf "%-10s%-10s%-10s\n" ${columns[@]} # 格式化输出
done < ./test.csv # 通过输入重定向使read命令从test.csv文件读取

注意:利用while read line 读取文件时,如果文件最后一行没有换行符,则read 读取到最后一行时遇到文件结束符EOF,循环立即终止,从而导致最后一行无法读取。当遇到此种情况时,可通过测试$line 是否有内容来判断是否继续。

十、并发执行

可以通过后台运行或命名管道方式实现并发执行。示例如下:

# 利用后台运行实现并发
for i in $(seq 1 10); do
{
echo $i
sleep 1 # 模拟程序、命令
}& # 把循环体放入后台运行,相当于起了个独立线程
done
wait # 等待上面放入后台的工作执行完毕
# 利用命名管道实现并发
process=5 # 指定最多并发5个进程
tmp_fifo=/tmp/$$.fifo # 使用当前pid来创建管道文件,放置名字冲突
mkfifo $tmp_fifo # 创建管道文件
exec 9<> $tmp_fifo # 打开文件,并且将文件描述符设置为9
rm $tmp_fifo # 删除刚刚的文件,不会影响文件描述符
for i in $(seq $process); do
echo >&9 # 向文件描述符9中传递5个空行
done
for i in {1..10}; do
read -u 9 # 读取管道的内容,每次读取一行
{
echo $i # 显示当前值
sleep 1 # 模拟程序、命令
echo >&9 # 程序运行结束后再向管道传递一个空行
}&
done
wait # 等待上面放入后台的工作执行完毕
exec 9<&- 9>&- # 释放文件描述符

十一、括号的用途

括号 用途
() • 命令分组:括号中的命令将会新开一个子shell顺序执行,如echo $BASH_SUBSHELL; (ls; echo $BASH_SUBSHELL)。
• 命令替换:等同于`cmd`,shell扫描命令行,发现了$(cmd)结构,便将$(cmd)中的cmd执行一次,得到其标准输出,再用此输出替换原来的命令结构。
• 初始化数组:如array=(a b c d)。
(()) • 整数运算:同[],如$((2*3))。
• 符合C语言运行规则的表达式运算:如$((16#5f))、$((i>0 ? 1 : 0))。
• 重新定义变量值:比如a=5; ((a++))可将a的值重定义为6。
• 算术比较:如$if ((i>3))、for ((i=0; i<5; i++))。
[] • bash内部命令,等同于test命令。
• 用于条件结构中,与运算用-a,或运算要用-o,如[ $i -gt 0 -a $i -lt 10 ],不支持&&、||、>、<等操作符。
[[]] • bash的关键字,比[]通用,其中的特殊字符(如*)不会比扩展。
• 支持字符串的模式匹配,使用=~操作符时支持正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串。如[[ hello == hell? ]]、
[[ hello =~ [a-z]+ ]]。
• 能够在其中正常使用&&、||、>、<等操作符,[]中则不支持。
{} • 字符扩展,如echo {a,b}.txt输出a.txt b.txt。
• 表示一段范围,如echo {0..5}输出0 1 2 3 4 5,echo {a..c}.txt输出a.txt b.txt c.txt。
• 表示一段代码块,格式为{ cmd1; cmd2; },相当于创建了一个匿名函数。和()包含的命令区别在于()会开子进程执行,而{}不会。
• 模式匹配截取,如${s#*aa}。
• 字串截取,如${s:2}。

你可能感兴趣的:(LINUX虚拟机,linux,android)