Shell编程

sh 复制代码
# 自定义vim,以便于更好的编写shell
[root@kylin-xu ~]# vim .vimrc
set ts=4
set ai
autocmd BufNewFile *.sh exec ":call SetTitle()"

func SetTitle()
    if expand("%:e") == 'sh'
        call setline(1,"#!/bin/bash")
        call setline(2,"#")
        call setline(3,"#****************************************************")
        call setline(4,"#Author:            xu")
        call setline(5,"#QQ:                123456")
        call setline(6,"#Date:              ".strftime("%Y-%m-%d"))
        call setline(7,"#FileName:          ".expand("%"))
        call setline(8,"#URL:               https://www.cnblogs.com/xuruizhao")
        call setline(9,"#Description:       test")
        call setline(10,"#Copyright(C):     ".strftime("%Y")." All right")
        call setline(11,"#***************************************************")
        call setline(12,"")
        call setline(13,"")
        call setline(14,"")
    endif
endfunc
autocmd BufNewFile * normal G

一、 shell脚本语言的基本用法

【1】、shell脚本的用途

  • 将简单的命令组合完成复杂的工作,自动化执行命令,提高工作效率
  • 减少手工命令的输入,一定程度上避免人为错误
  • 将软件或应用的安装及配置实现标准化
  • 用于实现日常性的,重复性的,非交互式的运维工作,如:文件打包压缩备份,监控系统运行状态并实现告警等

【2】、shell脚本格式

shell是基于过程式、解释执行的语言。

shell脚本是包含一些命令或声明,并符合一定格式的文本文件。

一个shell脚本文件中主要包含以下内容

  • 各种系统命令的组合
  • 数据存储:变量、数组
  • 表达式:a + b
  • 控制语句:if
ini 复制代码
格式要求:首行shebang机制
#!/bin/bash 
#!/usr/bin/python
#!/usr/bin/perl 
#!/usr/bin/ruby
#!/usr/bin/lua

【3】、shell脚本创建过程

  1. 用编辑器创建新文件,首行必须是 shell 声明(shebang),编辑完成后保存退出

  2. 添加可执行权限

  3. 运行脚本

sh 复制代码
#新建文件
[root@kylin-xu ~]# vim test.sh
#!/bin/bash
echo "hello world" 

# 如果没有x权限,在执行时就需要加上解释器
[root@kylin-xu ~]# bash test.sh 
hello world

#加可执行权限,通过绝对路径和相对路径执行
[root@kylin-xu ~]# ./test.sh
-bash: ./test.sh: 权限不够
[root@kylin-xu ~]# chmod +x test.sh 
[root@kylin-xu ~]# ./test.sh
hello 

# 我们也可以将脚本路径加到PATH中,然后就可以直接执行脚本了
[root@kylin-xu ~]# test.sh
-bash: test.sh:未找到命令
[root@kylin-xu ~]# PATH=`pwd`:$PATH
[root@kylin-xu ~]# echo $PATH
/root:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin
[root@kylin-xu ~]# test.sh
hello world

【4】、第一个shell脚本

sh 复制代码
[root@kylin-xu scripts]# vim hello.sh
#!/bin/bash
echo hello world
echo "hello world"
echo 'hello world'

[root@kylin-xu scripts]# bash hello.sh 
hello world
hello world
hello world

当前shell执行,和子shell执行

sh 复制代码
# 父子shell的关系
root@xu-ubuntu:~/scripts# cat 01.sh 
#!/bin/bash

echo hello world
echo 'hello world'
echo "hello world"
sleep 100

# bash 01.sh是在当前shell执行
root@xu-ubuntu:~/scripts# bash 01.sh 
hello world
hello world
hello world
root@xu-ubuntu:~# pstree -p
 ├─sshd(29225)─┬─sshd(83993)───sshd(84113)───bash(84114)───su(84241)───bash(84256)───bash(84456)───sleep(84457)
# ./01.sh 是在子shell执行
root@xu-ubuntu:~/scripts# ./01.sh 
hello world
hello world
hello world

root@xu-ubuntu:~# pstree -p
├─sshd(29225)─┬─sshd(83993)───sshd(84113)───bash(84114)───su(84241)───bash(84256)───01.sh(84468)───sleep(84469)

本地执行远程脚本 - 脚本文件在远程主机,在本机执行并在本机生效,执行结果影响的是本机

sh 复制代码
test.sh脚本在192.168.121.99主机上
[root@kylin-xu html]# cat /var/www/html/test.sh 
#!/bin/bash

echo 'haha'
touch /root/1.txt

在192.168.121.88上执行并生效
root@xu-ubuntu:~/scripts# curl http://192.168.121.99/test.sh | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   375  100   375    0     0  72032      0 --:--:-- --:--:-- --:--:-- 93750
haha
root@xu-ubuntu:~/scripts# ll /root/1.txt 
-rw-r--r-- 1 root root 0 Nov 24 19:24 /root/1.txt

脚本在本地,我在远程主机执行本地脚本,脚本的输出结果是在本地,但是脚本的作用对象是远程主机

sh 复制代码
脚本在本地,在本地执行
root@xu-ubuntu:~/scripts# vim 02.sh
#!/bin/bash

hostname
hostname -I
root@xu-ubuntu:~/scripts# bash 02.sh 
xu-ubuntu
192.168.121.88 

# 连接上远程主机,执行本地shell,shell的结果输出在本地,但是却作用于远程主机
root@xu-ubuntu:~/scripts# ssh 192.168.121.99 /bin/bash < 02.sh 
kylin-xu
192.168.121.99 

【5】、shell脚本调试

写一个脚本,获取主机系统信息,包括CPU型号,内存大小,硬盘大小,操作系统版本这四个指标

sh 复制代码
# version1.0
root@xu-ubuntu:~/scripts# vim sys_ver1.0.sh
#! /bin/bash
lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'
free -h | awk 'NR==2{print $2}' 
lsblk | awk '/^sda/{print $(NF-2)}' 
grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'
root@xu-ubuntu:~/scripts# chmod +x sys_ver1.0.sh 
root@xu-ubuntu:~/scripts# ./sys_ver1.0.sh 
13th Gen Intel(R) Core(TM) i5-13500HX
3.8Gi
50G
Ubuntu 22.04.5 LTS


# version2
root@xu-ubuntu:~/scripts# vim sys_ver2.0.sh
#!/bin/bash

echo -e "CPU info: \c"
lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'
echo -e "free total: \c"
free -h | awk 'NR==2{print $2}'
echo -e "hd size: \c"
lsblk | awk '/^sda/{print $(NF-2)}'
echo -e "sys info: \c"
grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'
root@xu-ubuntu:~/scripts# bash sys_ver2.0.sh 
CPU info: 13th Gen Intel(R) Core(TM) i5-13500HX
free total: 3.8Gi
hd size: 50G
sys info: Ubuntu 22.04.5 LTS


# version3,给输出加上颜色
root@xu-ubuntu:~/scripts# vim sys_ver2.0.sh
#!/bin/bash

echo -e "CPU info: \c"
echo -e "\E[1;32m`lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'`\E[0m"

echo -e "free total: \c"
echo -e "\E[1;32m`free -h | awk 'NR==2{print $2}'`\E[0m"

echo -e "hd size: \c"
echo -e "\E[1;32m`lsblk | awk '/^sda/{print $(NF-2)}'`\E[0m"

echo -e "sys info: \c"
echo -e "\E[1;32m`grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'`\E[0m"
root@xu-ubuntu:~/scripts# bash sys_ver2.0.sh 
CPU info: 13th Gen Intel(R) Core(TM) i5-13500HX
free total: 3.8Gi
hd size: 50G
sys info: Ubuntu 22.04.5 LTS

执行前先做语法检查

此选项只能检测脚本中的语法错误,不能检测命令错误,也不会执行脚本

sh 复制代码
bash -n
sh 复制代码
root@xu-ubuntu:~/scripts# vim 03.sh
#!/bin/bash

