shell脚本编程基础

编程基础

linus:Talk is cheap, show me the code

程序组成

  • 程序:算法+数据结构
  • 算法:处理数据的方式
  • 数据结构:数据在计算机中的类型和组织方式
  • 数据:是程序的核心,程序为数据提供服务

程序编程风格

  • 面向过程语言

    • 做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况 A,做什么处理,如果出现了情况 B,做什么处理
    • 问题规模小,可以步骤化,按部就班处理
    • 以指令为中心,数据服务于指令
    • C, shell
  • 面向对象语言

    • 将编程看成是一个事物,对外界来说,事物是直接使用的,不用关心事物内部的情况。而编程 就是设置事物能够完成功能。
    • 一种认识世界、分析世界的方法论。将万事万物抽象为各种对象
    • 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
    • 对象是类的具体实现,是一个实体
    • 问题规模大,复杂系统
    • 以数据为中心,指令服务于数据
    • java,C#,python,golang等

编程语言

计算机:运行二进制指令
编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言

  • 低级编程语言: 机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写
    汇编:用一些助记符号替代机器指令,称为汇编语言
    如:ADD A,B 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中
    汇编语言写好的程序需要汇编程序转换成机器指令
    汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言
  • 高级编程语言:
    编译:高级语言–>编译器–>机器代码文件–>执行,如:C,C++ (编译转化成新的二进制文件)
    解释:高级语言–>执行–>解释器–>机器代码,如:shell,python,php,JavaScript,perl
    编译和解释型语言 (边运行边解释)

编程逻辑处理方式

脚本语言的基本用法

shell 脚本的用途

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

shell 脚本基本结构

shell脚本编程:是基于过程式、解释执行的语言
编程语言的基本结构:

  • 各种系统命令的组合
  • 数据存储:变量、数组
  • 表达式:a + b
  • 控制语句:if shell脚本:包含一些命令或声明,并符合一定格式的文本文件
    格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
#!/usr/bin/ruby
#!/usr/bin/lua

shell脚本创建过程

第一步:使用文本编辑器来创建文本文件
第一行必须包括shell声明序列:#! 示例:

#!/bin/bash

添加注释,注释以#开头
第二步:加执行权限
给予执行权限,在命令行上指定脚本的绝对或相对路径
第三步:运行脚本
直接运行解释器,将脚本作为解释器程序的参数运行

shell 脚本注释规范

1、第一行一般为调用使用的语言
2、程序名,避免更改文件名为无法找到正确的文件
3、版本号
4、更改后的时间
5、作者相关信息
6、该程序的作用,及注意事项
7、最后是各版本的更新简要说明

第一个 shell 脚本

#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE

范例:第一个 Shell 脚本 hello world 参考文档:

[root@centos8 ~]#vim /data/hello.sh
#!/bin/bash
# ------------------------------------------
# Filename: hello.sh
# Version: 1.0
# Date: 2017/06/01
# Author: wang
# Email: 29308620@qq.com
# Website: www.wangxiaochun.com
# Description: This is the first script
# Copyright: 2017 wang
# License: GPL
# ------------------------------------------
#经典写法
echo "hello, world"
#流行写法
echo 'Hello, world!'
#执行方法1
[root@centos8 ~]#bash /data/hello.sh
#执行方法2
[root@centos8 ~]#cat /data/hello.sh | bash
#执行方法3
[root@centos8 ~]#bash < /data/hello.sh
#执行方法4
[root@centos8 ~]#chmod +x /data/hello.sh
#绝对路径
[root@centos8 ~]#/data/hello.sh
#相法路径
[root@centos8 ~]#cd /data/
[root@centos8 ~]#./hello.sh
#执行方法5,本方法可以实现执行远程主机的shell脚本
[root@centos8 ~]#yum -y install httpd
[root@centos8 ~]#systemctl start httpd
[root@centos8 ~]#mv /data/hello.sh /var/www/html/
[root@centos8 ~]#curl http://10.0.0.8/hello.sh
#!/bin/bash
# ------------------------------------------
# Filename: hello.sh
# Version: 1.0
# Date: 2017/06/01
# Author: wang
# Email: 29308620@qq.com
# Website: www.wangxiaochun.com
# Description: This is the first script
# Copyright: 2017 wang
# License: GPL
# ------------------------------------------
#经典写法
echo "hello, world"
#流行写法
echo 'Hello, world!'
[root@centos8 ~]#curl http://10.0.0.8/hello.sh |bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 388 100 388 0 0 378k 0 --:--:-- --:--:-- --:--:-- 378k
hello, world
Hello, world!
[root@centos8 ~]#curl -s http://10.0.0.8/hello.sh |bash
hello, world
Hello, world!
[root@centos8 ~]#curl http://10.0.0.8/hello.sh 2>/dev/null |bash
hello, world
Hello, world!
[root@centos8 ~]#curl http://10.0.0.8/hello.sh >/dev/null |bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 388 100 388 0 0 378k 0 --:--:-- --:--:-- --:--:-- 378k
[root@centos8 ~]#wget -qO - http://www.wangxiaochun.com/testdir/hello.sh | bash
hello, world
Hello, world!

范例:执行远程主机的脚本

[root@centos8 ~]#curl http://www.wangxiaochun.com/testdir/hello.sh |bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 388 100 388 0 0 16869 0 --:--:-- --:--:-- --:--:-- 16869
hello, world
Hello, world!
[root@centos8 ~]#curl -s http://www.wangxiaochun.com/testdir/hello.sh |bash
[root@centos8 ~]#curl http://www.wangxiaochun.com/testdir/hello.sh 2>/dev/null |
bash
hello, world
Hello, world!
[root@centos8 ~]#wget -qO - http://www.wangxiaochun.com/testdir/hello.sh |bash
hello, world
Hello, world!

范例:在远程主机运行本地shell脚本

[root@centos8 ~]#hostname -I
10.0.0.8
[root@centos8 ~]#cat test.sh
#!/bin/bash
hostname -I
[root@centos8 ~]#ssh 10.0.0.18 /bin/bash < test.sh
root@10.0.0.18's password:
10.0.0.18

范例:备份脚本

#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-20
#FileName: backup.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
echo -e "\033[1;32mStarting backup...\033[0m"
sleep 2
cp -av /etc/ /data/etc`date +%F`/
echo -e "\033[1;32mBackup is finished\033[0m"

shell 脚本调试

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

bash -n /path/to/some_script

调试并执行

bash -x /path/to/some_script

范例:

