shell编程进阶

循环执行介绍

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

  • 循环次数事先已知
  • 循环次数事先未知 常见的循环的命令: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.

image

格式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
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
[ $# -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
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
for i in {1..9};do
    for j in `seq $i`;do
        echo -e "${j}x${i}=$[j*i]\t\c"
    done
    echo
done

范例: 实现九九乘法表

[root@centos8 ~]#vim 9x9.sh
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
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
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
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++)) image

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done

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

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次
  • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断 范例: image image image image image

练习:用 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天早上想再吃时,只 剩下一个桃子了。求第一天共摘了多少?

循环 while

1-241205154214b2 格式:

while COMMANDS; do COMMANDS; done

while CONDITION; do
    循环体
done

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

while true; do
    循环体
done

while : ; do
    循环体
done

image image

练习:用while实现

1、编写脚本,求100以内所有正奇数之和
2、编写脚本,提示请输入网络地址,如:192.168.0.0,判断输入的网段中主机在线状态,并统计在线和 离线主机各多少
3、编写脚本,打印九九乘法表
4、编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值
5、编写脚本,实现打印国际象棋棋盘
6、后续六个字符串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、 ad865d2f63是通过对随机数变量RANDOM随机执行命令: echo $RANDOM|md5sum|cut -c1-10 后的结果,请破解这些字符串对应的RANDOM值

while 特殊用法 while read

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

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

说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
范例:

[root@centos8 ~]#echo magedu | read X ; echo $X
[root@centos8 ~]#echo magedu | while read X ; do echo $X;done
magedu
[root@centos8 ~]#echo magedu | { read X ; echo $X; }
magedu
[root@centos8 ~]#echo magedu | ( read X ; echo $X )
magedu
[root@centos8 ~]#echo mage wang zhang | ( read X Y Z; echo $X $Y $Z )
mage wang zhang
[root@centos8 ~]#echo mage wang zhang | while read X Y Z; do echo $X $Y $Z;done
mage wang zhang

范例:

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

范例:

cat while_read_check_dos.sh
#!/bin/bash
MAX=3
lastb | sed -rn '/ssh:/s@.* ([0-9.]{1,3}{3}[0-9]{1,3}) .*@\1@p'|sort |uniq -c
|while read count ip ;do
    if [ $count -gt $MAX ];then
        iptables -A INPUT -s $ip -j REJECT
    fi
done

范例:查看/sbin/nologin的shell类型的用户名和UID

cat while_read_passwd.sh
#!/bin/bash
while read line ;do
        if [[ "$line" =~ /sbin/nologin$ ]] ;then
                echo $line | cut -d: -f1,3
        fi
done < /etc/passwd

循环 until

格式:

until COMMANDS; do COMMANDS; done

until CONDITION; do
循环体
done

说明:
进入条件: CONDITION 为false
退出条件: CONDITION 为true
范例:

[root@centos8 ~]#sum=0;i=1;until ((i>100));do let sum+=i;let i++;done;echo $sum
5050

无限循环

until false; do
循环体
Done

循环控制语句 continue

continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
格式:
while CONDITION1; do CMD1 … if CONDITION2; then continue fi CMDn … done

范例:

[root@centos8 script40]#cat continue_for.sh #!/bin/bash for ((i=0;i<10;i++));do for((j=0;j<10;j++));do [ $j -eq 5 ] && continue 2 echo $j done echo ————————— [root@centos8 script40]#bash continue_for.sh 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4

### 循环控制语句 break
break [N]:提前结束第N层整个循环,最内层为第1层  
格式:  

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

范例:

[root@centos8 script40]#cat break_for.sh #!/bin/bash for ((i=0;i<10;i++));do for((j=0;j<10;j++));do [ $j -eq 5 ] && break echo $j done echo ————————— done

范例:

[root@centos8 script40]#cat break_for.sh #!/bin/bash for ((i=0;i<10;i++));do for((j=0;j<10;j++));do [ $j -eq 5 ] && break 2 echo $j done echo ————————— done