echo 123
echo 456
echo "ased
root@xu-ubuntu:~/scripts# bash -n 03.sh 
03.sh: line 16: unexpected EOF while looking for matching `"'
03.sh: line 17: syntax error: unexpected end of file

调试并执行,查找命令的错误

逐行输出命令,并输出执行结果

sh 复制代码
bash -x
sh 复制代码
root@xu-ubuntu:~/scripts# vim 04.sh
echo '123'
id
hostname
hostname -I
echo 45
root@xu-ubuntu:~/scripts# bash -x 04.sh 
+ echo 123
123
+ id
uid=0(root) gid=0(root) groups=0(root)
+ hostname
xu-ubuntu
+ hostname -I
192.168.121.88 
+ echo 456
456
sh 复制代码
# 获取两天后的日期
root@xu-ubuntu:~/scripts# date +%F
2024-11-24
root@xu-ubuntu:~/scripts# date +%F --date="+2 day"
2024-11-26

总结:脚本错误常见的有三种

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

【6】、变量

1、变量类型

变量类型:

  • 内置变量,如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE
  • 用户自定义变量

不同的变量存放的数据不同,决定了以下

  • 数据存储方式
  • 参与的运算
  • 表示的数据范围

变量数据类型:

  • 字符
  • 数值:整型、浮点型,bash 不支持浮点数
  • 布尔
  • 指针
  • 结构体:自定义的复合类型的数据类型
  • ........

2、 Shell中变量命名法则

命名要求

  • 区分大小写
  • 不能使用程序中的保留字和内置变量:如:if, for
  • 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 " - ",和主机名相反

命名习惯

  • 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
  • 变量名大写
  • 局部变量小写
  • 函数名小写
  • 大驼峰StudentFirstName,由多个单词组成,且每个单词的首字母是大写,其它小写
  • 小驼峰studentFirstName ,由多个单词组成,第一个单词的首字母小写,后续每个单词的首字母
  • 是大写,其它小写
  • 下划线: student_first_name

3、变量的定义和引用

变量的生效范围等标准划分变量类型

  • 普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell 进程均无效
  • 环境变量:生效范围为当前shell进程及其子进程
  • 本地变量:生效范围为当前shell进程中某代码片断,通常指函数

(1)、变量赋值

sh 复制代码
name='value'

value 可以是以下多种形式

sh 复制代码
name='root' #直接字串
name="$USER" #变量引用
name=`COMMAND` #命令引用
name=$(COMMAND) #命令引用

root@xu-ubuntu:~/scripts# name="tom"
root@xu-ubuntu:~/scripts# echo $name
tom
root@xu-ubuntu:~/scripts# name=$USER
root@xu-ubuntu:~/scripts# echo $name
root
root@xu-ubuntu:~/scripts# name=`hostname`
root@xu-ubuntu:~/scripts# echo $name
xu-ubuntu
root@xu-ubuntu:~/scripts# name=$(hostname)
root@xu-ubuntu:~/scripts# echo $name
xu-ubuntu

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

(2)、变量引用

sh 复制代码
$name
${name}

弱引用和强引用

  • "$name" 弱引用,其中的变量引用会被替换为变量值
  • '$name' 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

变量的间接赋值和引用

sh 复制代码
root@xu-ubuntu:~/scripts# Ti=cto
root@xu-ubuntu:~/scripts# 
root@xu-ubuntu:~/scripts# name=wang
root@xu-ubuntu:~/scripts# Ti=$name
root@xu-ubuntu:~/scripts# echo $Ti
wang
root@xu-ubuntu:~/scripts# name=xu
root@xu-ubuntu:~/scripts# echo $Ti
wang

变量追加值

sh 复制代码
root@xu-ubuntu:~# TI=cto
root@xu-ubuntu:~# TI+=:xu
root@xu-ubuntu:~# echo $TI
cto:xu

在脚本中使用变量

sh 复制代码
COLOR=33

echo -e "CPU info: \c"
echo -e "\E[1;${COLOR}m`lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'`\E[0m"

echo -e "free total: \c"
echo -e "\E[1;${COLOR}m`free -h | awk 'NR==2{print $2}'`\E[0m"

echo -e "hd size: \c"
echo -e "\E[1;${COLOR}m`lsblk | awk '/^sda/{print $(NF-2)}'`\E[0m"

echo -e "sys info: \c"
echo -e "\E[1;${COLOR}m`grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'`\E[0m"


root@xu-ubuntu:~/scripts# bash sys_ver2.0.sh 
CPU info: 13th Gen Intel(R) Core(TM) i5-13500HX
free total: 3.8Gi
hd size: 50G
sys info: Ubuntu 22.04.5 

(3)、删除变量

sh 复制代码
root@xu-ubuntu:~/scripts# name=xixi
root@xu-ubuntu:~/scripts# echo $name
xixi
root@xu-ubuntu:~/scripts# unset name
root@xu-ubuntu:~/scripts# echo $name

(4)、显示系统中定义的所有变量

sh 复制代码
root@xu-ubuntu:~/scripts# set | grep name=
name=xixi

4、环境变量

环境变量:

  • 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
  • 一般只在系统配置文件中使用,在脚本中较少使用

变量声明和赋值:

sh 复制代码
#声明并赋值
export name=VALUE
declare -x name=VALUE
#或者分两步实现
name=VALUE
export name

显示所有环境变量

sh 复制代码
env
printenv
export
declare -x

查看指定进程的环境变量

sh 复制代码
cat /proc/$PID/environ

删除变量

sh 复制代码
unset

bash内建的环境变量

sh 复制代码
PATH
SHELL
USER
UID
HOME
PWD
SHLVL #shell的嵌套层数,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_   #下划线,表示前一命令的最后一个参数
sh 复制代码
root@xu-ubuntu:~# var1=123;export var1
root@xu-ubuntu:~# var2=456
# var1是环境变量,var2是普通变量
root@xu-ubuntu:~# vim scripts/en.sh
echo $var1
echo $var2
echo $PS1
echo $PATH

root@xu-ubuntu:~# ./scripts/en.sh   # 子shell执行
123


/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

在父shell中定义的环境变量,在子shell中同样也可以引用

5、只读变量

只读变量:只能声明定义,但后续不能修改和删除,即常量

声明只读变量

sh 复制代码
readonly name
declare -r name

查看只读变量

sh 复制代码
readonly [-p]
declare -r
sh 复制代码
root@xu-ubuntu:~# readonly name
root@xu-ubuntu:~# name="asd"
-bash: name: readonly variable

root@xu-ubuntu:~# readonly name="aaa"
root@xu-ubuntu:~# echo $name
aaa
root@xu-ubuntu:~# name=haha
-bash: name: readonly variable

6、位置变量

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

sh 复制代码
$1,$2,...,$9,${10}      #对应第1个、第2个等参数,shift [n]换位置
$0             #shell脚本的文件名
$*             #传递给脚本的所有参数,全部参数合为一个字符串
$@             #传递给脚本的所有参数,每个参数为独立字符串
$#             #传递给脚本的参数的个数
$$             #输出当前的PID
sh 复制代码
root@xu-ubuntu:~# vim scripts/arg.sh
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${10}"
echo "11st arg is ${11}"
echo "The number of arg is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"

[root@ubuntu2204 ~]# bash /data/scripts/arg.sh {a..z}
1st arg is a
2st arg is b
3st arg is c
10st arg is j
11st arg is k
The number of arg is 26
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
The scriptname is arg.sh

\*和@的区别

sh 复制代码
for i in "$*";do
        echo $i
done

for j in "$@";do
        echo $j
done
root@xu-ubuntu:~# bash scripts/05.sh 1 2 3
1 2 3
1
2
3

$*:将接收到的参数看为一个整体

$@:将接收到每一个参数看作一个单独的参数

范例: 利用软链接实现同一个脚本不同功能

