SHELL 脚本编程基础

116 篇文章 17 订阅
6 篇文章 1 订阅

文章目录


1. shell 概述

1.1 shell 和 shell 脚本

  shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务,在用户和内核之间充当翻译官的角色,是一个命令解释器。Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 shell。
在这里插入图片描述
  shell 脚本(shell script),是一种为 shell 编写的脚本程序。业界所说的 shell 通常都是指 shell 脚本,shell 和 shell script 是两个不同的概念。

1.2 Shell 脚本应用场景

  • 重复性操作
  • 交互性任务
  • 批量事务处理
  • 服务运行状态监控
  • 定时任务执行

1.3 Linux 中的 shell 类型

以 CentOS 7 为例

[root@c7-1 ~]#cat /etc/shells
/bin/sh			#/bin/sh 是 bash 命令的软链接(已经被 /bin/bash 所替换)
/bin/bash		#基准于 GNU 的框架下发展出的 shell
/usr/bin/sh		#己经被 bash 所替换
/usr/bin/bash	#centos 和 redhat 系统默认使用 bash shell
/bin/tcsh		#csh 的增强版,与 csh 完全兼容,提供更多的功能
/bin/csh		#已经被 /bin/bash 所替换(整合 c shell,提供更多的功能)
/sbin/nologin	#奇怪的 shel1,这个 shell 可以让用户无法登录主机

1.4 脚本实例

[root@c7-1 ~]#curl -s http://47.117.130.238/hello.sh
# ------------------------------------------
# Filename: hello.sh
# Version: 1.0
# Date: 2021/06/06
# Author: gongboyi
# Email: 1520509800@qq.com
# Website: https://www.cnblogs.com/shenyuanhaojie/
# Description: This is the first script
# Copyright: 2021 zhou
# License: GPL
# ------------------------------------------
echo "hello world!"

[root@c7-1 ~]#curl -s http://47.117.130.238/hello.sh|bash
hello world!

2. shell 脚本编程基础

2.1 脚本调试

只检测脚本中的语法错误,无法检查出命令错误,不真正执行脚本

bash -n /path/to/script.sh

调试并执行

bash -x /path/to/script.sh

脚本错误常见的有三种:

语法错误:会导致后续的命令不继续执行,可以用 bash -n 检查错误,提示的出错行数不一定是准确的。
命令错误:后续的命令还会继续执行,用 bash -n 无法检查出来 ,可以使用 bash -x 进行观察。
逻辑错误:只能使用 bash -x 进行观察。

2.2 脚本执行方法

相对路径执行( ./script.sh )在脚本当前目录,脚本需要执行权限
绝对路径执行( /PATH/to/script.sh )无需在脚本目录,脚本需要执行权限
bash 执行( bash /PATH/to/script.sh )bash 后可跟绝对路径和相对路径,脚本无需执行权限
source 执行( source /PATH/to/script.sh )source 后可跟绝对路径和相对路径,脚本无需执行权限
bash < script.sh 或 cat script.sh | bash

示例:

[root@aliyunhost01html]#./hello.sh 
hello world!
[root@aliyunhost01html]#/var/www/html/1.sh 
hello world
[root@aliyunhost01html]#bash /var/www/html/1.sh
hello world
[root@aliyunhost01html]#source /var/www/html/1.sh
hello world
[root@aliyunhost01html]#bash < /var/www/html/1.sh
hello world
[root@aliyunhost01html]#cat /var/www/html/1.sh | bash
hello world

2.3 管道符号 |

将左侧命令的输出结果作为右侧命令的处理对象。
示例:

[root@c7-1 ~]#grep "/sbin/nologin" /etc/passwd | awk -F: '{print $1,$7}'
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
mail /sbin/nologin
......

2.4 交互式硬件设备

标准输入:从该设备接收用户输入的数据
标准输出:通过该设备向用户输出数据
标准错误:通过该设备报告执行出错信息
类型设备文件文件描述编号默认设备
标准输入/dev/stdin0键盘
标准输出/dev/stdout1显示器
标准错误输出/dev/stderr2显示器
[root@c7-1 ~]#ll /dev/std*
lrwxrwxrwx 1 root root 15 92 10:02 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 92 10:02 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 92 10:02 /dev/stdout -> /proc/self/fd/1

2.5 重定向操作

类型操作符用途
重定向输入<从指定的文件读取数据,而不是从键盘输入
重定向输出>将输出结果保存到指定的文件(覆盖原有内容)
>>将输出结果追加到指定的文件尾部
标准错误输出2>将错误信息保存到指定的文件(覆盖原有内容)
2>>将错误信息追加到指定的文件中
混合输出&>将标准输出、标准错误的输出保存到同一个文件中

示例:

#将 ./test.sh 的输出重定向到 log.txt 文件中,同时将标准错误也重定向到 log.txt 文件中
./test.sh > log.txt 2>&1

# nohup ... & 代表后台运行并且生成 nohup.log 日志文件
# command>/dev/null 代表命令输出结果导入到空设备
# 2>&1 代表将标准错误的内容重定向到标准输出,即将程序运行中的错误信息也打印出来
nohup ./test.sh>/dev/null 2>&1 &

参考:重定向理解

2.6 变量

(1) 变量的赋值与引用

① 变量赋值
name='value'

value 可以是以下多种形式:

直接字串:name='root'
变量引用:name="$USER"
命令引用:name=`COMMAND` 或者 name=$(COMMAND)

变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量随着脚本结束,也会自动删除。

使用 read 从标准输入读取数值:

read -p "提示信息" [变量名]
read -p "提示信息" [变量名] < [文件]