[root@centos8 script40]#bash break_for.sh 0 1 2 3 4

范例:
[root@centos8 script40]#cat menu.sh
#!/bin/bash
sum=0
COLOR='echo -e \033[1;31m'
COLOR2='echo -e \033[1;32m'
END="\033[0m"

while true;do
    echo -e "\033[33;1m\c"
    cat <<-EOF
1) 鲍鱼
2) 满汉全席
3) 龙虾
4) 燕窝
5) 帝王蟹
6) 点菜结束,结帐
    EOF
    echo -e "\033[0m"

    read -p "请点菜(1-6): " MENU
    case $MENU in
    1|4)
        $COLOR'菜价: $10'$END
        let sum+=10
        ;;
    3|5)
        $COLOR'菜价: $20'$END
        let sum+=20
        ;;
    2)
        $COLOR'菜价: $200000'$END
        let sum+=200000
        ;;
    6)
        $COLOR2"你点的菜总价格是 \$$sum"$END
        break
        ;;
    *)
        echo "点错了,没有这道菜"
        ;;
    esac
    $COLOR2"你点的菜总价格是 \$$sum"$END
done

循环控制 shift 命令

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

#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
        echo "$1"
        shift
done
echo
./shfit.sh a b c d e f g h

练习

1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记
录于日志/var/log/login.log中,并退出脚本
2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出
3、用文件名做为参数,统计所有参数文件的总行数
4、用二个以上的数字为参数,显示其中的最大值和最小值

循环与菜单 select

格式:

select NAME [in WORDS ... ;] do COMMANDS; done

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

说明:

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示 PS3 提示 符,等待用户输入
  • 用户输入菜单列表中的某个数字,执行相应的命令
  • 用户输入菜单列表中的某个数字,会将对应的WORD值赋值给NAME变量
  • 用户输入被保存在内置变量 REPLY 中
  • select 是个无限循环,因此要用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环
  • select 经常和 case 联合使用
  • 与 for 循环类似,可以省略 in list,此时使用位置参量 范例:
cat select.sh
#!/bin/bash
#
sum=0
PS3="请点菜(1-6): "
select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束; do
    case $REPLY in
        1)
            echo $MENU 价格是 100
            let sum+=100
            ;;
        2)
            echo $MENU 价格是 88
            let sum+=88
            ;;
        3)
            echo $MENU价格是 66
            let sum+=66
            ;;
        4)
            echo $MENU 价格是 166
            let sum+=166
            ;;
        5)
            echo $MENU 价格是 200
            let sum+=200
            ;;
        6)
            echo "点菜结束,退出"
            break
            ;;
        *)
            echo "点菜错误,重新选择"
            ;;
    esac
done
echo "总价格是: $sum"

函数 function

函数介绍

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程 它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一 部分 函数和shell程序区别

  • Shell程序在子Shell中运行
  • 函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改

管理函数

函数由两部分组成:函数名和函数体
帮助参看:help function

定义函数

#语法一:
func_name(){
    ...函数体...
}

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

#语法三:
function func_name(){
    ...函数体...
}

范例:

#!/bin/bash

# 使用语法一来定义函数
func_name () {
    echo "Hello from func_name defined with syntax 1."
}

# 使用语法二来定义函数
function func_name_2 {
    echo "Hello from func_name_2 defined with syntax 2."
}

# 使用语法三来定义函数
function func_name_3 () {
    echo "Hello from func_name_3 defined with syntax 3."
}

# 调用所有函数
func_name
func_name_2
func_name_3


[root@rocky9 ~]# vim test.sh
[root@rocky9 ~]# bash test.sh
Hello from func_name defined with syntax 1.
Hello from func_name_2 defined with syntax 2.
Hello from func_name_3 defined with syntax 3.

查看函数

#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#查看指定当前已定义的函数名
declare -f func_name
#查看当前已定义的函数名定义
declare -F func_name

删除函数

格式:

unset func_name

函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中 调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数 代码
    函数的生命周期:被调用时创建,返回时终止