sh 复制代码
root@xu-ubuntu:~# ll /usr/sbin/shutdown
lrwxrwxrwx 1 root root 14 Nov 22  2023 /usr/sbin/shutdown -> /bin/systemctl*
root@xu-ubuntu:~# ll /usr/sbin/reboot
lrwxrwxrwx 1 root root 14 Nov 22  2023 /usr/sbin/reboot -> /bin/systemctl*
sh 复制代码
root@xu-ubuntu:~/scripts# cat test.sh 
echo $0

root@xu-ubuntu:~/scripts# ln -s test.sh a.sh
root@xu-ubuntu:~/scripts# bash a.sh 
a.sh

root@xu-ubuntu:~/scripts# ln -s test.sh b.sh
root@xu-ubuntu:~/scripts# bash b.sh 
b.sh
sh 复制代码
if [ $0 == "a.sh" ];then
        echo "aaa"
elif [ $0 == "b.sh" ];then
        echo "bbb"
fi
root@xu-ubuntu:~/scripts# bash a.sh 
aaa
root@xu-ubuntu:~/scripts# bash b.sh 
bbb

7、退出状态码变量

进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功或失败,?取值范例 0-255

sh 复制代码
$? 		   #0代表成功,1-255代表失败
sh 复制代码
root@xu-ubuntu:~/scripts# echo $?
0
root@xu-ubuntu:~/scripts# ping -c1 -w1 baidu.afasfsascom >/dev/null 2>&1
root@xu-ubuntu:~/scripts# echo $?
2

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

sh 复制代码
exit [n]

注意:

  • 脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
  • 如果exit后面无数字,终止退出状态取决于exit命令前面命令执行结果
  • 如果没有exit命令,即未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一 条命令的状态码
sh 复制代码

8、脚本安全$-

sh 复制代码
root@xu-ubuntu:~/scripts# echo $-
himBHs
选项 含义
h 开启hash表缓存外部命令路径
i 当前是交互shell
m 开启监控模式,可通过 Job control来控制进程的停止、继续,后台
B 是否支持大括号扩展
H 是否可用 ! 快速展开history命令
sh 复制代码
root@xu-ubuntu:~/scripts# echo $-
himBHs
root@xu-ubuntu:~/scripts# set +B
root@xu-ubuntu:~/scripts# echo $-
himHs
root@xu-ubuntu:~/scripts# echo {1..4}
{1..4}
root@xu-ubuntu:~/scripts# set -B
root@xu-ubuntu:~/scripts# echo $-
himBHs
root@xu-ubuntu:~/scripts# set +h
root@xu-ubuntu:~/scripts# hash
-bash: hash: hashing disabled

终端中的- 和 shell脚本中的- 不同

sh 复制代码
root@xu-ubuntu:~/scripts# cat 07.sh 
echo $-
root@xu-ubuntu:~/scripts# bash 07.sh 
hB
root@xu-ubuntu:~/scripts# echo $-
himBHs

【7】、格式化输出printf

相当于增强版的 echo, 实现丰富的格式化输出

格式

sh 复制代码
printf format args..

常用格式替换符

替换符 功能
%s 字符串
%d,%i 十进制整数
%f 浮点类型
%c ASCII字符,即显示对应参数的第一个字符
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%o 八进制
%u 无符号数
%x 十六进制数值(a-f)
%X 十六进制数值(A-F)
%% 表示%本身

常用转义字符

转义符 功能
\a 警告字符通常为ASCII的BEL字符
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ 表示 \ 本身
sh 复制代码
root@xu-ubuntu:~/scripts# printf "%s\n"  1 2 3
1
2
3
root@xu-ubuntu:~/scripts# printf "%f\n" 1 2
1.000000
2.000000
# .3f表示保存3位小数
root@xu-ubuntu:~/scripts# printf "%.3f\n" 1.1234 2.12345
1.123
2.123

# 最后的echo表示换行的意思
root@xu-ubuntu:~/scripts# printf "(%s)---" 1 2 3 ;echo
(1)---(2)---(3)---
sh 复制代码
root@xu-ubuntu:~/scripts# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50
# - 表示左对齐,不加- 是右对齐
姓名     性别     年龄 体重 
小明     男        20   70 
小红     女        18   50 
sh 复制代码
# 进制转换
14root@xu-ubuntu:~/scripts# printf "%o" 12;echo 
14

0root@xu-ubuntu:~/scripts# printf "%x" 999 ;echo
3e7
root@xu-ubuntu:~/scripts# printf "%X" 999 ;echo
3E7

【8】、运算符

1、算术运算符

Shell允许在某些情况下对算术表达式进行求值,比如:let和declare 内置命令,(( ))复合命令和算术扩展。求值以固定宽度的整数进行,不检查溢出,尽管除以0 被标记为错误。运算符及其优先级,关联性和值与C语言相同。以下运算符列表分组为等优先级运算符级别。级别按降序排列优先。

注意:bash 只支持整数,不支持小数

sh 复制代码
+ -                               #addition, subtraction
* / %                             #multiplication, division, remainder, %表示取模,即取余数,示例:9%4=1,5%3=2 
i++ i--                           #variable post-increment and post-decrement
++i --i                           #variable pre-increment and pre-decrement
= *= /= %= += -= <<= >>= &= ^= |=   #assignment
- +                                 #unary minus and plus
! ~                                 #logical and bitwise negation
**                                  #exponentiation 乘方,即指数运算
<< >>                               #left and right bitwise shifts
<= >= < >                           #comparison
== !=                               #equality and inequality
&                                   #bitwise AND
|                                   #bitwise OR
^                                   #bitwise exclusive OR
&&                                  #logical AND
||                                  #logical OR
expr?expr:expr                      #conditional operator
expr1 , expr2                       #comma

乘法符号有些场景中需要转义

实现算术运算

sh 复制代码
let var=expression
((var=expression))
var=$[expression]
var=$((expression))
var=$(expr arg1 arg2 ...)
declare -i var=number
echo expression|bc
sh 复制代码
# 如果不进行转义是无法进行运算的
root@xu-ubuntu:~/scripts# i=3
root@xu-ubuntu:~/scripts# j=4
root@xu-ubuntu:~/scripts# echo i+j
i+j

# 进行转义
root@xu-ubuntu:~/scripts# echo $[i+j]
7
root@xu-ubuntu:~/scripts# echo $((i+j))
7
root@xu-ubuntu:~/scripts# let k=i+j
root@xu-ubuntu:~/scripts# echo $k
7
sh 复制代码
# expr进行算数。在进行乘法运算时要进行转义,而且必须要有空格
root@xu-ubuntu:~/scripts# expr 3 + 4
7
root@xu-ubuntu:~/scripts# expr 3 \* 4
12

范例:生成 0 - 49 之间随机数

sh 复制代码
$RANDOM 随机数的范围 0-32767
root@xu-ubuntu:~/scripts# echo $[RANDOM%50]
33
root@xu-ubuntu:~/scripts# echo $[RANDOM%50]
5
root@xu-ubuntu:~/scripts# echo $[RANDOM%50]
44

# 随机字体颜色
root@xu-ubuntu:~/scripts# echo -e "\033[1;$[RANDOM%7+31]mhello\033[0m"
hello

增强型赋值:

sh 复制代码
-=                # i-=j 相当于 i=i-j
*=
/=
%=
++               # i++,++i   相当于 i=i+1
--               # i--,--i   相当于 i=i-1


root@xu-ubuntu:~/scripts# a=10
root@xu-ubuntu:~/scripts# let a+=20
root@xu-ubuntu:~/scripts# echo $a
30
root@xu-ubuntu:~/scripts# echo $[a+=10]
40
root@xu-ubuntu:~/scripts# echo $((a+=10))
50

i++与++i

sh 复制代码
# i++先赋值再+1
root@xu-ubuntu:~/scripts# i=1
root@xu-ubuntu:~/scripts# echo j=i++
j=i++
root@xu-ubuntu:~/scripts# let j=i++
root@xu-ubuntu:~/scripts# echo $i
2
root@xu-ubuntu:~/scripts# echo $j
1