[root@centos8 scripts]#cat -A test.sh
#!/bin/bash$
#$
#********************************************************************$
#Author:^I^Iwangxiaochun$
#QQ: ^I^I^I29308620$
#Date: ^I^I^I2020-04-01$
#FileNameM-oM-<M-^Z^I^Itest.sh$
#URL: ^I^I^Ihttp://www.magedu.com$
#DescriptionM-oM-<M-^Z^I^IThe test script$
#Copyright (C): ^I2020 All rights reserved$
#********************************************************************$
echo line1$
hostnam$
cat > test.txt <<EOF $
aaa$
bbb$
ccc$
EOF $
$
echo line22$
[root@centos8 scripts]#bash -n test.sh
f1.sh: line 20: warning: here-document at line 14 delimited by end-of-file
(wanted `EOF')

常见脚本错误

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

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

查看不可见字符

:set list cat -A

变量

变量类型:

  • 内置变量,如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE
  • 用户自定义变量 不同的变量存放的数据不同,决定了以下
  • 数据存储方式
  • 参与的运算
  • 表示的数据范围 变量数据类型:
  • 字符
  • 数值:整型、浮点型,bash 不支持浮点数

编程语言分类

q
静态和动态语言

  • 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
  • 动态编译语言:不用事先声明,可随时改变类型,如:bash,Python

强类型和弱类型语言

  • 强类型语言:不同类型数据操作,必须经过强制转换才同一类型才能运算,如java , c# , python 如:参考以下 python 代码 print(‘magedu’+ 10) 提示出错,不会自动转换类型 print(‘magedu’+str(10)) 结果为magedu10,需要显示转换类型
  • 弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会 自动进行隐式类型转换;变量无须事先定义可直接调用 如:bash ,php,javascript

Shell中变量命名法则

命名要求

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

命名习惯

  • 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
  • 变量名大写
  • 局部变量小写
  • 函数名小写
  • 大驼峰StudentFirstName,由多个单词组成,且每个单词的首字母是大写,其它小写
  • 小驼峰studentFirstName ,由多个单词组成,第一个单词的首字母小写,后续每个单词的首字母是大写,其它小写
  • 下划线: student_name 范例:
[root@rocky ~]# NAME=lan
[root@rocky ~]# echo $NAME
lan
[root@rocky ~]# AGE=18
[root@rocky ~]# echo $NAME:$AGE
lan:18
[root@rocky ~]# echo $NAME_$AGE
18
[root@rocky ~]# echo ${NAME}_$AGE
lan_18

变量定义和引用

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

  • 普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell 进程均无效
  • 环境变量:生效范围为当前shell进程及其子进程
  • 本地变量:生效范围为当前shell进程中某代码片断,通常指函数
    查看进程关系命令pstree -p
    查看自己所在进程:
[root@rocky ~]# echo $BASHPID
649758

变量赋值:

name='value'

value 可以是以下多种形式

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

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

$name
${name}

弱引用和强引用

  • “$name” 弱引用,其中的变量引用会被替换为变量值
  • ‘*$name’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
    范例:变量的各种赋值方式和引用
[root@centos8 ~]#TITLE='cto'
[root@centos8 ~]#echo $TITLE
cto
[root@centos8 ~]#echo I am $TITLE
I am cto
[root@centos8 ~]#echo "I am $TITLE"
I am cto
[root@centos8 ~]#echo 'I am $TITLE'
I am $TITLE
[root@centos8 ~]#NAME=$USER
[root@centos8 ~]#echo $NAME
root
[root@centos8 ~]#USER=`whoami`
[root@centos8 ~]#echo $USER
root
[root@centos8 ~]#FILE=`ls /run`
[root@centos8 ~]#echo $FILE
agetty.reload atd.pid auditd.pid autofs.fifo-misc autofs.fifo-net console
cron.reboot cryptsetup dbus faillock fsck initctl initramfs lock log mount
NetworkManager plymouth rsyslogd.pid screen sepermit setrans sshd.pid sssd.pid
sudo systemd tmpfiles.d tuned udev user utmp vmware
[root@centos8 ~]#FILE=/etc/*
[root@centos8 ~]#echo $FILE
/etc/adjtime /etc/aliases /etc/alternatives /etc/anacrontab /etc/at.deny
/etc/audit /etc/authselect /etc/autofs.conf /etc/autofs_ldap_auth.conf
[root@centos8 ~]#seq 10
1
2
3
4
5
6
7
8
9
10
[root@centos8 ~]#NUM=`seq 10`
[root@centos8 ~]#echo $NUM
1 2 3 4 5 6 7 8 9 10
[root@centos8 ~]#echo "$NUM"
1
2
3
4
5
6
7
8
9
10
[root@centos8 ~]#NAMES="wang
> zhang
> zhao
> li"
[root@centos8 ~]#echo $NAMES
wang zhang zhao li
[root@centos8 ~]#echo "$NAMES"
wang
zhang
zhao
li

范例:变量引用

[root@centos8 data]#NAME=mage
[root@centos8 data]#AGE=20
[root@centos8 data]#echo $NAME
mage
[root@centos8 data]#echo $AGE
20
[root@centos8 data]#echo $NAME $AGE
mage 20
[root@centos8 data]#echo $NAME$AGE
mage20
[root@centos8 data]#echo $NAME_$AGE
20
[root@centos8 data]#echo ${NAME}_$AGE
mage_20

范例:变量的间接赋值和引用

[root@centos8 ~]#TITLE=cto
[root@centos8 ~]#NAME=wang
[root@centos8 ~]#TITLE=$NAME
[root@centos8 ~]#echo $NAME
wang
[root@centos8 ~]#echo $TITLE
wang
[root@centos8 ~]#NAME=mage
[root@centos8 ~]#echo $NAME
mage
[root@centos8 ~]#echo $TITLE
wang

范例:变量追加值

[root@centos8 ~]#TITLE=CTO
[root@centos8 ~]#TITLE+=:wang
[root@centos8 ~]#echo $TITLE
CTO:wang

范例:利用变量实现动态命令

[root@centos8 ~]#CMD=hostname
[root@centos8 ~]#$CMD
centos8.wangxiaochun.com

显示已定义的所有变量:

set

删除变量:

unset <name>

范例:

[root@centos8 ~]#NAME=mage
[root@centos8 ~]#TITLE=ceo
[root@centos8 ~]#echo $NAME $TITLE
mage ceo
[root@centos8 ~]#unset NAME TITLE
[root@centos8 ~]#echo $NAME $TITLE

范例:显示系统信息

[root@centos8 scripts]#cat systeminfo.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-23
#FileName: systeminfo.sh
#URL: http://www.magedu.com
#Description: Show system information
#Copyright (C): 2019 All rights reserved
#********************************************************************
RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"
. /etc/os-release
$GREEN----------------------Host systeminfo--------------------$END
echo -e "HOSTNAME: $RED`hostname`$END"
#echo -e "IPADDR: $RED` ifconfig eth0|grep -Eo '([0-9]{1,3}\.){3}[0-9]
{1,3}' |head -n1`$END"
echo -e "IPADDR: $RED` hostname -I`$END"
echo -e "OSVERSION: $RED$PRETTY_NAME$END"
echo -e "KERNEL: $RED`uname -r`$END"
echo -e "CPU: $RED`lscpu|grep '^Model name'|tr -s ' '|cut -d : -
f2`$END"
echo -e "MEMORY: $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e "DISK: $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
$GREEN---------------------------------------------------------$END
[root@centos8 scripts]#cat system_info.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-23
#FileName: systeminfo.sh
#URL: http://www.magedu.com
#Description: Show system information
#Copyright (C): 2019 All rights reserved
#********************************************************************
RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"
$GREEN----------------------Host systeminfo--------------------$END
echo -e "HOSTNAME: $RED`hostname`$END"
echo -e "IPADDR: $RED` ifconfig eth0|grep -Eo '([0-9]{1,3}\.){3}[0-9]
{1,3}' |head -n1`$END"
echo -e "OSVERSION: $RED`cat /etc/redhat-release`$END"
echo -e "KERNEL: $RED`uname -r`$END"
echo -e "CPU: $RED`lscpu|grep 'Model name'|tr -s ' '|cut -d : -f2`$END"
echo -e "MEMORY: $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e "DISK: $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
$GREEN---------------------------------------------------------$END

范例:

[root@centos8 script]#cat backup.sh
#!/bin/bash
#Author: wang
#Date: 2020-08-05
#Description: test
#FILE: backup.sh
#VERSION: 1.0
COLOR='echo -e \E[1;35m'
END='\E[0m'
BACKUP=/backup
SRC=/etc
DATE=`date +%F`
${COLOR}Starting backup...$END
sleep 2
cp -av $SRC ${BACKUP}${SRC}_$DATE
${COLOR}Backup is finished$END

练习:

  • 1、编写脚本 systeminfo.sh,显示当前主机系统信息,包括:主机名,IPv4地址,操作系统版本,内核 版本,CPU型号,内存大小,硬盘大小
  • 2、编写脚本 backup.sh,可实现每日将 /etc/ 目录备份到 /backup/etcYYYY-mm-dd中
  • 3、编写脚本 disk.sh,显示当前硬盘分区中空间利用率最大的值
  • 4、编写脚本 links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排 序

环境变量

环境变量:

  • 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
  • 一般只在系统配置文件中使用,在脚本中较少使用 变量声明和赋值:
#声明并赋值
export name=VALUE
declare -x name=VALUE
#或者分两步实现
name=VALUE
export name

变量引用:

$name
${name}

显示所有环境变量:

env
printenv
export
declare -x

查看指定进程的环境变量

cat /proc/$PID/environ

删除变量:

unset name

bash内建的环境变量

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

范例: 查看进程的环境变量

[root@centos8 ~]#cat /proc/1235/environ
USER=rootLOGNAME=rootHOME=/rootPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/us
r/binSHELL=/bin/bashTERM=linuxSSH_AUTH_SOCK=/tmp/ssh-
iIeuAxdLiY/agent.1234XDG_SESSION_ID=1XDG_RUNTIME_DIR=/run/user/0DBUS_SESSION_BUS
_ADDRESS=unix:path=/run/user/0/busSSH_CLIENT=10.0.0.1 13258
22SSH_CONNECTION=10.0.0.1 13258 10.0.0.8 22SSH_TTY=/dev/pts/0[root@centos8 ~]#
[root@centos8 ~]#
[root@centos8 ~]#cat /proc/1235/environ |tr '\0' '\n'
USER=root
LOGNAME=root
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
SHELL=/bin/bash
TERM=linux
SSH_AUTH_SOCK=/tmp/ssh-iIeuAxdLiY/agent.1234
XDG_SESSION_ID=1
XDG_RUNTIME_DIR=/run/user/0
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus
SSH_CLIENT=10.0.0.1 13258 22
SSH_CONNECTION=10.0.0.1 13258 10.0.0.8 22
SSH_TTY=/dev/pts/0

只读变量

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

readonly name
declare -r name

查看只读变量:

readonly [-p]
declare -r

范例:

[root@centos8 ~]#readonly PI=3.14159
[root@centos8 ~]#echo $PI
3.14159
[root@centos8 ~]#PI=3.14
-bash: PI: readonly variable
[root@centos8 ~]#unset PI
-bash: unset: PI: cannot unset: readonly variable
[root@centos8 ~]#echo $PI
3.14159
[root@centos8 ~]#exit
logout
Connection closed by foreign host.
Disconnected from remote host(10.0.0.8) at 14:27:04.
Type `help' to learn how to use Xshell prompt.
[c:\~]$
Reconnecting in 1 seconds. Press any key to exit local shell.
.
Connecting to 10.0.0.8:22...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.
WARNING! The remote SSH server rejected X11 forwarding request.
Last login: Wed Apr 1 13:51:28 2020 from 10.0.0.1
[root@centos8 ~]#echo $PI

位置变量

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

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

清空所有位置变量

set --

范例:

[root@centos8 ~]#cat /data/scripts/arg.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-01
#FileName: arg.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
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@centos8 ~]#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

范例:删库跑路之命令rm的安全实现

[root@centos8 ~]#cat /data/scripts/rm.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-01
#FileName: rm.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
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
[root@centos8 ~]#chmod a+x /data/scripts/rm.sh
[root@centos8 ~]#alias rm='/data/scripts/rm.sh'
[root@centos8 ~]#touch {1..10}.txt
[root@centos8 ~]#rm *.txt
Move 10.txt 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt to /tmp/2020-
04-01_15-15-28

范例:$*和$@的区别

[root@centos8 scripts]#cat f1.sh
#!/bin/bash
echo "f1.sh:all args are $@"
echo "f1.sh:all args are $*"
./file.sh "$*"
[root@centos8 scripts]#cat f2.sh
#!/bin/bash
echo "f2.sh:all args are $@"
echo "f2.sh:all args are $*"
./file.sh "$@"
[root@centos8 scripts]#cat file.sh
#!/bin/bash
echo "file.sh:1st arg is $1"
[root@centos8 scripts]#./f1.sh a b c
f1.sh:all args are a b c
f1.sh:all args are a b c
file.sh:1st arg is a b c
[root@centos8 scripts]#./f2.sh a b c
f2.sh:all args are a b c
f2.sh:all args are a b c
file.sh:1st arg is a

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

[root@centos8 ~]#cat test.sh
#!/bin/bash
#********************************************************************
echo $0
[root@centos8 ~]#ln -s test.sh a.sh
[root@centos8 ~]#ln -s test.sh b.sh
[root@centos8 ~]#./a.sh
./a.sh
[root@centos8 ~]#./b.sh
./b.sh

退出状态码变量

当我们浏览网页时,有时会看到下图所显示的数字,表示网页的错误信息,我们称为状态码,在shell脚 本中也有相似的技术表示程序执行的相应状态。
进程执行后,将使用变量 $? 保存状态码的相关数字,不同的值反应成功或失败,$?取值范例 0-255

$?的值为0 #代表成功
$?的值是1到255 #代表失败

范例:

ping -c1 -W1 hostdown &> /dev/null
echo $?

范例:

[root@centos8 ~]#curl -fs http://www.wangxiaochun.com >/dev/null
[root@centos8 ~]#echo $?
0

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

exit [n]

注意:

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

展开命令行

展开命令执行顺序

把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配符*、?、[abc]等等
准备I/0重导向 <、>
运行命令

防止扩展

反斜线(\)会使随后的字符按原意解释

范例:

[root@centos8 ~]#echo Your cost: \$5.00
Your cost: $5.00
[root@rocky8 ~]#echo "The book's price is \$10"
The book's price is $10

加引号来防止扩展

单引号(’’)防止所有扩展
双引号(”“)也可防止扩展,但是以下情况例外:$(美元符号)

变量扩展

`` : 反引号,命令替换
\:反斜线,禁止单个字符扩展
!:叹号,历史命令替换

范例: ``和$() 区别

[root@centos8 ~]#echo `echo \`
> ^C
[root@centos8 ~]#echo `echo \\`
[root@centos8 ~]#echo `echo \\\`
> ^C
[root@centos8 ~]#echo `echo \\\\`
\
[root@centos8 ~]#echo $(echo \)
> ^C
[root@centos8 ~]#echo $(echo \\)
\
[root@centos8 ~]#echo $(echo \\\)
> ^C
[root@centos8 ~]#echo $(echo \\\\)
\\

脚本安全和 set

set 命令:可以用来定制 shell 环境
$- 变量
h:hashall,打开选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选
项关闭
i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的 shell。所谓的交互式shell, 在脚本中,i选项是关闭的
m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等
B:braceexpand,大括号扩展
H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的 一个历史命令,“!n”返回第 n 个历史命令
范例:

[root@centos8 ~]#echo $-
himBHs
[root@centos8 ~]#set +h
[root@centos8 ~]#echo $-
imBHs
[root@centos8 ~]#hash
-bash: hash: hashing disabled
[root@centos8 ~]#echo {1..10}
1 2 3 4 5 6 7 8 9 10
[root@centos8 ~]#echo $-
imBHs
[root@centos8 ~]#set +B
[root@centos8 ~]#echo $-
imHs
[root@centos8 ~]#echo {1..10}
{1..10}

set 命令实现脚本安全
-u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit
-o option 显示,打开或者关闭选项
显示选项:set -o
打开选项:set -o 选项
关闭选项:set +o 选项
-x 当执行命令时,打印命令及其参数,类似 bash -x
范例:

[root@centos8 ~]#set -o
allexport off
braceexpand on
emacs on
errexit off
errtrace off
functrace off
hashall on
histexpand on
history on
ignoreeof off
interactive-comments on
keyword off
monitor on
noclobber off
noexec off
noglob off
nolog off
notify off
nounset off
onecmd off
physical off
pipefail off
posix off
privileged off
verbose off
vi off
xtrace off

范例:

DIR=/data
cd $DIR
rm -rf *
#rm -rf $DIr/*

格式化输出 printf

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

printf "指定的格式" "文本1" ”文本2“……
printfformatitem1item2
printf“%s\n”abcdef

常用格式替换符

替换符功能
%s字符串
%d, %i十进制整数
%f浮点格式
%cASCII字符,即显示对应参数的第一个字符
%b相应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%o八进制值
%u不带正负号的十进制值
%x十六进制值 (a-f)
%X十六进制值 (A-F)
%%表示%本身
说明:
%#s 中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,- 表示
左对齐
%03d 表示3位宽度,不足前面用0补全,超出位数原样输出
%.2f 中的2表示小数点后显示的小数位数

常用转义字符

转义符功能
\a警告字符,通常为ASCII的BEL字符
\b后退
\f换页
\n换行
\r回车
\t水平制表符
\v垂直制表符
\表示\本身

范例:

[root@centos8 ~]#printf "%s" 1 2 3 4
1234[root@centos8 ~]#
[root@centos8 ~]#printf "%s\n" 1 2 3 4
1
2
3
4
[root@centos8 ~]#printf "%f\n" 1 2 3 4
1.000000
2.000000
3.000000
4.000000
#.2f 表示保留两位小数
[root@centos8 ~]#printf "%.2f\n" 1 2 3 4
1.00
2.00
3.00
4.00
[root@centos8 ~]#printf "(%s)" 1 2 3 4;echo
(1)(2)(3)(4)
[root@centos8 ~]#printf " (%s) " 1 2 3 4;echo ""
(1) (2) (3) (4)
[root@centos8 ~]#printf "(%s)\n" 1 2 3 4
(1)
(2)
(3)
(4)
[root@centos8 ~]#printf "%s %s\n" 1 2 3 4
1 2
3 4
[root@centos8 ~]#printf "%s %s %s\n" 1 2 3 4
1 2 3
4
#%-10s 表示宽度10个字符,左对齐
[root@centos8 ~]#printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70
小红 女 18 50
姓名 性别 年龄 体重
小明 男 20 70
小红 女 18 50
#将十进制的17转换成16进制数
[root@centos8 ~]#printf "%X" 17
11[root@centos8 ~]#
#将十六进制C转换成十进制
[root@centos8 ~]#printf "%d\n" 0xC
12
[root@rocky8 ~]#VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m " $VAR
welcome to Magedu
[root@centos8 ~]#VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m\n" $VAR
welcome
to
Magedu
[root@centos8 ~]#VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m\n" "$VAR"
welcome to Magedu

算术运算

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

+ - addition, subtraction
* / % multiplication, division, remainder, %表示取模,即取余数,示例:9%4=15%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

乘法符号有些场景中需要转义
实现算术运算:

(1) let var=算术表达式
(2) ((var=算术表达式)) 和上面等价
(3) var=$[算术表达式]
(4) var=$((算术表达式))
(5) var=$(expr arg1 arg2 arg3 ...)
(6) declare -i var = 数值
(7) echo '算术表达式' | bc

随机数生成器变量

内建的随机数生成器变量:

$RANDOM 取值范围:0-32767

范例:

#生成 31 - 37 之间随机数
[root@rocky ~]# echo $[RANDOM%7+31]   对7取余数后+31
#随机字体颜色
[root@rocky ~]# echo -e "\E[1;$[RANDOM%7+31]mhello\E[0m"

增强型赋值

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

格式:

let varOPERvalue

范例:

[root@centos8 ~]#let i=10*2
[root@centos8 ~]#echo $i
20
[root@centos8 ~]#((j=i+10))
[root@centos8 ~]#echo $j
30

范例:

#自加3后自赋值
let count+=3
[root@centos8 ~]#i=10
[root@centos8 ~]#let i+=20 #相当于let i=i+20
[root@centos8 ~]#echo $i
30
[root@centos8 ~]#j=20
[root@centos8 ~]#let i*=j
[root@centos8 ~]#echo $i
600

范例:

#自增,自减
let var+=1
let var++
let var-=1
let var--
[root@centos8 ~]#unset i j ; i=1; let j=i++; echo "i=$i,j=$j"  i赋值给j再自增
i=2,j=1
[root@centos8 ~]#unset i j ; i=1; let j=++i; echo "i=$i,j=$j"   i自增再赋值给j
i=2,j=2

范例:

[root@centos8 ~]#expr 2 * 3
expr: syntax error: unexpected argument ‘anaconda-ks.cfg’
[root@centos8 ~]#ls
anaconda-ks.cfg
[root@centos8 ~]#expr 2 \* 3
6

范例:

[root@centos8 ~]#echo "scale=3;20/3"|bc
6.666

范例:

[root@centos8 ~]#i=10
[root@centos8 ~]#j=20
[root@centos8 ~]#declare -i result=i*j
[root@centos8 ~]#echo $result
200

范例:鸡兔同笼,是中国古代著名典型趣题之一,记载于《孙子算经》之中。今有雉兔同笼,上有三十 五头,下有九十四足,问雉兔各几何?

[root@centos8 scripts]#cat chook_rabbit.sh
#!/bin/bash
HEAD=$1
FOOT=$2
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHOOK=$[HEAD-RABBIT]
echo RABBIT:$RABBIT
echo CHOOK:$CHOOK
[root@centos8 scripts]#./chook_rabbit.sh 30 80
RABBIT:10
CHOOK:20

逻辑运算

与或非

与逻辑 一假则假 全真则真或逻辑 一真则真,全假则假非逻辑 假则真,真则假异或逻辑 相同为假,不同为真
ABFABFAFABF
00000001000
010011110
100101011
11111110101

异或逻辑:相同为假,不同为真

true, false

1,真
0,假
#注意,以上为二进制

与:&

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

0 与 0 = 0
0 与 1 = 0
1 与 0 = 0
1 与 1 = 1

范例:

[root@ubuntu1804 ~]#x=$[2&6]
[root@ubuntu1804 ~]#echo $x
2
[root@ubuntu1804 ~]#x=$[7&3]
[root@ubuntu1804 ~]#echo $x
3

或:|

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

0 或 0 = 0
0 或 1 = 1
1 或 0 = 1
1 或 1 = 1

范例:

[root@ubuntu1804 ~]#x=$[7|3]
[root@ubuntu1804 ~]#echo $x
7
[root@ubuntu1804 ~]#x=$[2|5]
[root@ubuntu1804 ~]#echo $x
7

非:!

! 1 = 0 ! true
! 0 = 1 ! false

异或:^

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

0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0

范例:

[root@centos8 ~]#true
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#false
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#! true
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#! false
[root@centos8 ~]#echo $?
0

范例: 变量互换

[root@centos8 ~]#x=10;y=20;temp=$x;x=$y;y=$temp;echo x=$x,y=$y
x=20,y=10
[root@centos8 ~]#x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo x=$x,y=$y
x=20,y=10

短路运算

  • 短路与 &&
CMD1 && CMD2
第一个CMD1结果为真(1),第二个CMD2必须要参与运算,才能得到最终的结果
第一个CMD1结果为假(0),总的结果必定为0,因此不需要执行CMD2
  • 短路或 ||
CMD1 || CMD2
第一个CMD1结果为真(1),总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为假(0),第二个CMD2 必须要参与运算,才能得到最终的结果
  • 短路与和或组合
CMD1 && CMD2 || CMD3
当CMD1执行成功时,会执行CMD2
当CMD1执行失败时,会执行CMD3
注意: CMD1 || CMD2 && CMD3 逻辑不通,不使用

条件测试命令

条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成
测试过程,实现评估布尔声明,以便用在条件性环境下进行执行
若真,则状态码变量 $? 返回0
若假,则状态码变量 $? 返回1
条件测试命令

  • test EXPRESSION
  • [ EXPRESSION ] #和test 等价,建议使用 [ ]
  • [[ EXPRESSION ]] 相关于增强版的 [ ], 支持[]的用法,且支持扩展正则表达式和通配符 注意:EXPRESSION前后必须有空白字符
    帮助:
[root@ocky9 ~]#type [
[ is a shell builtin
[root@centos8 ~]#help [
[: [ arg... ]
Evaluate conditional expression.
This is a synonym for the "test" builtin, but the last argument must
be a literal `]', to match the opening `['.
[root@rocky9 ~]# help test
测试:test [表达式] 评估条件表达式。