常用选项:

-p   #指定要显示的提示信息
-s   #静默输入,一般用于密码
-n N #指定输入的字符长度 N
-d '字符'	#输入结束符
-t N 	    #TIMEOUT 为 N 秒

示例:

[root@c7-1 ~]#read -p "请输入密码:" passwd
请输入密码:120604
[root@c7-1 ~]#echo $passwd
120604

[root@c7-1 ~]#vim IP.txt
[root@c7-1 ~]#read -p "请输入IP地址:" IP < IP.txt
[root@c7-1 ~]#echo $IP
47.117.130.238
[root@c7-1 ~]#cat IP.txt 
47.117.130.238

#实现运维工作菜单
echo -en "\E[$[RANDOM%7+31];1m"
cat <<EOF
请选择:
1)备份数据库
2)清理日志
3)软件升级
4)软件回滚
5)删库跑路
EOF
echo -en '\E[0m'
read -p  "请输入上面数字1-5: " MENU
[ $MENU -eq 1 ] && ./backup.sh
[ $MENU -eq 2 ] && echo "清理日志"
[ $MENU -eq 3 ] && echo "软件升级"
[ $MENU -eq 4 ] && echo "软件回滚"
[ $MENU -eq 5 ] && echo "删库跑路"
② 变量引用
" "    弱引用,允许通过 $ 符号引用其他变量值
' '    强引用,禁止引用其他变量值,$ 视为普通字符
` `    命令引用,提取命令执行后的输出结果

示例:

[root@c7-1 ~]#username=zc
[root@c7-1 ~]#echo "you are $username"
you are zc
[root@c7-1 ~]#echo 'you are $username'
you are $username
[root@c7-1 ~]#echo `whoami`
root

(2) 变量类型

  • 自定义变量:由用户自己定义、修改、使用,只在用户自己的 shell 环境中有效,因此又称为本地变量
  • 特殊变量:环境变量、只读变量、位置变量、预定义变量
① 自定义变量

基本格式:

变量名=变量值

示例:

[root@c7-1 ~]#username=zc
[root@c7-1 ~]#echo $username
zc
[root@c7-1 ~]#set | grep username	#set 显示用户自定义的变量
username=zc
        local remote_opts="--username= --config-dir= --no-auth-cache";
				--no-auth-cache --username=
[root@c7-1 ~]#unset username
[root@c7-1 ~]#set | grep username
_=username
        local remote_opts="--username= --config-dir= --no-auth-cache";
				--no-auth-cache --username=
② 环境变量

环境变量分为:

  • 全局变量:不仅对 shell 可见,对其子进程也可见
  • 局部变量:只能在定义其的进程中可见

显示所有环境变量:

env
printenv
export
declare -x

Bash 内建的环境变量:

PATH
SHELL
USER
UID
HOME
PWD
SHLVL	#shell 的嵌套层数
LANG
MAIL
HOSTNAME
HISTSIZE
_ 		#下划线,表示前一命令的最后一个参数

声明全局变量:

export [变量名]
declare -x [变量名]

声明的全局变量虽然在环境变化的时候仍然生效,但是当系统重启后不会保存,要想永久生效可以保存到配置文件中。

变量配置文件:

系统层面:/etc/profile、/etc/bashrc
用户层面:~/.bash_profile、~/.bashrc、~/.bash_history、~/.bash_logout
#系统层面的配置文件通常在登录时加载,用户层面的配置文件只对单个用户生效

参考:Linux环境变量配置全攻略

③ 只读变量

只读变量的声明:

readonly [变量名]
declare -r [变量名]

查看:

readonly [-p]
declare -r

只读变量无法删除或更改,当退出登录或者重启系统会失效。

示例:

[root@c7-1 ~]#readonly PI=3.14
[root@c7-1 ~]#echo $PI
3.14
[root@c7-1 ~]#PI=3.14159
-bash: PI: 只读变量
[root@c7-1 ~]#unset PI
-bash: unset: PI: 无法反设定: 只读 variable
[root@c7-1 ~]#logout

Connection closed by foreign host.

Disconnected from remote host(c7-1) at 21:53:33.