# ++i先+1再赋值
root@xu-ubuntu:~/scripts# i=1
root@xu-ubuntu:~/scripts# let  j=++i
root@xu-ubuntu:~/scripts# echo $j
2
root@xu-ubuntu:~/scripts# echo $i
2

2、逻辑运算

true、false

bool值 二进制表示 含义
true 1
false 0

与:&和相与结果为0,和1相与结果保留原值,一假则假,全真才真

或:|和1相或结果为1,和0相或结果保留原值,一真则真,全假才假

异或:

异或的两个值,相同为假,不同为真。两个数字X,Y异或得到结果Z,Z再和任意两者之一X异或,将得

出另一个值Y

sh 复制代码
root@xu-ubuntu:~/scripts# false
root@xu-ubuntu:~/scripts# echo $?
1
root@xu-ubuntu:~/scripts# true
root@xu-ubuntu:~/scripts# echo $?
0

# 变量互换
root@xu-ubuntu:~/scripts# x=10
root@xu-ubuntu:~/scripts# y=20
root@xu-ubuntu:~/scripts# let x=x^y
root@xu-ubuntu:~/scripts# let y=x^y
root@xu-ubuntu:~/scripts# let x=x^y
root@xu-ubuntu:~/scripts# echo $x $y
20 10

短路运算

短路&&

cmd1&&cmd2

当 cmd1 的结果为 false 时,整个表达式就是 false, cmd2 不参与运算,这就是所谓的短路

当 cmd1 的结果为 true 时,cmd2 才参与运算, 看cmd2的结果是真假,在整体判断 表达式的真假

短路||

cmd1||cmd2

当 cmd1 的结果为 true 时,整个表达式就是 true, cmd2 不参与运算,这就是所谓的短路

当 cmd1 的结果为 false 时,cmd2 参与运算,根据cmd2的结果再去判断整个表达式的真假

sh 复制代码
[root@kylin-xu ~]# id tom > /dev/null  2>&1 && echo have || echo no
no

【9】、条件测试命令

条件测试:

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

实现评估布尔声明,以便用在条件性环境下进行执行

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

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

条件测试命令

sh 复制代码
test expr
[ arg... ]            #同 test
[[ expression ]]      #增加版的[],支持[],支持扩展正则表达式和通配符

# 注意:[] 中的表达式,前后要有空格

[[ expression ]] 用法
== #左侧字符串是否和右侧的PATTERN相同
   #注意:此表达式用于[[ ]]中,PATTERN为通配符
   
=~ #左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配
   #注意: 此表达式用于[[ ]]中为扩展的正则表达式

常用选项:文件判断相关

sh 复制代码
注意:最终结果由用户对文件的实际权限决定,而非文件属性决定
-a FILE     #如果文件存在则为真
-b FILE     #如果文件为块特殊文件则为真
-c FILE     #如果文件为字符特殊文件则为真
-d FILE     #如果文件为目录则为真
-e FILE     #如果文件存在则为真
-f FILE     #如果文件存在且为常规文件则为真
-g FILE     #如果文件的组属性设置打开则为真
-h FILE     #如果文件为符号链接则为真
-L FILE     #如果文件为符号链接则为真
-k FILE     #如果文件的粘滞 (sticky) 位设定则为真
-p FILE     #如果文件为命名管道则为真
-r FILE     #如果当前用户对文件有可读权限为真
-s FILE     #如果文件存在且不为空则为真
-S FILE     #如果文件是套接字则为真
-t FD       #如果文件描述符在一个终端上打开则为真
-u FILE     #如果文件设置了SUID特殊权限则为真
-w FILE     #如果当前用户对文件有可写权限为真
-x FILE     #如果当前用户对文件有可执行权限为真
-O FILE     #如果当前用户是文件属主为真
-G FILE     #如果当前用户对文件属组为真
-N FILE     #如果文件上次被读取之后修改过则为真
FILE1 -nt FILE2 #如果 file1 文件新于 file2 文件 则为真(根据修改日期)
FILE1 -ot FILE2 #如果 file1 文件旧于 file2 文件则为真
FILE1 -ef FILE2 #如果 file1 文件是 file2 文件的硬链接则为真

[root@kylin-xu ~]# [ -a /root/1.txt  ] && echo exist || echo not
not
[root@kylin-xu ~]# touch 1.txt
[root@kylin-xu ~]# [ -a /root/1.txt  ] && echo exist || echo not
exist

常用选项:字符串判断相关

在比较字符串时,建议放在""中

sh 复制代码
-z STRING     #判断字符串是否为空,为空则为真
-n STRING     #如果字符串不为空则为真
STRING #同上  

[root@kylin-xu ~]# [[ -z "" ]]
[root@kylin-xu ~]# echo $?
0
[root@kylin-xu ~]# [[ -z "asdadasd" ]]
[root@kylin-xu ~]# echo $?
1
[root@kylin-xu ~]# [[ -n "" ]]
[root@kylin-xu ~]# echo $?
1
[root@kylin-xu ~]# [[ -n "asds" ]]
[root@kylin-xu ~]# echo $?
0


#两个字符串变量比较,一定要有空格,如果没有空格,就变成赋值了
STRING1 = STRING2    #如果 string1 和 string2 字符串相同则为真
STRING1 != STRING2   #如果 string1 和 string2 字符串不相同则为真
STRING1 < STRING2    #只比较第一个字符,以字母表顺序来比较,string1在string2 之前则为真,< 要转义
STRING1 > STRING2    #只比较第一个字符,以字母表顺序来比较,string1在string2 之后则为真, > 要转义

[root@kylin-xu ~]# [ "s" == "s" ] &&  echo true
true
[root@kylin-xu ~]# [ "s" == "s" ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ "s" == "ssss" ] &&  echo true || echo false
false
[root@kylin-xu ~]# [ "s" \< "i" ] &&  echo true || echo false
false
[root@kylin-xu ~]# [ "s" \> "i" ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ "s" != "i" ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ "s" != "s" ] &&  echo true || echo false
false


# 使用正则进行匹配
[root@kylin-xu ~]# [[ $str =~ [a-z]+ ]];echo $?
0
[root@kylin-xu ~]# [[ $str =~ [0-9]+ ]];echo $?
1

常用选项:数学相关

sh 复制代码
#数字相关 格式 arg1 OP arg2
-eq                    #等于
-ne                    #不等于
-lt                    #小于
-le                    #小于等于
-gt                    #大于
-ge                    #大于等于
#算术表达式比较
==                    #相等
!=                    #不相等
<=                    #小于或等于
>=                    #大于或等于
<                     #小于
>                     #大于


[root@kylin-xu ~]# [ 10 -gt 9 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -lt 9 ] &&  echo true || echo false
false
[root@kylin-xu ~]# [ 10 -lt 99 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -eq 10 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -ne 100 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -ne 10 ] &&  echo true || echo false
false

常用选项:其它

sh 复制代码
-o OPTION                  #如果在shell 中开启了某项,则为真
-v VAR                     #如果变量被设置为为真
-R VAR                     #如果变量被设置且被引用则为真
! EXPR                     #表达式结果取反
EXPR1 -a EXPR2             #如果 expr1 和 expr2 都为真则为真 &&
EXPR1 -o EXPR2             #如果 expr1 和 expr2 有一个为真则为真

[root@kylin-xu ~]# x=100
[root@kylin-xu ~]# test -v x
[root@kylin-xu ~]# [ -v x ] && echo exist || echo "not exists"
exist

【10】、关于{} 和 ()

sh 复制代码
( cmd1;cmd2;... ) 和 { cmd1;cmd2;...; } 都可以将多个命令组合在一起,批量执行
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境
{ list; } 不会开启子shell, 在当前shell中运行,会影响当前shell环境,左侧要有空格,右侧要有; 结束
sh 复制代码
[root@kylin-xu ~]# echo $BASHPID
553624
# 小括号开启子进程
[root@kylin-xu ~]# (echo $BASHPID ; str="aaa";echo $str);echo $str
592898
aaa
abc