根据EXPR的评估结果,退出状态为0(真)或1(假)。表达式可以是单目或双目的。单目表达式常用于检查文件的状态。还包括字符串操作符和数值比较操作符。

test的行为取决于参数的数量。阅读bash手册页以获取完整规范。

文件操作符:

  -a FILE        如果文件存在,则为真。
  -b FILE        如果文件是块特殊文件,则为真。
  -c FILE        如果文件是字符特殊文件,则为真。
  -d FILE        如果文件是一个目录,则为真。
  -e FILE        如果文件存在,则为真。
  -f FILE        如果文件存在且是一个普通文件,则为真。
  -g FILE        如果文件设置了组ID,则为真。
  -h FILE        如果文件是一个符号链接,则为真。
  -L FILE        如果文件是一个符号链接,则为真。
  -k FILE        如果文件设置了其“粘滞”位,则为真。
  -p FILE        如果文件是一个命名管道,则为真。
  -r FILE        如果文件对你可读,则为真。
  -s FILE        如果文件存在且不为空,则为真。
  -S FILE        如果文件是一个套接字,则为真。
  -t FD          如果FD是在终端上打开的,则为真。
  -u FILE        如果文件设置了用户ID,则为真。
  -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的硬链接,则为真。

    字符串操作符:

      -z STRING      如果字符串为空,则为真。

      -n STRING
         STRING      如果字符串不为空,则为真。

  STRING1 = STRING2
                 如果两个字符串相等,则为真。
  STRING1 != STRING2
                 如果两个字符串不相等,则为真。
  STRING1 < STRING2
                 如果STRING1在字典顺序上排在STRING2之前,则为真。
  STRING1 > STRING2
                 如果STRING1在字典顺序上排在STRING2之后,则为真。

    其他操作符:

  -o OPTION      如果shell选项OPTION被启用,则为真。
  -v VAR         如果shell变量VAR已被设置,则为真。
  -R VAR         如果shell变量VAR已被设置并且是一个名称引用,则为真。
  ! EXPR         如果EXPR为假,则为真。
  EXPR1 -a EXPR2 如果EXPR1和EXPR2都为真,则为真。
  EXPR1 -o EXPR2 如果EXPR1或EXPR2为真,则为真。

  arg1 OP arg2   算术测试。OP是以下之一:-eq, -ne, -lt, -le, -gt, 或 -ge。

    算术二元操作符如果ARG1等于、不等于、小于、小于等于、大于或大于等于ARG2,则返回真。

    退出状态: 如果EXPR评估为真,则返回成功;如果EXPR评估为假或给出了无效参数,则失败。