交互式环境调用函数

交互式环境下定义和使用函数 范例:

[root@centos8 ~]#dir() {
> ls -l
> }
[root@centos8 ~]#dir
total 4
-rw-------. 1 root root 1559 Nov 7 19:33 anaconda-ks.cfg

范例:实现判断CentOS的主版本

[root@centos8 ~]#centos_version() {
> sed -rn 's#^.* +([0-9]+)\..*#\1#p' /etc/redhat-release
> }
[root@centos8 ~]#centos_version
8

在脚本中定义及使用函数

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

[root@centos8 ~]#cat func1.sh
#!/bin/bash
#name:func1
hello(){
echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello"
hello
echo "back from the function"
[root@centos8 ~]#./func1.sh
now going to the function hello
Hello there today's date is 2019-12-18
back from the function

范例:

#!/bin/bash
disable_selinux(){
    sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
    echo "SElinux已禁用,重新启动后才可生效"
}

disable_firewall(){
    systemctl disable --now firewalld &> /dev/null
    echo "防火墙已禁用"
}

set_ps1() {
    echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
    echo "提示符已修改成功,请重新登录生效"
}

set_eth(){
    sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
    grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
    echo "网络名称已修改成功,请重新启动才能生效"
}

PS3="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关防火墙
修改提示符
修改网卡名
以上全实现
退出
'
select M in $MENU ;do
    case $REPLY in
        1)
            disable_selinux
            ;;
        2)
            disable_firewall
            ;;
        3)
            set_ps1
            ;;
        4)
            set_eth
            ;;
        5)
            disable_selinux
            disable_firewall
            set_ps1
            set_eth
            ;;
        6)
            break
            ;;
        *)
            echo "请输入正确的数字"
            ;;
    esac
done

使用函数文件

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

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

范例:

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

hello2() {
    echo Run hello2 Function
}

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

hello2 () {
    echo Run hello2 Function
}

范例:

#!/bin/bash

# Disable SELinux
disable_selinux() {
    sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
    action "SELinux已禁用,重新启动后才可生效"
}

# Disable firewall
disable_firewall() {
    systemctl disable --now firewalld &> /dev/null
    action "防火墙已禁用"
}

# Set PS1 prompt
set_ps1() {
    echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
    action "提示符已修改成功,请重新登录生效"
}

# Set network interface name
set_eth() {
    sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0#" /etc/default/grub
    grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
    action "网络名称已修改成功,请重新启动才能生效"
}

PS3="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关闭防火墙
修改提示符
修改网卡名
以上全实现
退出
'

select M in $MENU ; do
    case $REPLY in
        1)
            disable_selinux
            ;;
        2)
            disable_firewall
            ;;
        3)
            set_ps1
            ;;
        4)
            set_eth
            ;;
        5)
            disable_selinux
            disable_firewall
            set_ps1
            set_eth
            ;;
        6)
            break
            ;;
        *)
            echo "请输入正确的数字"
            ;;
    esac
done

函数返回值

函数的执行结果返回值:

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

环境函数

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

export -f function_name
declare -xf function_name

查看环境函数:

export -f
declare -xf

函数参数

函数可以接受参数:

  • 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 …
  • 在函数体中当中,可使用$1, $2, …调用这些参数;还可以使用$@, $*, $#等特殊变量 范例:实现进度条功能
[root@centos8 ~]#cat progress_chart.sh
#!/bin/bash
function print_chars() {
    # 传入的第一个参数指定要打印的字符串
    local char="$1"
    # 传入的第二个参数指定要打印多少次指定的字符串
    local number="$2"
    local c
    for ((c = 0; c < number; ++c)); do
        printf "%s" "$char"
    done
}

COLOR=32
declare -i end=50
for ((i = 1; i <= end; ++i)); do
    printf "\e[1;%sm\e[80D[" "${COLOR}"
    print_chars "#" $i
    print_chars " " $((end - i))
    printf "] %3d%%\e[0m\n" $((i * 2))
    sleep 0.1s