# 大括号不会开子进程
[root@kylin-xu ~]# { echo $BASHPID ; str="aaa";echo $str; };echo $str
553624
aaa
aaa

练习

sh 复制代码
# 1. 编写脚本 argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户"至少应该给 一个参数",并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
#! /bin/bash
if [ "$#" -lt "1" ];then
        echo "至少应该输入一个变量"
        exit
fi

sed '/^$/p' -n "$1"|wc -l

#编写脚本 hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提 示用户"该IP地址可访问";如果不可ping通,则提示用户"该IP地址不可访问"
if [[ $1 =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$ ]];then 
        ping $1 -c1 -w1 > /dev/null  2>&1 && echo "该ip地址可以访问" || echo "该IP地址不能访问"
fi

#编写脚本 checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
for i in `df -ih | sed '2,$p' -n | awk '{print $(NF-1),$1}' | column -t | sort -rn | awk -F "[% ]+" '$1>=80{print $2}'`; 
do 
        echo $i"inode将满"; 
done

for i in `df -h | sed '2,$p' -n | awk '{print $(NF-1),$1}' | column -t | sort -rn | awk -F "[% ]+" '$1>=80{print $2}'`;
do
    echo $i"空间将满";
done

# 编写脚本 per.sh,判断当前用户对指定参数文件,是否不可读并且不可写
if [ ! -r $1 -a ! -x $1 ];then
    echo "不可读,不可写"
else
    echo "可读或可写"
fi

# 编写脚本 excute.sh ,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件
if [ -f $1 ];then
        chmod +x $1
fi

# 编写脚本 nologin.sh和 login.sh,实现禁止和允许普通用户登录系统
nologin.sh
for i in `awk -F: '$3>=500&&$NF=="/bin/bash"{print $1}' /root/passwd`;
do
    usermod -s /sbin/nologin $i
done

login.sh
for i in `awk -F: '$3>=500&&$NF=="/sbin/nologin"{print $1}' /etc/passwd`; 
do 
    usermod -s /bin/bash $i; 
done

【11】、使用read命令来接受输入

使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY

sh 复制代码
[root@kylin-xu ~]# read
aaa
[root@kylin-xu ~]# echo $REPLY
aaa
[root@kylin-xu ~]# read -p "input:" NAME
input:xxx
[root@kylin-xu ~]# echo $NAME
xxx

管道符左右两侧都是子进程

sh 复制代码
[root@kylin-xu ~]# echo $BASHPID ; { echo $BASHPID; } | { xargs ;echo $BASHPID; } 
654136
663419
663420 

二、bash shell的配置文件

bash shell的配置文件很多,可以分成下面类别

【1】、按照生效范围登录

全局配置:针对所有用户皆有效

sh 复制代码
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc ------------------------ /etc/bash.bashrc #ubuntu  

个人配置:只针对特定用户有效

sh 复制代码
~/.bash_profile
~/.bashrc

【2】、shell登录方式分类

1、交互式登录

  • 直接通过终端输入账号密码登录
  • 使用 su - UserName 切换的用户

配置文件生效和执行顺序:

sh 复制代码
#放在每个文件最前
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc 
~/ .bash_ profile
~/ .bashrc
/etc/bashrc                 #此文件执行两次
#放在每个文件最后
/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc                #此文件执行两次
~/.bashrc
~/.bash_profile

注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序

2、非交互式登录

  • su UserName
  • 图形界面下打开的终端
  • 执行脚本
  • 任何其它的bash实例

执行顺序:

sh 复制代码
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc

【3】、编辑配置文件生效

修改profile和bashrc文件后需生效两种方法:

  • 重新启动shell进程
  • source|. 配置文件

注意:source 会在当前shell中执行脚本,所有一般只用于执行置文件,或在脚本中调用另一个脚本的场景

【4】、Bash退出任务

保存在~/.bash_logout文件中(用户),在退出登录shell时运行

功能:

  • 创建自动备份
  • 清除临时文件

三、流程控制

【1】、条件判断

1、if语法

sh 复制代码
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
sh 复制代码
# if单分支
if [ "$1" = "abc" ];then
    echo "abc"
fi
sh 复制代码
# if多分枝
str=abc
if [ "$1" = "abc" ];then
    echo "abc"
elif [ "$1" = "def" ];then
    echo "def"
else
    echo "haha"
fi
sh 复制代码
# if 嵌套
str=abc
if [ "$1" = "abc" ];then
        echo "abc"
        if [ "$2" = "def" ];then
                echo "2 is def"
        else
                echo "2 not def"
        fi
elif [ "$1" = "def" ];then
        echo "def"
else 
        echo "haha"
fi
sh 复制代码
# 判断操作系统类型
. /etc/os-release 

if [ $ID = "ubuntu" ];then
        echo "is ubuntu"
elif [[ $ID =~ rhel|centos|rocky ]];then
        echo "rhel"
else
        echo "other system"
fi

2、case语句

sh 复制代码
case $1 in
"abc")
        echo "is abc"
        ;;
"def")
        echo "is def"
        ;;
"xyz")
        echo "is xyz"
        ;;
"123")
        echo "is 123"
        ;;
*)
        echo "any"
        ;;
esac
sh 复制代码
read -p "" INPUT
INPUT=`echo  $INPUT | tr "A-Z" "a-z"`
case $INPUT in
y|yes)
        echo "is yes"
        ;;
n|no)
        echo "is no"
        ;;
*)
        echo "none"
        ;;
esac
sh 复制代码
# 文件后缀处理
read -p "请输入文件名:" FILE
FILE=`echo $FILE |sed 's#.*\.(.*$)#\1#g' -r`
case $FILE in
txt)
        echo "is txt file"
        ;;
sh)
        echo "is shell file"
        ;;
conf)
        echo "is configure file"
        ;;
*)
        echo "any"
        ;;
esac

【2】、循环控制

将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件

重复运行次数

  • 循环次数事先已知
  • 循环次数事先未知

常见的循环的命令:for, while, until

1、for循环

执行机制:

  • 依次将列表中的元素赋值给"变量名"; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
  • 如果省略 [in WORDS ... ] ,此时使用位置参数变量 in "$@"