变量测试

#判断 NAME 变量是否定义
[ -v NAME ]

范例:

[root@centos8 ~]#unset x
[root@centos8 ~]#test -v x
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#x=10
[root@centos8 ~]#test -v x
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#y=
[root@centos8 ~]#test -v y
[root@centos8 ~]#echo $?
0
#注意 [ ] 中需要空格,否则会报下面错误
[root@centos8 ~]#[-v name]
-bash: [-v: command not found
[root@centos8 ~]#[ -v name ]
[root@centos8 ~]#echo $?
0

数值测试

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

范例:

[root@centos8 ~]#i=10
[root@centos8 ~]#j=8
[root@centos8 ~]#[ $i -lt $j ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[ $i -gt $j ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ i -gt j ]
-bash: [: i: integer expression expected

算术表达式比较

== 相等
!= 不相等
<=
>=
<
>

范例:

[root@centos8 ~]#x=10;y=10;(( x == y ));echo $?
0
[root@centos8 ~]#x=10;y=20;(( x == y ));echo $?
1
[root@centos8 ~]#x=10;y=20;(( x != y ));echo $?
0
[root@centos8 ~]#x=10;y=10;(( x != y ));echo $?
1

范例:

[root@centos8 ~]#x=10;y=20;(( x > y ));echo $?
1
[root@centos8 ~]#x=10;y=20;(( x < y ));echo $?
0

字符串测试

test和 [ ] 字符串测试用法

-z STRING 字符串是否为空,没定义或空为真,不空为假,
-n STRING 字符串是否不空,不空为真,空为假
STRING 同上
STRING1 = STRING2 是否等于,注意 = 前后有空格
STRING1 != STRING2 是否不等于
> ascii码是否大于ascii码
< 是否小于

[[]] 字符串测试用法

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

建议:当使用正则表达式或通配符使用[[ ]],其它情况一般使用 [ ] 范例:使用 [ ]

[root@centos8 ~]#unset str
[root@centos8 ~]#[ -z "$str" ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#str=""
[root@centos8 ~]#[ -z "$str" ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#str=" "
[root@centos8 ~]#[ -z "$str" ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[ -n "$str" ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#unset str
[root@centos8 ~]#[ -n "$str" ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[ "$str" ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#str=magedu
[root@centos8 ~]#[ "$str" ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#str=magedu
[root@centos8 ~]#[ "$str" ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#str1=magedu
[root@centos8 ~]#str2=mage
[root@centos8 ~]#[ $str1 = $str2 ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#str2=magedu
[root@centos8 ~]#[ $str1 = $str2 ]
[root@centos8 ~]#echo $?
0

范例:在比较字符串时,建议变量放在“ ”中

[root@centos8 ~]#[ "$NAME" ]
[root@centos8 ~]#NAME="I love linux"
[root@centos8 ~]#[ $NAME ]
-bash: [: love: binary operator expected
[root@centos8 ~]#[ "$NAME" ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ I love linux ]
-bash: [: love: binary operator expected

范例: [[ ]] 和通配符

[root@centos8 ~]#FILE="a*"
[root@centos8 ~]#echo $FILE
a*
[root@centos8 ~]#[[ $FILE == a* ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#FILE="ab"
[root@centos8 ~]#[[ $FILE == a* ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#FILE="a*"
#[[]]中如果不想使用通配符*,只想表达*本身,可以用" "引起来
[root@centos8 ~]#[[ $FILE == a"*" ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#FILE="ab"
[root@centos8 ~]#[[ $FILE == a"*" ]]
[root@centos8 ~]#echo $?
1
#[[]]中如果不想使用通配符*,只想表达*本身,也可以使用转义符
[root@centos8 ~]#[[ $FILE == a\* ]]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#FILE="a\b"
[root@centos8 ~t]#[[ $FILE == a\* ]]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#FILE="a*"
[root@centos8 ~]#[[ $FILE == a\* ]]
[root@centos8 ~]#echo $?
0
#通配符?
[root@centos8 script]#FILE=abc
[root@centos8 script]#[[ $FILE == ??? ]]
[root@centos8 script]#echo $?
0
[root@centos8 script]#FILE=abcd
[root@centos8 script]#[[ $FILE == ??? ]]
[root@centos8 script]#echo $?
1
#通配符
[root@centos8 ~]#NAME="linux1"
[root@centos8 ~]#[[ "$NAME" == linux* ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[[ "$NAME" == "linux*" ]]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#NAME="linux*"
[root@centos8 ~]#[[ "$NAME" == "linux*" ]]
[root@centos8 ~]#echo $?
0
#结论:[[ == ]] == 右侧的 * 做为通配符,不要加“”,只想做为*符号使用时, 需要加 “” 或转义

范例: 判断合理的考试成绩

[root@centos8 script]#SCORE=101
[root@centos8 script]#[[ $SCORE =~ 100|[0-9]{1,2} ]]
[root@centos8 script]#echo $?
0
[root@centos8 script]#[[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
[root@centos8 script]#echo $?
1
[root@centos8 script]#SCORE=10
[root@centos8 script]#[[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
[root@centos8 script]#echo $?
0
[root@centos8 script]#SCORE=abc
[root@centos8 script]#[[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
[root@centos8 script]#echo $?
1

范例:使用 [[ ]] 判断文件后缀

#通配符
[root@centos8 ~]#FILE=test.log
[root@centos8 ~]#[[ "$FILE" == *.log ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#FILE=test.txt
[root@centos8 ~]#[[ "$FILE" == *.log ]]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[[ "$FILE" != *.log ]]
[root@centos8 ~]#echo $?
0
#正则表达式
[root@centos8 ~]#[[ "$FILE" =~ \.log$ ]]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#FILE=test.log
[root@centos8 ~]#[[ "$FILE" =~ \.log$ ]]
[root@centos8 ~]#echo $?
0

范例: 判断合法的非负整数

[root@centos8 ~]#N=100
[root@centos8 ~]#[[ "$N" =~ ^[0-9]+$ ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#N=Magedu10
[root@centos8 ~]#[[ "$N" =~ ^[0-9]+$ ]]
[root@centos8 ~]#echo $?
1

范例: 判断合法IP

[root@centos8 ~]#IP=1.2.3.4
[root@centos8 ~]#[[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#IP=1.2.3.4567
[root@centos8 ~]#[[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[[ $IP =~ ^(([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-5])$ ]]
[root@centos8 ~]#echo $?
1

范例:

[root@centos7 ~]#cat check_ip.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-12-11
#FileName: check_ip.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
IP=$1
[[ $IP =~ ^(([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-5])$ ]] && echo $IP is valid || echo $IP is invalid

文件测试

存在性测试

-a FILE:同 -e
-e FILE: 文件存在性测试,存在为真,否则为假
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件

范例: -e和-a 表示判断文件的存在性,建议使用-e

#文件是否不存在
[root@centos8 ~]#[ -a /etc/nologin ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#! [ -a /etc/nologin ]
[root@centos8 ~]#echo $?
0
#文件是否存在
[root@centos8 ~]# [ -a /etc/issue ]
[root@centos8 ~]#echo $?
0
#取反后结果却没有变化
[root@centos8 ~]# [ ! -a /etc/issue ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#! [ -a /etc/issue ]
[root@centos8 ~]#echo $?
1
#文件是否存在
[root@centos8 ~]#! [ -e /etc/issue ]
[root@centos8 ~]#echo $?
1
#此为推荐写法
[root@centos8 ~]#[ ! -e /etc/issue ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[ -d /etc ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ -d /etc/issue ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[ -L /bin ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ -L /bin/ ]
[root@centos8 ~]#echo $?
1

文件权限测试:

-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限

注意:最终结果由用户对文件的实际权限决定,而非文件属性决定 范例:

[root@centos8 ~]#[ -w /etc/shadow ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ -x /etc/shadow ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#[ -w test.txt ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#chattr +i test.txt
[root@centos8 ~]#lsattr test.txt
----i-------------- nianling.txt
[root@centos8 ~]#[ -w test.txt ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#chattr -i test.txt
[root@centos8 ~]#[ -w test.txt ]
[root@centos8 ~]#echo $?
0

文件属性测试

-s FILE #是否存在且非空
-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

关于 () 和 { }

(CMD1;CMD2;…)和 { CMD1;CMD2;…; } 都可以将多个命令组合在一起,批量执行

[root@centos8 ~]#man bash
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境
帮助参看:man bash 搜索(list)
{ list; } 不会启子shell, 在当前shell中运行,会影响当前shell环境
帮助参看:man bash 搜索{ list; }

范例: () 和 {}

[root@centos8 ~]#name=mage;(echo $name;name=wang;echo $name );echo $name
mage
wang
mage
[root@centos8 ~]#name=mage;{ echo $name;name=wang;echo $name; } ;echo $name
mage
wang
wang
[root@centos8 ~]#umask
0022
[root@centos8 ~]#(umask 066;touch f1.txt)
[root@centos8 ~]#ll f1.txt
-rw------- 1 root root 0 Dec 23 16:58 f1.txt
[root@centos8 ~]#umask
0022
[root@centos8 ~]#( cd /data;ls )
test.log
[root@centos8 ~]#pwd
/root
[root@centos8 ~]#{ cd /data;ls; }
test.log
[root@centos8 data]#pwd
/data
[root@centos8 data]#
#()会开启子shell
[root@centos8 ~]#echo $BASHPID
1920
[root@centos8 ~]#( echo $BASHPID;sleep 100)
1979
[root@centos8 ~]#pstree -p
├─sshd(719)───sshd(1906)───sshd(1919)─┬─bash(1920)───bash(1979)───sleep(1980)
#{ } 不会开启子shell
[root@centos8 ~]#echo $BASHPID
1920
[root@centos8 ~]#{ echo $BASHPID; }
1920

组合测试条件

第一种方式

[ EXPRESSION1 -a EXPRESSION2 ] #并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] #或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为
[ ! EXPRESSION ] #取反

说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持 范例:

[root@centos8 ~]#FILE="/data/scrips/test.sh"
[root@centos8 ~]#ll /data/scrips/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh
[root@centos8 ~]#[ -f $FILE -a -x $FILE ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#chmod +x /data/scripts/test.sh
[root@centos8 ~]#ll /data/scripts/test.sh
-rwxr-xr-x 1 root root 382 Dec 23 09:32 /data/script40/test.sh
#并且
[root@centos8 ~]#[ -f $FILE -a -x $FILE ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#chmod -x /data/scripts/test.sh
[root@centos8 ~]#ll /data/scripts/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh
#或者
[root@centos8 ~]#[ -f $FILE -o -x $FILE ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ -x $FILE ]
[root@centos8 ~]#echo $?
1
#取反
[root@centos8 ~]#[ ! -x $FILE ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#! [ -x $FILE ]
0

第二种方式

COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2
COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND #非,取反

1 (2) 2 (2)

[root@centos7 ~]#[ $[RANDOM%6] -eq 0 ] && rm -rf /* || echo "click"

范例:

[root@centos8 ~]#test "A" = "B" && echo "Strings are equal"
[root@centos8 ~]#test "A"-eq "B" && echo "Integers are equal"
[root@centos8 ~]#[ "A" = "B" ] && echo "Strings are equal"
[root@centos8 ~]#[ "$A" -eq "$B" ] && echo "Integers are equal"
[root@centos8 ~]#[ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
[root@centos8 ~]#[ -z "$HOSTNAME" -o "$HOSTNAME" = "localhost.localdomain" ]&&
hostname www.magedu.com
[root@centos8 ~]#id wang &> /dev/null || useradd wang
[root@centos8 ~]#id zhang &> /dev/null || useradd zhang
[root@centos8 ~]#getent passwd zhang
zhang:x:1002:1002::/home/zhang:/bin/bash
[root@centos8 ~]#grep -q no_such_user /etc/passwd || echo 'No such user'
No such user

范例:

[root@centos8 ~]#[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]] && chmod +x $FILE
[root@centos8 ~]#ping -c1 -W1 172.16.0.1 &> /dev/null && echo '172.16.0.1 is
up' || (echo '172.16.0.1 is unreachable'; exit 1)
172.16.0.1 is up
[root@centos8 ~]#IP=10.0.0.111;ping -c1 -W1 $IP &> /dev/null && echo $IP is up
|| echo $IP is down
10.0.0.111 is down
[root@centos8 ~]#IP=10.0.0.1;ping -c1 -W1 $IP &> /dev/null && echo $IP is up ||
echo $IP is down
10.0.0.1 is up

范例:&& 和 || 组合使用

[root@centos8 ~]#NAME=wang; id $NAME &> /dev/null && echo "$NAME is exist"
wang is exist
[root@centos8 ~]#NAME=wange; id $NAME &> /dev/null || echo "$NAME is not
exist"
wange is not exist
[root@centos8 ~]#NAME=wange; id $NAME &> /dev/null && echo "$NAME is exist" ||
echo "$NAME is not exist"
wange is not exist
[root@centos8 ~]#NAME=wang; id $NAME &> /dev/null && echo "$NAME is exist" ||
echo "$NAME is not exist"
wang is exist
[root@centos8 ~]#NAME=wang; id $NAME &> /dev/null && echo "$NAME is exist" ||
echo "$NAME is not exist"
wang is exist
[root@centos8 ~]#NAME=wang; id $NAME &> /dev/null || echo "$NAME is not exist"
&& echo "$NAME is exist"
wang is exist
[root@centos8 ~]#NAME=wange; id $NAME &> /dev/null || echo "$NAME is not
exist" && echo "$NAME is exist"
wange is not exist
wange is exist
#结论:如果&& 和 || 混合使用,&& 要在前,|| 放在后
[root@centos8 ~]#NAME=wange; id $NAME &> /dev/null && echo "$NAME is exist" ||
useradd $NAME
[root@centos8 ~]#id wange
uid=1002(wange) gid=1002(wange) groups=1002(wange)
[root@centos8 ~]#NAME=wangge; id $NAME &> /dev/null && echo "$NAME is exist" ||
( useradd $NAME; echo $NAME is created )
wangge is created
[root@centos8 ~]#id wangge
uid=1003(wangge) gid=1003(wangge) groups=1003(wangge)
[root@centos8 ~]#NAME=wanggege; id $NAME &> /dev/null && echo "$NAME is exist"
|| { useradd $NAME; echo $NAME is created; }
wanggege is created

范例:网络状态判断

[root@centos8 ~]#cat /data/scripts/ping.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-23
#FileName: ping.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
IP=172.16.0.1
ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || { echo "$IP is
unreachable"; exit; }
echo "Script is finished"
[root@centos8 ~]#bash /data/scripts/ping.sh
172.16.0.1 is up
Script is finished

范例:

[root@centos8 ~]#. /etc/os-release; [[ $ID == "rocky" ]] && [[ $VERSION_ID ==
8* ]] && echo Rocky8 || echo CentOS8
[root@rocky8 ~]#. /etc/os-release; [[ $ID == "rocky" ]] && [[ $VERSION_ID == 8*
]] && echo Rocky8 || echo CentOS8
Rocky8

范例:磁盘空间的判断

[root@centos8 ~]#cat /data/script/disk_check.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-23
#FileName: disk_check.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
WARNING=80
SPACE_USED=`df|grep '^/dev/sd'|tr -s ' ' %|cut -d% -f5|sort -nr|head -1`
[ "$SPACE_USED" -ge $WARNING ] && echo "disk used is $SPACE_USED,will be full"
| mail -s diskwaring root

范例:磁盘空间和Inode号的检查脚本

[root@centos8 scripts]#cat disk_check.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-03
#FileName: disk_check.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
WARNING=80
SPACE_USED=`df | grep '^/dev/sd'|grep -oE '[0-9]+%'|tr -d %| sort -nr|head -1`
INODE_USED=`df -i | grep '^/dev/sd'|grep -oE '[0-9]+%'|tr -d %| sort -nr|head
-1`
[ "$SPACE_USED" -gt $WARNING -o "$INODE_USED" -gt $WARNING ] && echo "DISK
USED:$SPACE_USED%, INODE_USED:$INODE_USED,will be full" | mail -s "DISK Warning"
root@wangxiaochun.com

练习

1、编写脚本 argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给 一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
2、编写脚本 hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提 示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”
3、编写脚本 checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
4、编写脚本 per.sh,判断当前用户对指定参数文件,是否不可读并且不可写
5、编写脚本 excute.sh ,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限, 否则提示用户非脚本文件
6、编写脚本 nologin.sh和 login.sh,实现禁止和允许普通用户登录系统

使用read命令来接受输入

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

read [options] [name ...]

常见选项:

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

范例:

[root@centos8 ~]#read
wangxiaochun
[root@centos8 ~]#echo $REPLY
wangxiaochun
[root@centos8 ~]#read NAME TITLE
wang cto
[root@centos8 ~]#echo $NAME
wang
[root@centos8 ~]#echo $TITLE
cto
[root@centos8 ~]#read -p "Please input your name: " NAME
Please input your name: wang
[root@centos8 ~]#echo $NAME
wang

范例:

[root@centos8 ~]#read x y z <<< "I love you"
[root@centos8 ~]#echo $x
I
[root@centos8 ~]#echo $y
love
[root@centos8 ~]#echo $z
you
[root@centos8 ~]#

范例:

[root@centos8 ~]#man bash
Each command in a pipeline is executed as separate process (i.e., in a subshell).
#Pipelines:A pipeline is a sequence of one or more commands separated by one of
the control operators | or |&
Each command in a pipeline is executed as a separate process (i.e., in a
subshell).
[root@centos8 ~]#echo magedu | read NAME
[root@centos8 ~]#echo $NAME
[root@centos8 ~]#echo magedu | { read NAME; echo $NAME; }
magedu

范例:面试题 read和输入重定向

[root@centos8 scripts]#cat test.txt
1 2
[root@centos8 scripts]#read i j < test.txt ; echo i=$i j=$j
i=1 j=2
[root@centos8 scripts]#echo 1 2 | read x y ; echo x=$x y=$y
x= y=       echo x=$x y=$y 为父进程,故无输出。
[root@centos8 ~]#echo 1 2 | ( read x y ; echo x=$x y=$y )
x=1 y=2
[root@centos8 ~]#echo 1 2 | { read x y ; echo x=$x y=$y; }
x=1 y=2

范例:判断用户输入的是否为 YES

root@ubuntu2004:~# cat yesorno.sh
#!/bin/bash
read -p "Please input yes or no: " input
answer=`echo $input| tr 'A-Z' 'a-z'`
[ $answer = 'yes' -o $answer = 'y' ] && echo YES
[ $answer = 'no' -o $answer = 'n' ] && echo NO
root@ubuntu2004:~# cat yesorno2.sh
#!/bin/bash
read -p "Please input yes or no: " input
[[ $input =~ ^([Yy][Ee][Ss]|[Yy])$ ]] && echo YES
[[ $input =~ ^([Nn][Oo]|[Nn])$ ]] && echo NO

范例: 检查主机的网络状态

[root@centos8 script]#cat check_host.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-08-07
#FileName: check_host.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
read -p "Please input a IP: " IP
[[ "$IP" =~ ^(([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-5])$ ]] || { echo "IP is invalid";exit; }
ping -c1 -W1 $IP &> /dev/null && echo $IP is up || echo $IP is down

范例:

read -p “Enter a filename: “ FILE

范例:鸡兔同笼算法,今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?

[root@centos8 ~]#cat /data/script40/chook_rabbit.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-23
#FileName: chook_rabbit.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
read -p "请输入头的数量: " HEAD
read -p "请输入脚的数量: " FOOT
RABBIT=$[FOOT/2-HEAD]
CHOOK=$[HEAD-RABBIT]
echo "兔子: " $RABBIT
echo "鸡: " $CHOOK

范例:实现运维工作菜单

[root@centos8 scripts]#cat work_menu.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-03
#FileName: work_menu.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
. /etc/init.d/functions
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 ] && action "清理日志"
[ $MENU -eq 3 ] && action "软件升级"
[ $MENU -eq 4 ] && action "软件回滚"
[ $MENU -eq 5 ] && action "删库跑路"

bash shell 的配置文件

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

按生效范围划分两类

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

/etc/profile
/etc/profile.d/*.sh
/etc/bashrc

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

~/.bash_profile
~/.bashrc

shell登录两种方式分类

交互式登录

  • 直接通过终端输入账号密码登录
  • 使用 su - UserName 切换的用户 配置文件生效和执行顺序:
#放在每个文件最前
/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

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

非交互式登录

  • su UserName
  • 图形界面下打开的终端
  • 执行脚本
  • 任何其它的bash实例 执行顺序:
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc

范例: 将命令放在最前面

Last login: Wed Jun 10 11:24:03 2020 from 10.0.0.1
/etc/profile
/etc/profile.d/test.sh
/etc/bashrc
~/.bash_profile
~/.bashrc
/etc/bashrc
[root@centos8 ~]#su - root
Last login: Wed Jun 10 12:26:31 CST 2020 from 10.0.0.1 on pts/0
/etc/profile
/etc/profile.d/test.sh
/etc/bashrc
~/.bash_profile
~/.bashrc
/etc/bashrc
[root@centos8 ~]#exit
logout
[root@centos8 ~]#su root
~/.bashrc
/etc/bashrc
/etc/profile.d/test.sh

范例:将命令放在最后面

Last login: Wed Jun 10 12:29:20 2020 from 10.0.0.1
/etc/profile.d/test.sh
/etc/bashrc
/etc/profile
/etc/bashrc
~/.bashrc
~/.bash_profile
[root@centos8 ~]#su - root
Last login: Wed Jun 10 12:30:03 CST 2020 from 10.0.0.1 on pts/3
/etc/profile.d/test.sh
/etc/bashrc
/etc/profile
/etc/bashrc
~/.bashrc
~/.bash_profile
[root@centos8 ~]#su root
/etc/profile.d/test.sh
/etc/bashrc
~/.bashrc

按功能划分分类

profile类和bashrc类

Profile类

profile类为交互式登录的shell提供配置

全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile

功用:
(1) 用于定义环境变量
(2) 运行命令或脚本

Bashrc类

bashrc类:为非交互式和交互式登录的shell提供配置

全局:/etc/bashrc
个人:~/.bashrc

功用:
(1) 定义命令别名和函数
(2) 定义本地变量

编辑配置文件生效

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

  1. 重新启动shell进程
  2. source|. 配置文件
    注意:source 会在当前shell中执行脚本,所有一般只用于执行置文件,或在脚本中调用另一个脚本的场景
    范例:
. ~/.bashr

Bash 退出任务

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

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

练习

  • 1、让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin
  • 2、用户 root 登录时,将命令指示符变成红色,并自动启用如下别名: rm=‘rm -i’ cdnet=‘cd /etc/sysconfig/network-scripts/’
    editnet=‘vim /etc/sysconfig/network-scripts/ifcfg-eth0’
    editnet=‘vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33 ’ (如果系统是 CentOS7)
  • 3、任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”
  • 4、编写生成脚本基本格式的脚本,包括作者,联系方式,版本,时间,描述等

流程控制

条件选择
条件判断分绍
单分支条件
多分支条件

选择执行 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

说明:

  • 多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
  • if 语句可嵌套
    范例:
#根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then
echo 'station1 is UP'
elif grep -q 'station1' ~/maintenance.txt; then
echo 'station1 is undergoing maintenance'
else
echo 'station1 is unexpectedly DOWN!'
exit 1
fi

范例:身体质量指数 (BMI)

[root@centos8 ~]#cat if_bmi.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-25
#FileName: if_bmi.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
read -p "请输入身高(m为单位): " HIGH
if [[ ! "$HIGH" =~ ^[0-2](\.[0-9]{,2})?$ ]];then
echo "输入错误的身高!"
exit 1
fi
read -p "请输入体重(kg为单位): " WEIGHT
if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]];then echo "输入错误的体重!"; exit 2; fi
BMI=`echo $WEIGHT/$HIGH^2|bc`
if [ $BMI -le 18 ] ;then
echo "太瘦了,多吃点!"
elif [ $BMI -lt 24 ] ;then
echo "身材很棒!"
else
echo "太胖了,注意节食,加强运动!"
fi

条件判断 case 语句

格式:

case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
case 变量引用 in
PAT1)
    分支1
   ;;
PAT2)
    分支2
   ;;
...
*)
   默认分支
   ;;
esac

case支持glob风格的通配符:

* 任意长度任意字符
? 任意单个字符
[] 指定范围内的任意单个字符
| 或者,如: a|b

范例:

[root@centos8 scripts]#cat case_yesorno.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-25
#FileName: case_yesorno.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
read -p "Do you agree(yes/no)? " INPUT
INPUT=`echo $INPUT | tr 'A-Z' 'a-z'`
case $INPUT in
y|yes)
      echo "You input is YES"
      ;;
n|no)
      echo "You input is NO"
      ;;
*)
      echo "Input fales,please input yes or no!"
esac
[root@centos8 scripts]#cat case_yesorno2.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2019-12-25
#FileName: case_yesorno.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2019 All rights reserved
#********************************************************************
read -p "Do you agree(yes/no)? " INPUT
case $INPUT in
[yY]|[Yy][Ee][Ss])
   echo "You input is YES"
   ;;
[Nn]|[Nn][Oo])
   echo "You input is NO"
   ;;
*)
echo "Input fales,please input yes or no!"
esac

范例: 文件后缀处理

[root@centos8 script]#cat suffix.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-08-07
#FileName: suffix.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
read -p "please input a file: " FILE
SUFFIX=`echo $FILE | grep -Eo "[^.]+$"`
case $SUFFIX in
gz)
   echo gzip
   ;;
bz2)
   echo bzip2
   ;;
xz)
   echo xz
   ;;
Z)
   echo compress
   ;;
zip)
   echo zip
   ;;
*)
   echo other
   ;;
esac

范例:运维菜单实现版本2

[root@centos8 scripts]#cat work_menu.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-03
#FileName: work_menu.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
echo -en "\E[$[RANDOM%7+31];1m"
cat <<EOF
请选择:
1)备份数据库
2)清理日志
3)软件升级
4)软件回滚
5)删库跑路
EOF
echo -en '\E[0m'
read -p "请输入上面数字1-5: " MENU
case $MENU in
1)
   echo "执行备份数据库"
   #./backup.sh
   ;;
2)
   echo "清理日志"
   ;;
3)
   echo "软件升级"
   ;;
4)
   echo "软件回滚"
   ;;
5)
   echo "删库跑路"
   ;;
*)
   echo "INPUT FALSE!"
esac

练习

1、编写脚本 createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就 显示其存在,否则添加之。并设置初始密码为123456,显示添加的用户的id号等信息,在此新用户第一 次登录时,会提示用户立即改密码,如果没有参数,就提示:请输入用户名

2、编写脚本 yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息

3、编写脚本 filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类 型)

4、编写脚本 checkint.sh,判断用户输入的参数是否为正整数

5、编写脚本 reset.sh,实现系统安装后的初始化环境,包括:1、别名 2、环境变量,如PS1等 3、 安装常用软件包,如:tree 5、实现固定的IP的设置,6、vim的设置等

循环

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

  • 循环次数事先已知
  • 循环次数事先未知 常见的循环的命令:for, while, until

循环 for

#CentOS7的for帮助比CentOS8全面
[root@centos7 ~]#help for
for: for NAME [in WORDS ... ] ; do COMMANDS; done
Execute commands for each member in a list.
The `for' loop executes a sequence of commands for each member in a
list of items. If `in WORDS ...;' is not present, then `in "$@"' is
assumed. For each element in WORDS, NAME is set to that element, and
the COMMANDS are executed.
Exit Status:
Returns the status of the last command executed.
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
Arithmetic for loop.
Equivalent to
(( EXP1 ))
while (( EXP2 )); do
COMMANDS
(( EXP3 ))
done
EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is
omitted, it behaves as if it evaluates to 1.
Exit Status:
Returns the status of the last command executed.

格式1:

for NAME [in WORDS ... ] ; do COMMANDS; done
#方式1
for 变量名 in 列表;do
    循环体
done
#方式2
for 变量名 in 列表
do
    循环体
done

执行机制:

  • 依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环 结束
  • 如果省略 [in WORDS … ] ,此时使用位置参数变量 in “$@” for 循环列表生成方式:
  • 直接给出列表
  • 整数列表:
{start..end}
$(seq [start [step]] end)
  • 返回列表的命令:
$(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@,$*,$# 范例:面试题,计算1+2+3+…+100 的结果
root@centos8 ~]#sum=0;for i in {1..100};do let sum+=i;done ;echo sum=$sum
sum=5050
[root@centos8 ~]#seq -s+ 100|bc5050
5050
[root@centos8 ~]#echo {1..100}|tr ' ' +|bc
5050
[root@centos8 ~]#seq 100|paste -sd +|bc
5050

范例: 100以内的奇数之和

[root@centos8 ~]#sum=0;for i in {1..100..2};do let sum+=i;done;echo sum=$sum
sum=2500
[root@centos8 ~]#seq -s+ 1 2 100| bc
2500
[root@centos8 ~]#echo {1..100..2}|tr ' ' + | bc
2500

范例:

[root@centos8 ~]#cat /data/scripts/for_sum.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-27
#FileName: for_sum.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
sum=0
for i in $* ; do
let sum+=i
done
echo sum=$sum
[root@centos8 ~]#bash /data/scripts/for_sum.sh 1 2 3 4 5 6
sum=21

范例: 批量创建用户

[root@centos8 ~]#cat createuser.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-12-14
#FileName: createuser.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
[ $# -eq 0 ] && { echo "Usage: createuser.sh USERNAME ..." ;exit 1 ; }
for user ;do
    id $user &> /dev/null && echo $user is exist || { useradd $user ; echo $user
is created; }
done

范例: 批量创建用户和并设置随机密码

[root@centos8 script]#cat user_for.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-08-07
#FileName: user_for.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
for i in {1..10};do
useradd user$i
PASS=`cat /dev/urandom | tr -dc '[:alnum:]' |head -c12`
echo $PASS | passwd --stdin user$i &> /dev/null
echo user$i:$PASS >> /data/user.log
echo "user$i is created"
done

范例: 九九乘法表

[root@centos8 script]#cat 9x9_for.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-08-07
#FileName: 9x9_for.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
for i in {1..9};do
    for j in `seq $i`;do
      echo -e "${j}x${i}=$[j*i]\t\c"
    done
   echo
done

范例: printf 实现九九乘法表

for i in {1..9};do
   for j in {1..9};do
       printf "%sx%s=%s\t" $j $i $[i*j]
       [ $i -eq $j ] && break
   done
   printf '\n'
done

范例: 实现九九乘法表

[root@centos8 ~]#vim 9x9.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2021-04-02
#FileName: 9x9.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2021 All rights reserved
#********************************************************************
for j in {1..9};do
    for i in `seq $j`;do
        echo -e "\E[1;$[RANDOM%7+31]m${i}x${j}=$[i*j]\E[0m\t\c"
   done
   echo
done
echo
for((i=1;i<=9;i++));do
   for((j=1;j<=i;j++));do
      printf "\E[1;$[RANDOM%7+31]m${i}x${j}=$[i*j]\E[0m\t"
   done
   printf "\n"
done

范例: 倒状的九九乘法表

[root@ubuntu1804 ~]#cat 9x9_v2.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-12-14
#FileName: 9x9.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
for i in {1..9};do
   for j in $(seq `echo $[10-$i]`);do
      echo -ne "${j}x`echo $[10-i]`=$(((10-i)*j))\t"
   done
   echo
done
[root@ubuntu1804 ~]#bash 9x9_v2.sh
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x4=4 2x4=8 3x4=12 4x4=16
1x3=3 2x3=6 3x3=9
1x2=2 2x2=4
1x1=1

生产案例:将指定目录下的文件所有文件的后缀改名为 bak 后缀

[root@centos8 ~]#cat /data/scripts/for_rename.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-01-03
#FileName: /data/script40/for_rename.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
DIR=/data/test
cd $DIR || { echo 无法进入 $DIR;exit 1; }
for FILE in * ;do
   PRE=`echo $FILE|grep -Eo ".*\."`
   mv $FILE ${PRE}bak
# PRE=`echo $FILE|rev|cut -d. -f 2-|rev`
# PRE=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\1/p'
# SUFFIX=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\2/p'`
# mv $FILE $PRE.bak
done

范例:M37期面试题,要求将目录YYYY-MM-DD/中所有文件,移动到YYYY-MM/DD/下

#1.创建YYYY-MM-DD格式的目录,当前日期一年前365天到目前共365个目录,里面有10个文件.log后缀的
文件
[root@centos8 ~]#cat for_dir.sh
#!/bin/bash
PDIR=/data/test
for i in {1..365};do
   DIR=`date -d "-$i day" +%F`
   mkdir -pv $PDIR/$DIR
   cd $PDIR/$DIR
   for j in {1..10};do
      touch $RANDOM.log
   done
done
#2.将上面的目录移动到YYYY-MM/DD/下
#!/bin/bash
#
DIR=/data/test
cd $DIR || { echo 无法进入 $DIR;exit 1; }
for subdir in * ;do
    YYYY_MM=`echo $subdir |cut -d"-" -f1,2`
    DD=`echo $subdir |cut -d"-" -f3`
    [ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
    mv $subdir/* $YYYY_MM/$DD
done
rm -rf $DIR/*-*-*

面试题:扫描一个网段:10.0.0.0/24,判断此网段中主机在线状态,将在线的主机的IP打印出来

root@centos8 ~]#cat /data/scripts/for_scan_host.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-27
#FileName: for_scan_host.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
NET=10.0.0
for ID in {1..254};do
   {
      ping -c1 -W1 $NET.$ID &> /dev/null && echo $NET.$ID is up| tee -a
host_list.log || echo $NET.$ID is down
   }&
done
wait

格式2 双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格 的变量操作 I=10;((I++))

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
   循环体
done

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次
  • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断 范例:
[root@ubuntu1804 ~]#cat sum.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-01-03
#FileName: for_sum2.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
for((sum=0,i=1;i<=100;i++));do
      let sum+=i
done
echo sum=$sum
for((sum=0,i=1;i<=100;sum+=i,i++));do
      true
done
echo sum=$sum
[root@ubuntu1804 ~]#bash sum.sh
sum=5050
sum=5050

范例:九九乘法表

#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-01-03
#FileName: for_99_2.sh
#URL: http://www.magedu.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
#语法1实现
for i in {1..9};do
   for j in `seq $i`;do
      echo -e "${j}x$i=$((j*i))\t\c"
   done
   echo
done
#语法2实现
for((i=1;i<10;i++));do
   for((j=1;j<=i;j++));do
      echo -e "${j}x$i=$((j*i))\t\c"
      done
echo
done

范例:等腰三角形

[root@centos8 scripts]#cat for_triangle.sh
#!/bin/bash
#
#********************************************************************
#Author: wangxiaochun
#QQ: 29308620
#Date: 2020-04-27
#FileName: for_triangle.sh
#URL: http://www.wangxiaochun.com
#Description: The test script
#Copyright (C): 2020 All rights reserved
#********************************************************************
read -p "请输入三角形的行数: " line
for((i=1;i<=line;i++));do
   for((k=0;k<=line-i;k++));do
      echo -e ' \c'
   done
   for((j=1;j<=2*i-1;j++));do
      echo -e '*\c'
   done
   echo
done
[root@centos8 scripts]#bash for_triangle.sh
请输入三角形的行数: 10
          *
         ***
        *****
       *******
      *********
     ***********
    *************
   ***************
  *****************
 *******************
[root@centos8 scripts]#

范例:生成进度

[root@centos8 ~]#for ((i = 0; i <= 100; ++i)); do printf "\e[4D%3d%%" $i;sleep
0.1s; done
100%[root@centos8 ~]#

范例:

[root@centos8 ~]#for((;;));do echo for;sleep 1;done
for
for
for
for
for
for
for

练习:用 for 实现

1、判断/var/目录下所有文件的类型
2、添加10个用户user1-user10,密码为8位随机字符
3、/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为 文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename start
4、编写脚本,提示输入正整数n的值,计算1+2+…+n的总和
5、计算100以内所有能被3整除的整数之和
6、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态
7、打印九九乘法表
8、在/testdir目录下创建10个html文件,文件名格式为数字N(从1到10)加随机8个字母,如: 1AbCdeFgH.html
9、打印等腰三角形
10、猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个。第二天早上又将剩下的桃子 吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,只 剩下一个桃子了。求第一天共摘了多少?