Type `help' to learn how to use Xshell prompt.
[c:\~]$ 
Reconnecting in 5 seconds. Press any key to exit local shell.
...

Connecting to 192.168.10.20:22...
Connection established.
To escape to local shell, press Ctrl+Alt+].

Last login: Fri Sep  3 21:33:20 2021 from 192.168.10.1
[root@c7-1 ~]#echo $PI

[root@c7-1 ~]#
④ 位置变量

在 bash shell 中内置的变量,在脚本代码中调用通过命令行传递给脚本的参数。

$1, $2, ... 对应第1个、第2个等参数,shift [n] 换位置
$0 脚本名称
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
注意:$@ $* 只在被双引号包起来的时候才会有差异

清空所有位置变量:

set --

示例:

[root@c7-1 ~]#cat test.sh 
echo $1 $2 $3
echo $0
echo $?
echo $$
echo $!
echo $#
echo $*
echo $@
[root@c7-1 ~]#bash test.sh a b c
a b c
test.sh
0
72999

3
a b c
a b c

#实现添加用户
[root@c7-1 ~]#cat useradd.sh
#!/bin/bash
useradd $1
echo $2 | passwd --stdin $1
[root@c7-1 ~]#bash useradd.sh zc 123456
更改用户 zc 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@c7-1 ~]#cat /etc/passwd|grep zc
zc:x:1001:1001::/home/zc:/bin/bash

#删库跑路之命令 rm 的安全实现
WARNING_COLOR="echo -e \E[1;31m"
END="\E[0m"
DIR=/tmp/`date +%F_%H-%M-%S`
mkdir $DIR
mv  $*  $DIR
${WARNING_COLOR}Move $* to $DIR $END
⑤ 预定义变量

  预定义变量是在 Shell 一开始时就定义的变量,这一点和默认环境变量有些类似。不同的是,预定义变量不能重新定义,用户只能根据 Shell 的定义来使用这些变量。严格来说,位置参数变量也是预定义变量的一种,只是位置参数变量的作用比较统一,所以我们把位置参数变量单独划分为一类数量。

$? 表示前一条命令执行后的返回状态,返回值为О表示执行正确,返回任何非О值均表示执行出现异常
$$ 表示返回当前进程的进程号
$! 返回最后一个后台进程的进程号

用户可以在脚本中使用以下命令自定义退出状态码:

exit [n]
#脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
#如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

示例:

#测试网络通信
[root@c7-1 ~]#ping -c1 -W1 47.117.130.238 &> /dev/null
[root@c7-1 ~]#echo $?
0

#测试服务端口
[root@aliyunhost01~]#curl 127.0.0.1:80 &> /dev/null
[root@aliyunhost01~]#echo $?
0

(3) 变量的查看与删除

① 查看已定义的所有变量
set
② 删除变量
unset [变量名]
#只读变量无法删除

(4) 变量的赋值运算

+	加
-	减
\*	乘
/	除
% 	取模,即取余数,示例:9%4=1,5%3=2
** 	乘方

示例:

[root@c7-1 ~]#expr 2 + 3
5
[root@c7-1 ~]#num1=2
[root@c7-1 ~]#num2=3
[root@c7-1 ~]#expr $num1 + $num2
5
[root@c7-1 ~]#expr $num2 / $num1
1
[root@c7-1 ~]#expr $num1 \* $num2
6
[root@c7-1 ~]#expr $num2 % $num1
1
[root@c7-1 ~]#sum=$(($num1**$num2))
[root@c7-1 ~]#echo $sum
8

2.7 算数运算

(1) 实现算数运算的方式

let var=算术表达式
((var=算术表达式))
var=$[算术表达式]
var=$((算术表达式))
var=$(expr arg1 arg2 arg3 ...)
declare -i var = 数值
echo '算术表达式' | bc 

(2) 增强型赋值

+=	# i+=j	相当于 i=i+j
-= 	# i-=j	相当于 i=i-j
*=	# i*=j	相当于 i=i*j 
/=  # i/=j	相当于 i=i/j
%=	# i%=j	相当于 i=i%j	
++  # i++,++i   相当于 i=i+1
--  # i--,--i   相当于 i=i-1

说明:i++ 和 ++i,i-- 和 --i 含义是不同的,我们用具体的示例来解释

# i 先将值赋值给了 j ,然后再完成自加
[root@c7-1 ~]#unset i j; i=1; let j=i++; echo "i=$i,j=$j"
i=2,j=1
# i 完成自加后赋值给 j
[root@c7-1 ~]#unset i j; i=1; let j=++i; echo "i=$i,j=$j"
i=2,j=2

(3) 逻辑运算

布尔型变量bool的取值只有false和true
true	1
false	0

& 与

 11 = 1
 10 = 0
 01 = 0
 00 = 0

| 或

 11 = 1
 10 = 1
 01 = 1
 00 = 0 

! 非

! 1 = 0
! 0 = 1

^ 异或

相同为假,不同为真
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0 

&& 短路与

CMD1 短路与 CMD2
第一个CMD1结果为真(1),第二个CMD2必须要参与运算,才能得到最终的结果
第一个CMD1结果为假(0),总的结果必定为0,因此不需要执行CMD2

|| 短路或

CMD1 短路或 CMD2
第一个CMD1结果为真(1),总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为假(0),第二个CMD2必须要参与运算,才能得到最终的结果

(4) 随机数生成器变量 – $RANDOM

bash 有内建的随机数生成器:$RANDOM(0-32767)

#生成 0-49 之间随机数
echo $[$RANDOM%50]

#更换字体颜色,每次输出的结果显示不同的颜色
echo -e "\E[1;$[RANDOM%7+31]m hello \e[0m"

(5) “空” 设备 – /dev/null

“空” 设备,也被称为黑洞。任何输入到这个设备的数据都将被直接丢弃(但是操作返回成功 $? 值为 0)。最常用的用法是把不需要的输出重定向到这个文件。例如:

#将标准输出和错误输出重定向到 /dev/null,运行这个脚本不会输出任何信息到终端
run.sh 1>/dev/null 2>&1  

(6) “零” 设备 – /dev/zero

“零” 设备,可以无限的提供空字符(0x00,ASCII代码NUL)。常用来生成一个特定大小的文件。例如:

#产生一个 1k 大小的文件 output.txt
dd if=/dev/zero of=./output.txt bs=1024 count=1 

(7) 随机数生成器设备 – /dev/random /dev/urandom

随机数设备,提供不间断的随机字节流。二者的区别是 /dev/random 产生随机数据依赖系统中断,当系统中断不足时,/dev/random 设备会 “挂起”,因而产生数据速度较慢,但随机性好;/dev/urandom 不依赖系统中断,数据产生速度快,但随机性较低。

#生成随机密码
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;
openssl rand -base64 32
date +%s | sha256sum | base64 | head -c 32 ; echo
tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1
strings /dev/urandom | grep -o '[[:alnum:]]' | head -n 30 | tr -d '\n'; echo
dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev
date | md5sum

参考:Linux 中的虚拟设备

2.8 echo -n/-e 的用法

echo -n 不换行输出

[root@c7-1 ~]#echo "123456"
123456
[root@c7-1 ~]#echo -n "123456"
123456[root@c7-1 ~]#

echo -e 处理特殊字符

若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出
\a 发出警告声
\b 删除前一个字符
\c 最后不加上换行符号
\f 换行但光标仍旧停留在原来的位置
\n 换行且光标移至行首
\r 光标移至行首,但不换行
\t 插入tab
\v 与\f相同
\\ 插入\字符
\nnn 插入nnn(八进制)所代表的ASCII字符

参考:
echo -n/-e 用法
echo 命令详解
echo 显示内容带颜色

2.9 条件测试命令

判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程。

若真,则状态码变量 $? 返回 0
若假,则状态码变量 $? 返回 1

条件测试格式:

test <选项> <测试的内容>
[ 选项 测试的内容 ]
[[ 选项 测试的内容 ]]

常用测试选项:

-e	测试目录或文件是否存在,[ -a file ] 等于 [ -e file ]
-d	测试是否为目录
-f	测试是否为文件
-r	测试当前用户是否有权限读取
-w	测试当前用户是否有权限写入
-x	测试当前用户是否有权限执行
-u 	测试文件是否存在且拥有 suid 权限
-g	测试文件是否存在且拥有 sgid 权限
-k	测试文件是否存在且拥有 sticky 权限
-z	如果 STRING 的长度为零则为真
-n	如果 STRING 的长度非零则为真
-b	测试文件是否存在并且是块设备文件
-c	测试文件是否存在并且是字符设备文件
-L	测试文件是否存在并且是链接文件
-p	测试文件是否存在并且是管道文件
-S	测试文件是否存在并且是套接字文件
-s	测试文件是否存在并且文件大小为空

其他文件属性测试选项:

-t fd 		fd 文件描述符是否在某终端已经打开
-N FILE 	文件自从上一次被读取之后是否被修改过
-O FILE 	当前有效用户是否为文件属主
-G FILE 	当前有效用户是否为文件属组
FILE1 -ef FILE2		FILE1 是否是 FILE2 的硬链接
FILE1 -nt FILE2 	FILE1 是否新于 FILE2(mtime)
FILE1 -ot FILE2 	FILE1 是否旧于 FILE2

字符串比较:

[ STRING1 = STRING2 ]	两个字符串相等为真
[ STRING1 != STRING2 ]	两个字符串不等为真                    
[ STRING1 < STRING2 ]	如果 STRING1 按字典顺序排在 STRING2 之前,则为真
[ STRING1 > STRING2 ]	如果 STRING1 按字典顺序排在 STRING2 之后,则为真
[[ STRING1 == STRING2 ]]	左侧字符串是否和右侧的 PATTERN 相同
[[ STRING1 =~ STRING2 ]]	左侧字符串是否能够被右侧的正则表达式的 PATTERN 所匹配

逻辑运算与、或、非(条件组合测试):

[ ! EXPR ]	逻辑非,与表达相反的结果为真
[ EXPR1 -a EXPR2 ]	逻辑与,要同时满足多个表达式才为真,等同于 &&
[ EXPR1 -o EXPR2 ]	逻辑或,满足其中任意一个表达式即为真,等同于 ||
#如果 && 和 || 混合使用,&& 要在前,|| 在后

变量测试:

-v VAR	变量 VAR 是否设置
-r VAR	变量 VAR 是否设置并引用

数值比较:

-eq	是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于

2.10 流程控制

(1) if 条件语句

格式:

if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else
COMMANDS; ] fi

单分支:

if	判断条件;then
	条件为真的分支代码
fi

双分支:

if	判断条件;then
	条件为真的分支代码
else
	条件为假的分支代码
fi

多分支:

if	判断条件1;then
	条件1为真的分支代码
elif	判断条件2;then
	条件2为真的分支代码
elif	判断条件3;then
	条件3为真的分支代码
...
else
	以上条件都为假的分支代码
fi

示例:

###单分支
[root@c7-1 ~]#cat if1.sh 
#!/bin/bash
if ping -c1 -w2 www.baidu.com &> /dev/null;then
	echo "网络连通"
fi
[root@c7-1 ~]#bash if1.sh 
网络连通

###双分支
[root@c7-1 ~]#cat if2.sh
#!/bin/bash
IP=47.117.130.238
if ping -c1 -i 0.2 -w2 $IP &> /dev/null;then
	echo "主机在线"
else 
	echo "主机断联"
fi
[root@c7-1 ~]#bash if2.sh
主机在线

###多分支
[root@c7-1 ~]#cat if3.sh
#!/bin/bash
read -p "请输入分数:" GRADE
if [ $GRADE -le 100 -a $GRADE -ge 80 ];then
	echo "成绩优秀"
elif [ $GRADE -lt 80 -a $GRADE -ge 60 ];then
	echo "成绩合格"
else 
	echo "不及格"
fi
[root@c7-1 ~]#bash if3.sh
请输入分数:65
成绩合格

(2) case 多分支语句

格式:

case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac

分支结构:

case 变量值 in
模式一)
      命令序列
;;
模式二)
      命令序列
;;
模式三)
     命令序列
;;
......
*)
     默认命令序列
esac                 

case 支持 glob 风格的通配符:

*	任意长度任意字符
?	任意单个字符
[]	指定范围内的任意单个字符
|	逻辑或

示例:

###判断输入yes或no
[root@c7-1 ~]#cat YESorNO.sh
#!/bin/bash
read -p "请输入YES或NO:" VAR
VAR=`echo $VAR | tr 'A-Z' 'a-z'`
case $VAR in
y|ye|ys|yes)
	echo "你输入的是YES"
	;;
n|no)
	echo "你输入的是NO"
	;;
*)
	echo "输入错误,请重新输入YES或NO"
esac
[root@c7-1 ~]#bash YESorNO.sh
请输入YES或NO:y
你输入的是YES

###httpd 服务控制
[root@c7-1 ~]#cat httpd.sh 
#!/bin/bash
case $1 in
start)
/usr/bin/systemctl $1 httpd
/usr/bin/ps aux | grep httpd
echo "httpd start"
;;
stop)
/usr/bin/systemctl $1 httpd
/usr/bin/ps aux | grep httpd
echo "httpd stop"
;;
restart)
echo "httpd stop......"
/usr/bin/ps aux | grep httpd
/usr/bin/systemctl $1 httpd
echo "httpd restart......"
/usr/bin/ps aux | grep httpd
;;
status)
/usr/bin/systemctl $1 httpd
;;
*)
echo "请输入 start|stop|restart|status"
esac
[root@c7-1 ~]#./httpd.sh status | grep active
   Active: active (running) since 一 2021-09-06 11:20:30 CST; 35s ago

(3) for 循环

格式:

#格式一
for 变量名 in 取值列表;
do
 循环体
done

#格式二
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
 循环体
done

示例:

#!/bin/bash
for i in `seq 10`
do 
        echo $i
done
————————————————————
#!/bin/bash
#输出 10 次 hello world
a=10
for i in `seq $a`
do
        echo "hello world"
done
————————————————————
#!/bin/bash
#输出 0-10,间隔 2
for i in {0..10..2}
do
        echo $i
done
————————————————————
#!/bin/bash
for ((i=1;i<=5;i++))
do 
	echo $i
done
————————————————————
#!/bin/bash
#批量创建用户并修改密码
for i in {1..5}
do
        useradd user$i
        echo "123456" | passwd --stdin user$i
done
————————————————————
#!/bin/bash
#根据用户列表文件批量创建用户并修改密码
ULIST=$(cat /opt/user.txt)

for UNAME in $ULIST
do
	useradd $UNAME
	echo "123456"|passwd --stdin $UNAME &> /dev/null
done
————————————————————
#!/bin/bash
#根据用户列表文件删除用户
ULIST=$(cat /opt/user.txt)

for UNAME in $ULIST
do
	userdel -r $UNAME &> /dev/null
done
————————————————————
#!/bin/bash
#测试文件中的 IP 是否连通
IPLIST=$(cat /opt/ipaddrs.txt)

for IP in $IPLIST
do
        ping -c 3 -i 0.2 -W 3 $IP &> /dev/null
        if [ $? -eq 0 ];then
                echo "Host $IP is up"
        else
                echo "Host $IP is down"
        fi
done
————————————————————
#!/bin/bash
#测试 IP 网段
for i in {1..255}
do
        ping -c 2 -i 0.1 -W 1 192.168.10.$i &>/dev/dull
        if [ $? -eq 0 ]
        then
                echo "192.168.10.$i is up"
        else
                echo "192.168.10.$i is down"
        fi
done
————————————————————
#!/bin/bash
#密码登录与确认
init=123456

for i in {1..3}
do
	read -p "请输入免密:" pass
	if [ $pass == $init ];then
		echo "密码正确"
			exit
	fi
done

echo "密码错误"
————————————————————
#!/bin/bash
#九九乘法表一
for i in {1..9};do
 for j in `seq $i`;do
 echo -e "${j}x${i}=$[i*j]\t\c"
 done
 echo
done
————————————————————
#!/bin/bash
#九九乘法表二
for ((i=1;i<10;i++));do
   for((j=1;j<=i;j++));do
       echo -e "${j}x${i}=$[i*j]\t\c"
   done
   echo
done
————————————————————
#!/bin/bash
#生成菱形
read -p "请输入菱形行数:" RM
for ((i=1;i<=$RM;i++))
do
	for ((j=$RM;j>$i;j--))
	do
		echo -n " "
	done
	for ((n=1;n<=$i;n++))
	do
		echo -n "* "
	done
		echo ""
done

for ((k=$RM-1;k>=1;k--))
do
	for ((m=$RM;m>$k;m--))
	do
		echo -n " "
	done
	for ((l=1;l<=$k;l++))
	do
		echo -n "* "
	done
		echo ""
done

(4) while 循环

格式:

while CONDITION; 
do
	循环体
done

说明:
CONDITION:循环控制条件,进入循环之前,先做一次判断,每一次循环之后会再次做判断。条件为 “true” 则执行一次循环,直到条件测试状态为 “false” 终止循环,因此:CONDTION 一般应该有循环控制变量,而此变量的值会在循环体不断地被修正。
进入条件:CONDITION 为 true
退出条件:CONDITION 为 false

示例:

#!/bin/bash
i=1
while [ $i -le 5 -a $i -ge 0 ]
do
        echo $i 
        let i++
done
echo -e "\033[31m最后的i值为:$i\033[0m"
echo -e "\033[31m循环结束\033[0m"
————————————————————
#!/bin/bash
#httpd 服务控制
while pgrep "httpd" &> /dev/null
do
        echo -e "\033[32mhttpd 服务正在运行\033[0m"
        sleep 2
done
        echo -e "\033[31mhttpd 服务异常\033[0m" 
        echo -e "\033[32m安装并启动 httpd 服务......\033[0m"
        yum -y install httpd &>/dev/null
        systemctl start httpd &>/dev/null && systemctl enable httpd &> /dev/null
        systemctl status httpd
————————————————————
#!/bin/bash
#猜数字,猜对了通过 break 退出循环
NUM=8
while true
do
	read -p "请输入数字:" SHUZI
	if [ $SHUZI -eq $NUM ];then
		echo "猜对了"
		break
	elif [ $SHUZI -gt $NUM ];then
		echo "猜大了"
	elif [ $SHUZI -lt $NUM ];then
		echo "猜小了"
	fi
done
————————————————————
#无限循环,break 退出
while true; 
do
	循环体
done

while true; 
do
	循环体
	break
done

while 循环实现磁盘超载邮件告警:

#配置发邮件的邮箱
[root@centos8 ~]#cat .mailrc
set from=1520509800@qq.com
set smtp=smtp.qq.com
set smtp-auth-user=1520509800@qq.com
set smtp-auth-password=esvnhbnqocirbicf
set smtp-auth=login
set ssl-verify=ignore

#告警脚本
[root@centos8 ~]#cat while_diskcheck.sh
WARNING=80
while :;do
   USE=`df | sed -rn '/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p' |sort -nr|head -n1`
   if [ $USE -gt $WARNING ];then
      echo Disk will be full from `hostname -I` | mail -s "disk warning" 1520509800@qq.com
   fi
   sleep 10
done

(5) until 循环

重复测试某个条件,只要条件不成立则反复执行。

格式:

until CONDITION;
do
	循环体
done

说明:

进入条件:CONDITION 为 false
退出条件:CONDITION 为 true

无限循环:

until false;
do
	循环体
done

示例:

#!/bin/bash
i=1
sum=0

until [ $i -eq 10 ]
do
	sum=$[$i+$sum]
	let i++
done

echo $sum 

(6) 循环控制语句 continue

continue [N]:提前结束第 N 层的本轮循环,而直接进入下一轮判断,最内层为第 1 层。

格式:

while CONDITION1; 
do
	CMD1
	...
	if CONDITION2; then
	continue
	fi
	CMDn
	...
done

示例:

#!/bin/bash
for ((i=0;i<10;i++));
do
	for((j=0;j<10;j++));
	do
		[ $j -eq 5 ] && continue 2
		echo $j
	done
	echo ---------------------------
done

参考:continue-break

(7) 循环控制语句 break

break [N]:提前结束第 N 层整个循环,最内层为第 1 层。

格式:

while CONDITION1; 
do
	CMD1
	...
	if CONDITION2;then
 		break
 	fi
 	CMDn
 	...
done

示例:

#!/bin/bash
#可测试 break 和 break 2 输出结果有什么不同
#break 退出单个循环,break 2 退出 2 层循环
for ((i=0;i<10;i++));
do
	for((j=0;j<10;j++));
	do
		[ $j -eq 5 ] && break 2
   		echo $j
 	done
 	echo ---------------------------
done

参考:continue-break

(8) 循环控制 shift 命令

shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift。

示例:

#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
	echo "$1"
	shift
done
echo
 
./shift.sh a b c d e f g h
————————————————————
#!/bin/bash
if [ $# -eq 0 ];then
    echo "Usage: `basename $0` user1 user2 ..."
    exit
fi
                                                                                                                                                                        
while [ "$1" ];do
	if id $1 &> /dev/null;then
        echo $1 is exist
    else
        useradd $1
        echo "$1 is created"
    fi
    shift
done
echo "All user is created"

(9) while read 特殊用法

while 循环的特殊用法,遍历文件或文本的每一行。

格式:

while read line;
do
	循环体
done < /PATH/FROM/FILE

示例:

#!/bin/bash
WARNING=80
MAIL=root@gongboyi.com
df|sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p"|while read DEVICE USE;
do
	if [ $USE -gt $WARNING ] ;then
    	echo "$DEVICE will be full,use:$USE" | mail -s "DISK WARNING" $MAIL
 	fi
done

(10) select 循环与菜单

格式:

select variable in list;
do
	循环体命令
done

示例:

#!/bin/bash
#----------
sum=0
PS3="请点菜(1-6): "
select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束;do
case $REPLY in
1)
 echo $MENU 价格是 100
 let sum+=100
 ;;
2)
 echo $MENU 价格是 88
 let sum+=88
 ;;
3)
 echo $MENU价格是 66
 let sum+=66
 ;;
4)
 echo $MENU 价格是 166
 let sum+=166
 ;;
5)
 echo $MENU 价格是 200
 let sum+=200
 ;;
6)
 echo "点菜结束,退出"
 break
 ;;
*)
 echo "点菜错误,重新选择"
 ;;
esac
done
echo "总价格是: $sum"

2.11 Shell 函数

(1) 函数介绍

  函数 function 是由若干条 shell 命令组成的语句块,实现代码重用和模块化编程。它与 shell 程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分。
  shell 程序在子 shell 中运行,而 shell 函数在当前 shell 中运行。因此在当前 shell 中,函数可对 shell 中变量进行修改。
  函数由两部分组成:函数名和函数体。

(2) 函数管理

① 定义函数

语法一:

func_name () {
	...函数体...
}

语法二:

function func_name {
	...函数体...
}

语法三:

function func_name () {
	...函数体...
}
② 查看函数
#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#指定查看当前已定义的函数名
declare -F func_name
#指定查看当前已定义的函数名定义
declare -f func_name
③ 删除函数
unset func_name

(3) 函数调用

函数的调用方式:

可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中

函数的生命周期:

被调用时创建,返回时终止

调用的概念:

函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数
代码。
① 交互式环境调用函数
[root@c7-1 ~]#dir () {
> ls -l
> }
[root@c7-1 ~]#dir
总用量 4
-rw-r--r-- 1 root root 187 98 17:47 1.sh
② 在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至 shell 首次发现它后才能使用,调用函数仅使用其函数名即可。

#!/bin/bash
function fun () {
	echo "hello world!"
}
#通过函数名调用函数
fun
————————————————————
#!/bin/bash
function test {
	read -p "请输入数字:" num
	return $[$num*2]
}
test
echo $?
————————————————————
#!/bin/bash
sum () {
read -p "请输入第一个参数:" NUM1
read -p "请输入第二个参数:" NUM2
echo -e "\033[32m你输入的两个数为:$NUM1$NUM2\033[0m"
SUM=$(($NUM1+$NUM2))
echo -e "\033[31m两个数之和为:$SUM\033[0m"
}
sum
—————————————————————
#!/bin/bash
#函数调用函数
# local 一般用于局部变量声明
fa () {
        if [ $1 -eq 1 ];then
                echo 1
        else
                local tp=$[ $1 - 1 ]
                local res=$(fa $tp)
                echo $[ $1 * $res ]
        fi
}

read -p "请输入:" num
res=$(fa $num)
echo $res
————————————————————
#!/bin/bash
#修改本地 repo 源
#请事先挂载光盘镜像,VMware设置里选择已连接
function repobackup {
cd /etc/yum.repos.d
mkdir repo.bak
mv *.repo repo.bak
mount /dev/sr0 /mnt > /dev/null
}

makelocalrepo () {
echo -e '
[local]
name=local
baseurl=file:///mnt
enabled=1
gpgcheck=0' > local.repo
}

uselocalrepo () {
yum clean all > /dev/null
yum makecache > /dev/null
yum -y install httpd > /dev/null
systemctl start httpd
}

#-----main-----#
repobackup
makelocalrepo
uselocalrepo
③ 使用函数文件
  • 可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入 shell,再进行调用函数
  • 文件名可任意选取,但最好与相关任务有某种联系,例如:functions
  • 一旦函数文件载入 shell,就可以在命令行或脚本中调用函数。可以使用 delcare -f 或 set 命令查看所有定义的函数,其输出列表包括已经载入 shell 的所有函数
  • 若要改动函数,首先用 unset 命令从 shell 中删除函数,改动完毕后,再重新载入此文件

实现函数文件的过程:

创建函数文件,只存放函数的定义
在 shell 脚本或交互式 shell 中调用函数文件,格式如下:
. filename 或 source filename

示例:

[root@centos8 ~]#cat functions
#!/bin/bash
#functions
hello () {
    echo Run hello Function
}
hello2 () {
    echo Run hello2 Function
}
[root@centos8 ~]#. functions
[root@centos8 ~]#hello
Run hello Function
[root@centos8 ~]#hello2
Run hello2 Function
[root@centos8 ~]#declare -f hello hello2
hello ()
{
    echo Run hello Function
}
hello2 ()
{
    echo Run hello2 Function
}

(4) 函数返回值

函数的执行结果返回值:

使用 echo 等命令进行输出
函数体中调用命令的输出结果

函数的退出状态码:

默认取决于函数中执行的最后一条命令的退出状态码
自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回

(5) 环境函数

类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数。
定义环境函数:

export -f function_name
declare -xf function_name

查看环境函数:

export -f
declare -xf

(6) 函数参数

函数可以接受参数:

传递参数给函数,在函数名后面以空白分隔给定参数列表即可,如:func arg1 arg2 ...
在函数体中当中,可使用$1, $2, ...调用这些参数。还可以使用$@, $*, $#等特殊变量

(7) 函数变量

变量作用域:

普通变量:只在当前 shell 进程有效,为执行脚本会启动专用子 shell 进程。因此,本地变量的作用范围是当前 shell 脚本程序文件,包括脚本中的函数
环境变量:当前 shell 和子 shell 有效
本地变量:函数的生命周期结束时变量被自动销毁

注意:

如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量

在函数中定义本地变量的方法:

local NAME=VALUE

(8) 函数递归

函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环。
递归示例:

阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有 0 的阶乘为 1,自然数 n 的阶乘写作 n!
n!=1×2×3×...×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!

示例:

#!/bin/bash
#
fact() {
    if [ $1 -eq 0 -o $1 -eq 1 ]; then
 echo 1
    else
 echo $[$1*$(fact $[$1-1])]
    fi
}
fact $1

  fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源。
参考:
fork 炸弹
函数实现:

:(){ :|:& };:
 bomb() { bomb | bomb & }; bomb

脚本实现:

cat bomb.sh
#!/bin/bash
./$0|./$0&

2.12 一些脚本相关工具

(1) 信号捕捉 trap

trap '触发指令' 信号
进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作

trap '' 信号
忽略信号的操作

trap '-' 信号
恢复原信号的操作

trap -p
列出自定义信号操作

trap finish EXIT
当脚本退出时,执行 finish 函数

示例:

#!/bin/bash
trap 'echo "Press ctrl+c"' int quit
trap -p
for((i=0;i<=10;i++))
do
       sleep 1
       echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i++))
do
       sleep 1
       echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i++))
do
       sleep 1
       echo $i
done

(2) 创建临时文件 mktemp

mktemp 命令用于创建并显示临时文件,可避免冲突。
格式:

mktemp [OPTION]... [TEMPLATE]

常见选项:

-d 创建临时目录
-p DIR或--tmpdir=DIR 指明临时文件所存放目录位置

示例:

mktemp /tmp/testXXX
tmpdir=`mktemp –d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX

(3) 安装复制文件 install

install 命令格式:

install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录

选项:

-m MODE,默认755
-o OWNER
-g GROUP

示例:

install -m 700 -o wang -g admins srcfile desfile
install -m 770 -d /testdir/installdir 

(4) 交互式转化批处理工具 expect

  expect 主要应用于自动化交互式操作的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh 登录,ftp 登录等写在一个脚本上,使之自动完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率。

expect 语法:

expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]

常见选项:

-c:从命令行执行expect脚本,默认expect是交互执行的
-d:可以输出调试信息

expect 中相关命令:

spawn	启动新的进程
expect	从进程接收字符串
send	用于向进程发送字符串
interact		允许用户交互
exp_continue	匹配多个字符串在执行动作后加此命令

详细使用参考:交互式转化批处理工具 expect

2.13 数组

(1) 数组介绍

变量:存储单个元素的内存空间。
数组:存储多个元素的连续的内存空间,相当于多个变量的集合。
数组名和索引:

索引的编号从0开始,属于数值索引
索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0 版本之后开始支持
bash 的数组支持稀疏格式(索引不连续)

(2) 声明数组

#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
#两者不可相互转换

(3) 数组赋值

① 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE

示例:

weekdays[0]="Sunday"
weekdays[4]="Thursday"
② 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