for 循环列表生成方式:

  • 直接给出列表
  • 整数列表:如
  • 返回列表的命令:如 $(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:@,*,$#
sh 复制代码
# 计算1+2+3+...+100 的结果
sum=0
for i in {1..100}
do
        let sum=$i+$sum
done
echo $sum
sh 复制代码
# 批量创建用户
[ $# -eq 0 ] && { echo "你没有输入变量";exit 1; }

for user in $@;
do
    id $user &> /dev/null  && echo "$user已经存在" || { useradd $user;echo "$user is create";id $user; }
done
sh 复制代码
# 99乘法表
flag=$[RANDOM%7+31]
for i in {1..9}
do
    for j in {1..9}
    do
        if [ $j -le $i ];then
            let k=$i*$j
            echo -ne "\E[1;${flag}m$j×$i=$k\E[0m\t"
        else
            echo
            break
        fi
    done
done
echo

有若干只兔和鸡,兔和鸡加起来一共有100条腿,请写一个简单的shell算出兔和鸡各多少只可能组合(假设所有的鸡和兔子的腿都是健全的,且兔和鸡至少为1只)

sh 复制代码
# 暴力破解
count=0
sum=0
num=0
for i in {1..100}
do
    for j in {1..100}
    do
        let num++
        let sum=4*$i+2*$j
        if [ $sum == 100 ];then
            echo "$i只兔子,$j只鸡"
            let count++
        fi
    done
done
echo "共有$count种组合"
echo "循环了$num次"
sh 复制代码
# 第一次优化  加break
count=0
sum=0
num=0
for i in {1..100}
do
    for j in {1..100}
    do
        let num++
        let sum=4*$i+2*$j
        if [ $sum == 100 ];then
            echo "$i只兔子,$j只鸡"
            let count++
            break
        fi
    done
done
echo "共有$count种组合"
echo "循环了$num次"
sh 复制代码
# 第二次优化
count=0
sum=0
num=0
for i in {1..25}
do
    for j in {1..50}
    do
        let num++
        let sum=4*$i+2*$j
        if [ $sum == 100 ];then
            echo "$i只兔子,$j只鸡"
            let count++
            break
        fi
    done
done
echo "共有$count种组合"
echo "循环了$num次"
sh 复制代码
# 生成三角形
read -p "请输入三角形行数:" line
let num=$line*2-1
for i  in `seq $line`
do
        let star=$i*2-1
        let space=($num-$star)/2
        if [ $space == 0 ];then
                for j in `seq $num`
                do
                        echo -n "*"
                done
        else
                for k in `seq $space`
                do
                        echo -n " "
                done
                let x=$num-$space*2
                for m in `seq $x`
                do
                        echo -n "*"
                done
        fi
        echo 
done
请输入三角形行数:10
         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************
sh 复制代码
# 进度条
root@xu-ubuntu:~/scripts# for ((i = 0; i <= 100; ++i)); do printf "\e[4D%3d%%" $i;sleep 0.1s; done;echo
100%

2、while循环

sh 复制代码
while CONDITION; do COMMANDS; done

while CONDITION; do
循环体
done

while CONDITION
do
 循环体
done

说明:

CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为

"true",则执行一次循环;直到条件测试状态为"false"终止循环,因此:CONDTION一般应该有循环控

制变量;而此变量的值会在循环体不断地被修正

进入条件:CONDITION为 true

退出条件:CONDITION为 false

sh 复制代码
# 死循环
while true; do
  循环体
done

while : ; do
  循环体
done

# 计算1-100
root@xu-ubuntu:~/scripts# i=1
root@xu-ubuntu:~/scripts# while [ $i -le 100 ]; do let sum=$sum+$i;let i++; done
root@xu-ubuntu:~/scripts# echo $sum
5050

国际象棋棋盘

sh 复制代码
color=41
for i in {1..8}
do
        for j in {1..8}
        do
                echo -ne "\E[0${color}m  \E[0m"
                if [ $color -eq 41 ];then
                        color=43
                else
                        color=41
                fi
        done
        if [ $color -eq 41 ];then
            color=43
        else
            color=41
        fi
        echo
done

while特殊用法,while read

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

sh 复制代码
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
sh 复制代码
while read line
do
        echo $line"---------"
done < /etc/passwd

3、continue和break

continue:结束本次循环,进入下一次循环

break:结束当前循环,跳出当前的循环体

四、函数

【1】、函数定义

函数 function

是由若干条shell命令组成的语句块,实现代码重用和模块化编程

它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分

函数和shell程序区别

  • Shell程序在子Shell中运行
  • 函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改
sh 复制代码
#语法一
func_name(){
 ...函数体...
}

#语法二
function func_name {
 ...函数体...
}

#语法三
function func_name(){
 ...函数体...
}
sh 复制代码
func1(){
    echo "func1"
}

function func2(){
    echo  "func2"
}

function func3 {
    echo "func3"
}

func1
func2
func3

【2】、函数调用

函数的调用方式

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

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止

sh 复制代码
# 交互式环境下
root@xu-ubuntu:~/scripts# test(){
> echo "hanshu"
> }
root@xu-ubuntu:~/scripts# test
hanshu

root@xu-ubuntu:~/scripts# function test1(){
> echo hanshu1
> }
root@xu-ubuntu:~/scripts# test1
hanshu1

root@xu-ubuntu:~/scripts# function test2 {
> echo hanshu3
> }
root@xu-ubuntu:~/scripts# test2
hanshu3


# 脚本文件中
function test1(){
        echo hanshu1
}
function test2 {
        echo hanshu2
}
test3(){
        echo hanshu3
}

test1
test2
test3
root@xu-ubuntu:~/scripts# bash fun.sh 
hanshu1
hanshu2
hanshu3

# 单独文件
fun.sh文件
function test1(){
        echo hanshu1
}
function test2 {
        echo hanshu2
}
test3(){
        echo hanshu3
}

fun1.sh文件
. fun.sh
test1
test2
test3

root@xu-ubuntu:~/scripts# bash fun1.sh 
hanshu1
hanshu2
hanshu3

【3】、函数嵌套

sh 复制代码
# 在使用函数嵌套时一定要保证能够有一个出口,能有一个结束的地方,否则程序会不断地堆栈,报错

function test1(){
    echo hanshu1
}
function test2 {
    echo hanshu2
}
test3(){
    echo hanshu3
    test2
}
test3

root@xu-ubuntu:~/scripts# bash fun.sh
hanshu3
hanshu2

【4】、函数返回值

return

函数默认返回值是函数体中最后一条命令的退出状态码;

也可以使用return 自定义函数的返回值,在函数中使用 return,return 之后的语句将不会被执行

return 语句 返回值
return 由return前面一行的命令执行结果决定
return 0 无错误返回
return 1~255 有错误返回
sh 复制代码
function re1(){
        echo aaa
        return
        echo bbb
}

function re2 {
        echooo ccc
        return
}

function re3 {
        echo ddd
        return 123
}

re1
echo "re1函数的返回值:$?"
echo "==============================="
re2
echo "re2函数的返回值:$?"
echo "==============================="
re3
echo "re3函数的返回值:$?"
echo "==============================="
root@xu-ubuntu:~/scripts# bash return.sh 
aaa
re1函数的返回值:0
===============================
return.sh: line 21: echooo: command not found
re2函数的返回值:127
===============================
ddd
re3函数的返回值:123
===============================

return和exit的区别

return 只会中断函数执行,但exit 会中断整个脚本的执行

【5】、函数参数

函数可以接受参数:

  • 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
  • 在函数体中当中,可使用$1, 2, ...调用这些参数;还可以使用@, $*, $#等特殊变量
sh 复制代码
function canshu(){
    echo -e "\E[1;$2m$1\E[0m"
    return
}

yanse=$[RANDOM%7+31]
canshu "nihao" $yanse
echo "canshu的返回值是:$?"
sh 复制代码
function canshu(){
        for i in $*
        do
                echo $i
        done
}

canshu $*

root@xu-ubuntu:~/scripts# bash hanshucanshu.sh 1 2 3 4 5 6
1
2
3
4
5
6

【6】、函数变量

变量作用域

变量类型 特点
普通变量 只在当前shell进程中有效,函数外定义,可以在函数内修改
环境变量 当前shell和子shell有效
本地变量 作用域在函数内,函数结束会被自动销毁,只能在函数内定义

在函数中定义本地变量

sh 复制代码
local NAME=VALUE
sh 复制代码
# 普通变量可以在函数中被修改
root@xu-ubuntu:~/scripts# cat func1.sh 
#!/bin/bash

function test1(){
        echo "============test1 start============"
        echo $var
        var=789
        echo $var
        echo "============test1 end============"
}
root@xu-ubuntu:~/scripts# cat var.sh 
#!/bin/bash

. func1.sh

var=123

function test2(){
        echo "==========test2 start============"
        echo $var
        var=456
        echo $var
        echo "==========test2 end============"
}
function test3(){
        echo "==========test3 start============"
        echo $var
        var=147
        echo $var
        echo "==========test3 end============"
}

echo $var
test1
echo $var
test2
echo $var
test3
echo $var

root@xu-ubuntu:~/scripts# bash var.sh 
123
============test1 start============
123
789
============test1 end============
789
==========test2 start============
789
456
==========test2 end============
456
==========test3 start============
456
147
==========test3 end============
147
sh 复制代码
# 本地变量只作用在函数内
var=123

function test2(){
        echo "==========test2 start============"
        echo $var
        local var=456
        echo $var
        echo "==========test2 end============"
}
function test3(){
        echo "==========test3 start============"
        echo $var
        local var=147
        echo $var
        echo "==========test3 end============"
}

echo $var
test2
echo $var
test3
echo $var
# 函数中定义的var 只能在函数中被调用,作用域只在函数内
root@xu-ubuntu:~/scripts# bash var.sh 
123
==========test2 start============
123
456
==========test2 end============
123
==========test3 start============
123
147
==========test3 end============
123