done
echo

image

函数变量

变量作用域:

  • 普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用 范围是当前shell脚本程序文件,包括脚本中的函数
  • 环境变量:当前shell和子shell有效
  • 本地变量:函数的生命周期;函数结束时变量被自动销毁 注意:
  • 如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
  • 由于普通变量和局部变量会冲突,建议在函数中只使用本地变量 在函数中定义本地变量的方法
local NAME=VALUE

函数递归

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

  • 函数内部自已调用自已
  • 必须有结束函数的出口语句,防止死循环 范例: 无出口的递归函数调用
[root@centos8 ~]#func () { echo $i;echo "run func";let i++; func; }
[root@centos8 ~]#func

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

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

范例: 测试递归的嵌套深度

[root@centos8 ~]#test(){
    let i++
    echo i=$i
    test
}
[root@centos8 ~]#test

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

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

脚本实现

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

练习

  1. 编写函数,实现OS的版本判断
  2. 编写函数,实现取出当前系统eth0的IP地址
  3. 编写函数,实现打印绿色OK和红色FAILED
  4. 编写函数,实现判断是否无位置参数,如无参数,提示错误
  5. 编写函数,实现两个数字做为参数,返回最大值
  6. 编写服务脚本/root/bin/testsrv.sh,完成如下要求
    (1) 脚本可接受参数:start, stop, restart, status
    (2) 如果参数非此四者之一,提示使用格式后报错退出
    (3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
    考虑:如果事先已经启动过一次,该如何处理?
    (4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”
    考虑:如果事先已然停止过了,该如何处理?
    (5) 如是restart,则先stop, 再start
    考虑:如果本来没有start,如何处理?
    (6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running…”,如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped…”
    (7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理 说明:SCRIPT_NAME为当前脚本名
  7. 编写脚本/root/bin/copycmd.sh
    (1) 提示用户输入一个可执行命令名称
    (2) 获取此命令所依赖到的所有库文件列表
    (3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下
    如:/bin/bash ==> /mnt/sysroot/bin/bash
    /usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
    (4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86- 64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
    (5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述 功能;直到用户输入quit退出
  8. 斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称 为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列 以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2),利用函数, 求n阶斐波那契数列
    cat Bomb.sh
    #!/bin/bash
    ./$0|./$0&
  9. 汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱 子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开 始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一 次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤

信号捕捉 trap

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

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

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

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

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

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

范例:

#!/bin/bash

trap "echo 'Press ctrl+c or ctrl+\'" int quit
trap -p

for((i=0;i<=10;i++))
do
    sleep 1
    echo $i
done

trap '' int
trap -p

for((i=11;i<=20;i++))
do
    sleep 1
    echo $i
done

trap '-' int
trap -p

for((i=21;i<=30;i++))
do
    sleep 1
    echo $i
done

范例: 当脚本正常或异常退出时,也会执行finish函数

[root@centos8 ~]#cat trap_exit.sh
#!/bin/bash
finish() {
    echo "finish" | tee -a /root/finish.log
}

trap finish exit

while true; do
    echo "running"
    sleep 1
done

创建临时文件 mktemp

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


说明:TEMPLATE: filenameXXX,X至少要出现三个

常见选项:

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

范例:

[root@centos8 ~]#mktemp
/tmp/tmp.UogGCumh8C

[root@centos8 ~]#mktemp /tmp/testXXX
[root@centos8 ~]#tmpdir=`mktemp -d /tmp/testdirXXX`
[root@centos8 ~]#mktemp --tmpdir=/testdir testXXXXXX

范例:实现文件垃圾箱

#方法1:脚本实现
[root@centos8 ~]#cat /data/scripts/rm.sh
#!/bin/bash
DIR=`mktemp -d /tmp/trash-$(date +%F_%H-%M-%S)XXXXXX`
mv $* $DIR
echo $* is move to $DIR

[root@centos8 ~]#alias rm=/data/scripts/rm.sh

#方法2:函数实现
[root@centos8 ~]#function rm () { local trash=`mktemp -d /tmp/trashXXXX`;mv $*
$trash; }

安装复制文件 install

install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合
install命令格式:

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

选项:

-m MODE,默认755
-o OWNER
-g GROUP
-d DIRNAME 目录

范例:

[root@centos8 ~]#install -m 700 -o wang -g admins srcfile desfile
[root@centos8 ~]#install -m 770 -d /testdir/installdir

[root@centos8 ~]#install -m 600 -o wang -g bin anaconda-ks.cfg /data/a.cfg
[root@centos8 ~]#ll /data/a.cfg
-rw------- 1 wang bin 1550 Jun 23 20:28 /data/a.cfg

[root@centos8 ~]#install -m 700 -o mage -g daemon -d /data/testdir
[root@centos8 ~]#ll -d /data/testdir
drwx------ 2 mage daemon 6 Apr 29 15:09 /data/testdir

交互式转化批处理工具 expect

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

范例: 安装expect 及mkpasswd 工具

[root@centos8 ~]#yum -y install expect
[root@centos8 ~]#rpm -ql expect|head
/usr/bin/autoexpect
/usr/bin/dislocate
/usr/bin/expect
/usr/bin/ftp-rfc
/usr/bin/kibitz
/usr/bin/lpunlock
/usr/bin/mkpasswd
/usr/bin/passmass
/usr/bin/rftp
/usr/bin/rlogin-cwd

[root@centos8 ~]#mkpasswd
mghe5J&9A
[root@centos8 ~]#mkpasswd
XhyQ67uo=
[root@centos8 ~]#mkpasswd -l 15 -d 3 -C 5
9T{htJmcA7pgCJ2

expect 语法:

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

常见选项:

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

示例:

expect -c 'expect "\n" {send "pressed enter\n"}'
expect -d ssh.exp

expect中相关命令

  • spawn 启动新的进程
  • expect 从进程接收字符串
  • send 用于向进程发送字符串
  • interact 允许用户交互
  • exp_continue 匹配多个字符串在执行动作后加此命令 expect最常用的语法(tcl语言:模式-动作)
    单一分支模式语法:
[root@centos8 test]#expect
expect1.1> expect "hi" {send "You said hi\n"}
hahahixixi
You said hi

匹配到hi后,会输出“you said hi”,并换行
多分支模式语法:

[root@centos8 ~]#expect
expect1.1> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
hehe
Hehe yourself
expect1.2> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
bye
Good bye
expect1.3> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
hi
You said hi
expect1.4>

匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:

expect {
    "hi" { send "You said hi\n" }
    "hehe" { send "Hehe yourself\n" }
    "bye" { send " Good bye\n" }
}


[root@centos8 ~]# expect
expect1.1> expect {
+> "hi" { send "You said hi\n" }
+> "hehe" { send "Hehe yourself\n" }
+2> "bye" { send " Good bye\n" }
+> }
bye
Good bye
expect1.2>

范例1:非交互式复制文件

#!/usr/bin/expect  # 指定使用 Expect 脚本解释器

# 启动 scp 命令,将本地的 /etc/redhat-release 文件复制到远程服务器的 /data 目录
spawn scp /etc/redhat-release 10.0.0.7:/data

# 监听并处理可能出现的提示
expect {
    "yes/no" {  # 如果出现 "yes/no" 提示,则发送 "yes" 并继续监听
        send "yes\r"  # 发送 "yes" 并加上回车符,以确认连接
        exp_continue  # 继续执行后续的 expect 命令
    }
    "password" {  # 如果出现 "password" 提示,则发送密码
        send "magedu\r"  # 发送密码 "magedu" 并加上回车符
    }
}

# 等待 expect 脚本结束,即文件传输完成
expect eof

范例2:自动登录

#!/usr/bin/expect
spawn ssh 10.0.0.7
expect {
  "yes/no" { send "yes\n"; exp_continue }
  "password" { send "magedu\n" }
}
interact

范例3:expect 变量

#!/usr/bin/expect
set ip 10.0.0.7
set user root
set password magedu
set timeout 10
spawn ssh $user@$ip
expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "$password\n" }
}
interact

范例4:expect 位置参数

[root@centos8 ~]#cat expect4
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]

spawn ssh $user@$ip

expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "$password\r" }
}

interact

[root@centos8 ~]#./expect4 10.0.0.7 root magedu
spawn ssh root@10.0.0.7
root@10.0.0.7's password:
Last login: Wed Apr 29 15:34:14 2020 from 10.0.0.8
[root@centos7 ~]#exit
logout
Connection to 10.0.0.7 closed.

范例5:expect 执行多个命令

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n"; exp_continue }
    "password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo magedu |passwd --stdin haha\n" }