示例:

title=("ceo" "coo" "cto")
num=({0..10})
alpha=({a..g})
file=( *.sh )
③ 只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
④ 交互式数组值对赋值
read -a ARRAY

(4) 显示所有数组

declare -a

示例:

[root@aliyunhost01~]#declare -a
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="0")'

(5) 引用数组

① 引用数组元素
${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素

示例:

[root@centos8 ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@centos8 ~]#echo ${title[1]}
coo
[root@centos8 ~]#echo ${title}
ceo
[root@centos8 ~]#echo ${title[2]}
cto
[root@centos8 ~]#echo ${title[3]}
② 引用数组所有元素
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

示例:

[root@centos8 ~]#echo ${title[@]}
ceo coo cto
[root@centos8 ~]#echo ${title[*]}
ceo coo cto
③ 数组的长度,即数组中元素的个数
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

示例:

[root@centos8 ~]#echo ${#title[*]}
3

(6) 删除数组

① 删除数组中的某个元素
unset ARRAY[INDEX]

示例:

[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto
② 删除整个数组
unset ARRAY

示例:

[root@centos8 ~]#unset title
[root@centos8 ~]#echo ${title[*]}
[root@centos8 ~]#

(7) 数组数据处理

① 数组切片
${ARRAY[@]:offset:number}
offset #要跳过的元素个数
number #要取出的元素个数
#取偏移量之后的所有元素
{ARRAY[@]:offset}

示例:

[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 10
② 向数组中追加元素
ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

示例:

[root@centos8 ~]#num[${#num[@]}]=11
[root@centos8 ~]#echo ${#num[@]}
12
[root@centos8 ~]#echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11

(8) 关联数组

declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用

示例:

[root@centos8 ~]#name[ceo]=mage
[root@centos8 ~]#name[cto]=wang
[root@centos8 ~]#name[coo]=zhang
[root@centos8 ~]#echo ${name[ceo]}
zhang
[root@centos8 ~]#echo ${name[cto]}
zhang
[root@centos8 ~]#echo ${name[coo]}
zhang
[root@centos8 ~]#echo ${name}
zhang
[root@centos8 ~]#declare -A name
-bash: declare: name: cannot convert indexed to associative array
[root@centos8 ~]#unset name
[root@centos8 ~]#declare -A name
[root@centos8 ~]#name[ceo]=mage
[root@centos8 ~]#name[cto]=wang
[root@centos8 ~]#name[coo]=zhang
[root@centos8 ~]#echo ${name[coo]}
zhang
[root@centos8 ~]#echo ${name[ceo]}
mage
[root@centos8 ~]#echo ${name[cto]}
wang
[root@centos8 ~]#echo ${name[*]}
mage wang zhang

关联数组参考:
Shell 关联数组
Shell 中的关联数组,获取数组索引

(9) 数组脚本示例

范例:生成 10 个随机数保存于数组中,并找出其最大值和最小值

#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
 nums[$i]=$RANDOM
 [ $i -eq 0 ] && min=${nums[0]} &&  max=${nums[0]}&& continue
 [ ${nums[$i]} -gt $max ] && max=${nums[$i]}
 [ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min

范例:定义一个数组,数组中的元素对应的值是 /var/log 目录下所有以 .log 结尾的文件。统计出其下标为偶数的文件中的行数之和

#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
    if [ $[$i%2] -eq 0 ];then
 let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
    fi
done
echo "Lines: $lines"

范例:shell 脚本实现冒泡排序

#!/bin/bash
#a test about sort
echo "please input a number list"
read -a arrs
for((i=0;i<${#arrs[@]};i++)){
   for((j=0;j<${#arrs[@]}-1;j++)){
     if [[ ${arrs[j]} -gt ${arrs[j+1]} ]];then
          tmp=${arrs[j]}
          arrs[j]=${arrs[j+1]}
          arrs[j+1]=$tmp
      fi
}
}
echo "after sort"
echo ${arrs[@]}

数组参考:
Shell 数组
Shell 数组操作
Shell 脚本数组用法小结
Shell 数组(详细)

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值