【7】、函数递归

函数递归:

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

递归特点:

  • 函数内部自已调用自已
  • 必须有结束函数的出口语句,防止死循环
sh 复制代码
# 递归实现阶乘
function jiecheng(){
    if [ $1 -eq 1 ];then
        echo 1
    else
        echo $[$1 * $(jiecheng $[$1-1])]
    fi 
}
jiecheng $1
sh 复制代码
# 斐波那契数列第N项的值
fib(){
 if [ $1 -gt 1 ];then
     echo $[ $(fib $[$1-1]) + $(fib $[$1-2]) ]
 else
     echo $1
 fi
}
fib $1

fork炸弹

fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。

由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

sh 复制代码
:(){ :|:& };:
bomb(){ bomb | bomb & };bomb

五、其他脚本相关工具

【1】、trap信号捕捉

trap 命令可以捕捉信号,修改信号原来的功能,实现自定义功能

在脚本或程序的执行过程中,我们可以通过发送信号的方式,打断或终止程序的执行过程,为了避免这种情况,我们可以使用信号捕捉,来自定义信号处理。

sh 复制代码
#常用选项
-l #显示所有信号
-p #显示所有自定义的信号


trap 'command' signal #自定义指定信号的处理方式
trap '' signal #忽略指定的信号
trap '-' signal #恢复信号默认操作
trap func EXIT #退出时执行func

显示所有的信号

sh 复制代码
root@xu-ubuntu:~/scripts# trap -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
。。。

信号的3种表示方法

sh 复制代码
3) SIGQUIT
3          #信号ID
SIGQUIT    #完整写法,大小写都支持
QUIT       #简短写法,大小写都支持
sh 复制代码
trap "echo '捕捉到了 ctrl+c信号'" SIGINT
trap "echo '捕捉到了 ctrl+\信号'" quit
trap -p
for i in {1..15};do
 sleep 1
 echo $i
done

trap '' int
trap -p

for i in {16..30};do
 sleep 1
 echo $i
done

trap '-' int
trap -p

for i in {31..45};do
 sleep 1
 echo $i
done

【2】、创建临时文件mktemp

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

sh 复制代码
#常用选项
-d|--directory          #创建目录
-u|--dry-run            #只输出命令,不执行
-p DIR|--tmpdir[=DIR]   #指明临时文件所存放目录位置
-t                      #将文件保存到$TMPDIR 定义的目录中,如果该变量未定义,则保存到/tmp 目录中
sh 复制代码
root@xu-ubuntu:~/scripts# mktemp 
/tmp/tmp.mt31TMmcSK

# 如果指定了文件名,则后面一定要有X,至少要3个,X会被替换成随机串
root@xu-ubuntu:~/scripts# mktemp XXXX
vrWA
root@xu-ubuntu:~/scripts# ll vrWA 
-rw------- 1 root root 0 Dec  2 10:30 vrWA
root@xu-ubuntu:~/scripts# mktemp testXXXX
testFo7g
root@xu-ubuntu:~/scripts# ll testFo7g 
-rw------- 1 root root 0 Dec  2 10:30 testFo7g
sh 复制代码
# 赋值给变量
root@xu-ubuntu:~/scripts# file=`mktemp`
root@xu-ubuntu:~/scripts# echo $?
0
root@xu-ubuntu:~/scripts# echo $file
/tmp/tmp.Bz19OE4SHV
root@xu-ubuntu:~/scripts# echo 123 > $file
root@xu-ubuntu:~/scripts# cat $file
123
root@xu-ubuntu:~/scripts# ll $file
-rw------- 1 root root 4 Dec  2 10:31 /tmp/tmp.Bz19OE4SHV
sh 复制代码
# 创建目录
root@xu-ubuntu:~/scripts# mktemp -d 
/tmp/tmp.N1GOey7oZo
root@xu-ubuntu:~/scripts# ll /tmp/tmp.N1GOey7oZo -d
drwx------ 2 root root 4096 Dec  2 10:33 /tmp/tmp.N1GOey7oZo/

【3】、安装复制文件install

install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合

sh 复制代码
#常用选项
-m|--mode=MODE          #指定权限,默认755
-v|--verbose            #显示过程
-o|--owner=OWNER        #指定属主
-g|--group=GROUP        #指定属组
-d|--directory DIR      #指定目录,如果不存在就创建
sh 复制代码
#如果目录不存在,则创建目录,并将权限设成777,如果目录存在,则修改其权限
root@xu-ubuntu:~/scripts# install -m 777 -d testdir
root@xu-ubuntu:~/scripts# ll testdir/ -d
drwxrwxrwx 2 root root 4096 Dec  2 10:35 testdir/

root@xu-ubuntu:~/scripts# install -m 755 -d testdir
root@xu-ubuntu:~/scripts# ll testdir/ -d
drwxr-xr-x 2 root root 4096 Dec  2 10:35 testdir/



#将文件复制到指定目录,并指定属主属组,权限,可以一步完成
root@xu-ubuntu:~/scripts# install -m 777 -o xu -g xu func1.sh /tmp/ 
root@xu-ubuntu:~/scripts# ll /tmp/func1.sh 
-rwxrwxrwx 1 xu xu 483 Dec  2 10:38 /tmp/func1.sh*

六、数组

【1】、数组定义

变量:存储单个元素的内存空间

数组:存储多个元素的连续的内存空间,相当于多个变量的集合

数组名和索引

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

【2】、数组声明

sh 复制代码
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME

两者不能相互转换

【3】、数组赋值

sh 复制代码
# 一次只赋值一个元素
root@xu-ubuntu:~/scripts# num[0]=0
root@xu-ubuntu:~/scripts# num[1]=1
root@xu-ubuntu:~/scripts# echo ${num[0]}
0

# 一次性给所有元素赋值
root@xu-ubuntu:~/scripts# title=("ceo" "coo" "cto")
root@xu-ubuntu:~/scripts# echo ${title[1]}
coo
root@xu-ubuntu:~/scripts# echo ${title[2]}
cto

# 间断赋值
root@xu-ubuntu:~/scripts# week=([0]="sun" [4]="thur")
root@xu-ubuntu:~/scripts# echo ${week[0]}
sun
root@xu-ubuntu:~/scripts# echo ${week[1]}

root@xu-ubuntu:~/scripts# echo ${week[4]}
thur

# 使用read接收赋值
root@xu-ubuntu:~/scripts# read -a  test
a b c d 
root@xu-ubuntu:~/scripts# echo ${test[1]}
b

【4】、显示所有的数组