send "exit\n"
expect eof
#./ssh4.exp 10.0.0.7 root magedu

范例6:shell脚本调用 expect

#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password" { send "$password\r" }
}
expect "]#" { send "useradd hehe\r" }
expect "]#" { send "echo magedu |passwd --stdin hehe\r" }
expect "]#" { send "exit\r" }
expect eof
EOF


#./ssh5.sh 192.168.8.10 root magedu

范例7: shell脚本利用循环调用expect在CentOS和Ubuntu上批量创建用户

#!/bin/bash
NET=10.0.0
user=root
password=magedu
IPLIST="
7
18
101
"

for ID in $IPLIST;do
ip=$NET.$ID

expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "$password\n" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

范例:

[root@centos8 scripts]#cat expect8.sh
#!/bin/bash
NET=10.0.0
user=root
password=magedu
IPLIST="
7
18
"


for ID in $IPLIST ;do
ip=$NET.$ID

expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "$password\n" }
}
expect "#" { send "sed -i 's/^SELINUX=enforcing/SELINUX=disabled/'
/etc/selinux/config\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

数组 array

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

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

声明数组

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

注意:两者不可相互转换

数组赋值

数组元素的赋值 (1) 一次只赋值一个元素

ARRAY_NAME[INDEX]=VALUE

范例:

weekdays[0]="Sunday"
weekdays[4]="Thursday"

(2) 一次赋值全部元素

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

范例:

title=("ceo" "coo" "cto")
num=({0..10})
alpha=({a..g})
file=( *.sh )

(3) 只赋值特定元素

ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

(4) 交互式数组值对赋值

read -a ARRAY

范例:

[root@centos8 ~]#declare -A course
[root@centos8 ~]#declare -a course
-bash: declare: course: cannot convert associative to indexed array
[root@centos8 ~]#file=( *.sh )
[root@centos8 ~]#declare -A file
-bash: declare: file: cannot convert indexed to associative array

范例:

[root@ubuntu1804 ~]#i=a
[root@ubuntu1804 ~]#j=1
[root@ubuntu1804 ~]#declare -A arr
[root@ubuntu1804 ~]#arr[$i$j]=mage
[root@ubuntu1804 ~]#j=2
[root@ubuntu1804 ~]#arr[$i$j]=wang
[root@ubuntu1804 ~]#echo ${arr[*]}
wang mage
[root@ubuntu1804 ~]#echo ${arr[a1]}
mage
[root@ubuntu1804 ~]#echo ${arr[a2]}
wang

显示所有数组

显示所有数组:

declare -a

范例:

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

引用数组
引用特定的数组元素

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

范例:

[root@centos8 ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@centos8 ~]#echo ${title[1]}
coo
[root@centos8 ~]#echo ${title}
ceo
[root@centos8 ~]#echo ${title[2]}
cto
[root@centos8 ~]#echo ${title[3]}

引用数组所有元素

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

范例:

[root@centos8 ~]#echo ${title[@]}
ceo coo cto
[root@centos8 ~]#echo ${title[*]}
ceo coo cto

数组的长度,即数组中元素的个数

${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

范例:

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

数组的所有下标

${!ARRAY_NAME[*]}
${!ARRAY_NAME[@]}

范例:

[root@centos8 ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")

[root@centos8 ~]#echo ${!title[@]}
0 1 2
[root@centos8 ~]#echo ${!title[*]}
0 1 2

删除数组

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

unset ARRAY[INDEX]

[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto

删除整个数组

unset ARRAY

范例:

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

[root@centos8 ~]#

数组数据处理

数组切片:

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

范例:

[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 10

向数组中追加元素:

ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

范例:

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

关联数组

declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)

注意:关联数组必须先声明再调用
范例:

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

范例:关联数组

[root@centos8 ~]#declare -A student
[root@centos8 ~]#student[name1]=lijun
[root@centos8 ~]#student[name2]=ziqing
[root@centos8 ~]#student[age1]=18
[root@centos8 ~]#student[age2]=16
[root@centos8 ~]#student[gender1]=m
[root@centos8 ~]#student[city1]=nanjing
[root@centos8 ~]#student[gender2]=f
[root@centos8 ~]#student[city2]=anhui
[root@centos8 ~]#student[gender2]=m
[root@centos8 ~]#student[name50]=alice
[root@centos8 ~]#student[name3]=tom
[root@centos8 ~]#for i in {1..50};do echo student[name$i]=${student[name$i]};
done

范例: 显示关联数组的所有下标

[root@centos8 ~]#declare -A title=([ceo]=mage [coo]=zhang [cto]=wang)
[root@centos8 ~]#echo ${!title[@]}
ceo cto coo

范例

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

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

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

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

练习

  1. 输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序
  2. 将下图所示,实现转置矩阵 matrix.sh
123==>258
456789
789369
  1. 打印杨辉三角形

字符串处理

字符串切片

基于偏移量取字符串

#返回字符串变量var的字符的长度,一个汉字算一个字符
${#var}

#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,
offset的取值在0  ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset}

#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}

#取字符串的最右侧几个字符,取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
${var: -length}

#从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var:offset:-length}

#先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:-
length前空格,并且length必须大于offset
${var: -length:-offset}

范例:

[root@centos8 script40]#str=abcdef我你他
[root@centos8 script40]#echo ${#str}
9
[root@centos8 script40]#echo ${str:2}
cdef我你他
[root@centos8 script40]#echo ${str:2:3}
cde
[root@centos8 script40]#echo ${str:-3}
abcdef我你他
[root@centos8 script40]#echo ${str: -3}
我你他
[root@centos8 script40]#echo ${str:2:-3}
cdef
[root@centos8 script40]#echo ${str: -2:-3}
-bash: -3: substring expression < 0
[root@centos8 script40]#echo ${str: -3:-2}
[root@centos8 script40]#echo ${str:-3:-2}
abcdef我你他
[root@centos8 script40]#echo ${str: -3:-2}
[root@centos8 script40]#echo ${str: -5:-2}
ef我

基于模式取子串

#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字
符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}

#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字
符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}

范例:

[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file#*/}
log/messages
[root@centos8 ~]#echo ${file##*/}
messages
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word,
删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第
一个word为界删右留左
${var%word*}
${var%word}

#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向
左的最后一个word为界删右留左
${var%%word*}
${var%%word}

范例:

[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file%/*}
var/log
[root@centos8 ~]#echo ${file%%/*}
var

范例:

[root@centos8 ~]#url=http://www.magedu.com:8080
[root@centos8 ~]#echo ${url##*:}
8080
[root@centos8 ~]#echo ${url%%:*}
http

查找替换

#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}

#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}

#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}

#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}

查找并删除

#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}

#删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}

#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}

#删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}

字符大小写转换

#把var中的所有小写字母转换为大写
${var^^}

#把var中的所有大写字母转换为小写
${var,,}

变量扩展

#扩展以所有prefix开头的变量
${!prefix*}
${!prefix@}

范例:

[root@centos8 ~]#file1=a.txt
[root@centos8 ~]#file2=b.txt
[root@centos8 ~]#file3=c.txt
[root@centos8 ~]#echo ${!file*}
file1 file2 file3
[root@centos8 ~]#echo ${!file@}
file1 file2 file3

高级变量

高级变量赋值

变量配置方式str 没有配置str 为空字符串str 已配置非空字符串
var=${str-expr}var=exprvar=var=$str
var=${str:-expr}var=exprvar=exprvar=$str
var=${str+expr}var=var=exprvar=expr
var=${str:+expr}var=var=var=expr
var=${str=expr}str=expr
var=expr
str 不变
var=
str 不变
var=$str
var=${str:=expr}str=expr
var=expr
str=expr
var=expr
str 不变
var=$str
var=${str?expr}expr 输出至 stderrvar=var=$str
var=${str:?expr}expr 输出至 stderrexpr 输出至 stderrvar=$str

范例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name

[root@centos8 ~]#unset title
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
mage

范例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
mage
[root@centos8 ~]#unset title
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
mage

高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两 个命令是等价的

declare [选项] 变量名

选项:
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 declare -l var=UPPER
-u 声明变量为大写字母 declare -u var=lower
-n make NAME a reference to the variable named by its value

变量间接引用

eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实 现其功能的变量,该命令对变量进行两次扫描

范例:

[root@centos8 ~]# CMD=whoami
[root@centos8 ~]# echo $CMD
whoami
[root@centos8 ~]# eval $CMD
root
[root@centos8 ~]# n=10
[root@centos8 ~]# echo {0..$n}
{0..10}
[root@centos8 ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10
[root@centos8 ~]#for i in `eval echo {1..$n}` ;do echo i=$i ;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10

[root@centos8 ~]#i=a
[root@centos8 ~]#j=1
[root@centos8 ~]#$i$j=hello
-bash: a1=hello: command not found
[root@centos8 ~]#eval $i$j=hello
[root@centos8 ~]#echo $i$j
a1
[root@centos8 ~]#echo $a1
hello

间接变量引用

9.3.2 间接变量引用 如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用 variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过 variable1获得变量值value的行为

variable1=variable2
variable2=value

#示例:
i=1
$1=wang

bash Shell提供了两种格式实现间接变量引用

#方法1
#变量赋值
eval tempvar=\$$variable1
#显示值
eval echo \$$variable1
eval echo '$'$variable1

#方法2
#变量赋值
tempvar=${!variable1}
#显示值
echo ${!variable1}

范例:

[root@centos8 ~]#ceo=name
[root@centos8 ~]#name=mage
[root@centos8 ~]#echo $ceo
name
[root@centos8 ~]#echo $$ceo
33722ceo
[root@centos8 ~]#echo $$
33722
[root@centos8 ~]#echo \$$ceo
$name
[root@centos8 ~]#eval echo \$$ceo
mage

[root@centos8 ~]#eval tmp=\$$ceo
[root@centos8 ~]#echo $tmp
mage
[root@centos8 ~]#echo ${!ceo}
mage

范例:

[root@server ~]# N1=N2
[root@server ~]# N2=wangxiaochun

[root@server ~]# eval NAME=\$$N1
[root@server ~]# echo $NAME
wangxiaochun

[root@server ~]# NAME=${!N1}
[root@server ~]# echo $NAME
wangxiaochun

范例: 生成位置变量

[root@centos7 ~]#cat test.sh
#!/bin/bash
for i in {1..3};do
    echo ${!i}
    #eval echo \$$i
done

[root@centos7 ~]#bash test.sh a b c
a
b
c

范例: 批量创建用户

[root@centos8 ~]#cat create_user.sh
#!/bin/bash

n=$#
[ $n -eq 0 ] && { echo "Usage: `basename $0` username..." ; exit 2; }

for i in `eval echo {1..$n}`;do
    user=${!i}
    id $user &> /dev/null && echo $user is exist || { useradd $user; echo $user
is created; }
done

[root@centos8 ~]#bash create_user.sh hehe xixi haha
hehe is created
xixi is created
haha is created