sh 复制代码
root@xu-ubuntu:~/scripts# declare -a
declare -a BASH_ARGC=([0]="0")
declare -a BASH_ARGV=()
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="11")
declare -a BASH_LINENO=()
declare -a BASH_REMATCH=()
declare -a BASH_SOURCE=()
declare -ar BASH_VERSINFO=([0]="5" [1]="1" [2]="16" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")
declare -a DIRSTACK=()
declare -a FUNCNAME
declare -a GROUPS=()
declare -a PIPESTATUS=([0]="0")
declare -a num=([0]="0" [1]="1")
declare -a test=([0]="a" [1]="b" [2]="c" [3]="d")
declare -a title=([0]="ceo" [1]="coo" [2]="cto")
declare -a week=([0]="sun" [4]="thur")

【5】、数组引用

sh 复制代码
# 引用特定的数组元素
#如果省略[INDEX]表示引用下标为0的元素
${ARRAY_NAME[INDEX]}

root@xu-ubuntu:~/scripts# arr=({01..10})
root@xu-ubuntu:~/scripts# echo ${arr[2]}
03
root@xu-ubuntu:~/scripts# echo ${arr[4]}
05
# 如果不加index,默认是输出第一个值
root@xu-ubuntu:~/scripts# echo ${arr}
01
sh 复制代码
# 引用数组所有的元素
root@xu-ubuntu:~/scripts# echo ${arr[*]}
01 02 03 04 05 06 07 08 09 10
root@xu-ubuntu:~/scripts# echo ${arr[@]}
01 02 03 04 05 06 07 08 09 10

# 遍历数组
root@xu-ubuntu:~/scripts# arr=({01..05})
root@xu-ubuntu:~/scripts# for i in ${arr[*]};do echo $i ; done
01
02
03
04
05
root@xu-ubuntu:~/scripts# for i in ${arr[@]};do echo $i ; done
01
02
03
04
05

# 取出下标
root@xu-ubuntu:~/scripts# echo ${!arr[*]}
0 1 2 3 4
root@xu-ubuntu:~/scripts# unset arr[2]  # 取消变量
root@xu-ubuntu:~/scripts# echo ${!arr[*]}
0 1 3 4
root@xu-ubuntu:~/scripts# for i in ${!arr[@]};do echo "$i--->${arr[$i]}" ; done
0--->01
1--->02
3--->04
4--->05
sh 复制代码
# 数组长度
root@xu-ubuntu:~/scripts# echo ${#arr[*]}
4

【6】、删除数组

删除数组中的某元素,会导致稀疏格式

sh 复制代码
unset ARRAY[INDEX]

删除整个数组

sh 复制代码
unset ARRAY

【7】、数组数据处理

1、数组切片

sh 复制代码
root@xu-ubuntu:~/scripts# arr=({a..d})
root@xu-ubuntu:~/scripts# echo ${arr[*]}
a b c d
root@xu-ubuntu:~/scripts# echo ${arr[*]:1:2}
b c

# 从1位置开始取到末尾
root@xu-ubuntu:~/scripts# echo ${arr[*]:1}
b c 

2、数组追加元素

sh 复制代码
root@xu-ubuntu:~/scripts# arr[4]=1234
root@xu-ubuntu:~/scripts# echo ${arr[4]}
1234

root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# echo ${arr[*]}
a b c d 1234 haha haha haha haha

【8】、关联数组

关联数组与普通数组区别:

  • 关联数组要先声明,才能使用,普通数组可以不用声明
  • 关联数组可以自定义下标,普通数组必须用数
sh 复制代码
declare -A ARRAY_NAME 
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2'...)
sh 复制代码
root@xu-ubuntu:~/scripts# declare -A arr1
root@xu-ubuntu:~/scripts# arr1[name]="xu"
root@xu-ubuntu:~/scripts# arr1[age]=18
root@xu-ubuntu:~/scripts# arr1[gender]=男
root@xu-ubuntu:~/scripts# echo $arr1

root@xu-ubuntu:~/scripts# echo ${arr1[name]}
xu
root@xu-ubuntu:~/scripts# echo ${arr1[gender]}
男
root@xu-ubuntu:~/scripts# declare -A arr2

# 一次性定义完成
root@xu-ubuntu:~/scripts# arr2=([name]="aaa" [age]=18 [gender]="男")
root@xu-ubuntu:~/scripts# echo ${arr2[name]}
aaa

# 取出index
root@xu-ubuntu:~/scripts# echo ${!arr1[*]}
gender age name
root@xu-ubuntu:~/scripts# echo ${!arr2[*]}
gender age name
# 单独取出index是,顺序是不一定的,也就是说如果我们根据index遍历关联数组的结果顺序不是按照我们定义时的去输出
root@xu-ubuntu:~/scripts# for i in ${!arr2[*]} ;do echo "$i---${arr2[$i]}" ;done
gender---男
age---18
name---aaa

【9】、范例

sh 复制代码
# 生成10个随机数保存于数组中,并找出其最大值和最小值
function SORT(){
    temp=0
    for j in ${!arr[*]}
    do
        let k=$j+1
        for k  in ${!arr[*]}
        do
            if [ ${arr[$j]} -gt ${arr[$k]} ];then
                temp=${arr[$j]}
                arr[$j]=${arr[$k]}
                arr[$k]=$temp
            fi
        done
    done
    echo "max in arr is ${arr[1]}"
    echo "min in arr is ${arr[$j]}"
}

for i in `seq 10`
do
    arr[$i]=$RANDOM
done
echo "arr is ${arr[*]}"
SORT arr

七、字符串处理

【1】、字符串切片

1、基于偏移量取字符串

sh 复制代码
# #返回字符串变量var的字符的长度,一个汉字算一个字符
root@xu-ubuntu:~/scripts# echo ${#str}
6

root@xu-ubuntu:~/scripts# str=123456
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
root@xu-ubuntu:~/scripts# echo ${str:2}
3456
root@xu-ubuntu:~/scripts# echo ${str:2:2}
34

#取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
root@xu-ubuntu:~/scripts# echo ${str: -2}
56
root@xu-ubuntu:~/scripts# echo ${str: 2: -2}
34

2、基于模式取子串

sh 复制代码
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}
#从var变量的值中删除以word开头的部分
${var#word}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左留右
${var##*word}
${var##word}
sh 复制代码
root@xu-ubuntu:~/scripts# echo $str
abcd1234abcd1234
root@xu-ubuntu:~/scripts# echo ${str#abc}
d1234abcd1234
root@xu-ubuntu:~/scripts# echo ${str#*abc}
d1234abcd1234
root@xu-ubuntu:~/scripts# echo ${str#*cd}
1234abcd1234

# 贪婪匹配
root@xu-ubuntu:~/scripts# echo ${str##*cd}
1234
root@xu-ubuntu:~/scripts# echo ${str##*abcd}
1234
sh 复制代码
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第一个word为界删右留左

${var%word*}
${var%word}

#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向左的最后一个word为界删右留左
${var%%word*}
${var%%word}
sh 复制代码
[root@web01 ~]# str=abcd1234abcd12345
[root@web01 ~]# echo $str
abcd1234abcd12345
[root@web01 ~]# echo ${str%abc*}
abcd1234
[root@web01 ~]# echo ${str%345}
abcd1234abcd12
[root@web01 ~]# echo ${str%%34}
abcd1234abcd12345
[root@web01 ~]# echo ${str%%34*}
abcd12

【2】、查找替换删除

sh 复制代码
#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之,懒惰模式
${var/pattern/substr}
#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之,贪婪模式
${var//pattern/substr}
#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
sh 复制代码
[root@ubuntu2204 ~]# str=abcd1234abcd12345
#从左到右,替换第一个123为XYZ
[root@ubuntu2204 ~]# echo ${str/123/XYZ}
abcdXYZ4abcd12345
#从左到右,替换所有123为XYZ
[root@ubuntu2204 ~]# echo ${str//123/XYZ}
abcdXYZ4abcdXYZ45
#替换行首的abc为XYZ
[root@ubuntu2204 ~]# echo ${str/#abc/XYZ}
XYZd1234abcd12345
#替换行尾的12345为XYZ
[root@ubuntu2204 ~]# echo ${str/%12345/XYZ}
abcd1234abcdXYZ
sh 复制代码
# 删除就是将查找到的替换为空
#删除var表示的字符串中第一次被pattern匹配到的字符串,懒惰模式
${var/pattern}
#删除var表示的字符串中所有被pattern匹配到的字符串,贪婪模式
${var//pattern}
#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}
#删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}
sh 复制代码
[root@ubuntu2204 ~]# str=abcd1234abcd12345
#从左到右删除第一个1234
[root@ubuntu2204 ~]# echo ${str/1234}
abcdabcd12345
#从左到右删除所有1234
[root@ubuntu2204 ~]# echo ${str//1234}
abcdabcd5
#删除行首abcd
[root@ubuntu2204 ~]# echo ${str/#abcd}
1234abcd12345
#删除行尾12345
[root@ubuntu2204 ~]# echo ${str/%12345}
abcd1234abcd

【3】、字符大小写转换

sh 复制代码
#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}
sh 复制代码
[root@web01 ~]# str=abcd1234ABCD12345
[root@web01 ~]# echo ${str^^}
ABCD1234ABCD12345
[root@web01 ~]# echo ${str,,}
abcd1234abcd12345