SHELL编程-Linux自动化运维基础(循环,数组与函数)

本文详细介绍了Linuxshell编程中的for、while循环结构,数组操作,函数定义,以及如何使用expect工具进行自动化交互,包括内网主机存活探测、数组处理和参数传递等内容。
摘要由CSDN通过智能技术生成

SHELL编程-Linux自动化运维基础(循环,数组,函数)

循环结构for语法

  • 固定循环结构的常见写法
#!/bin/bash
for i in {1..5}; do
    echo $[$i*10]
done
#!/bin/bash
for i in $(seq 1 5); do
    echo $[$i*10]
done

需要注意的是,这里的for循环与CPP等编程语言不同,它的循环范围包括两端的值:

r123@localhost:~$ bash for_test.sh 
10
20
30
40
50
  • 指定范围内for循环结构示例
#!/bin/bash

read -p "Please enter a number as upper limit: "  user_input_num

for ((i=1; i<=user_input_num; i++)); do
    echo "This is : $[$i**2]"
done

得到运行结果:

r123@localhost:~$ bash 2.sh 
Please enter a number as upper limit: 5
This is : 1
This is : 4
This is : 9
This is : 16
This is : 25

使用for循环对内网C段主机进行存活探测

#!/bin/bash
> ip_up_list.txt
> ip_down_list.txt

echo "Start testing the host"

for i in {2..254}; do
    ip_addr=192.168.179.$i
    ping -c 1 -w 1 $ip_addr &> /dev/null
    if [ $? -eq 0 ]; then
        echo "$ip_addr is Up" | tee -a ip_up_list.txt
    else
        echo "$ip_addr is Down" | tee -a ip_down_list.txt
    fi
done

tee 是一个在Unix和类Unix系统中常用的命令,它的主要作用是从标准输入读取数据,并同时将数据写入标准输出和一个或多个文件。这个命令常用于在数据流中插入一个分支,以便同时查看数据并将其存储到文件中。基本的语法结构是:

command | tee [OPTION]... [FILE]...

其中 command 是产生输出的命令,FILE 是一个或多个文件名,表示将输出写入这些文件。

一些常见的选项包括:

  • -a:追加模式,将数据追加到文件而不是覆盖文件。
  • -i:交互模式,会在写入前询问用户是否覆盖已存在的文件。
  • -p:保持权限,尝试保持文件的原始权限。

示例:

# 将命令的输出显示在屏幕上,并将其追加到文件中
ls -l | tee -a output.log

这个例子将 ls -l 命令的输出同时显示在终端上,并追加到名为 output.log 的文件中。

  • 改成并发式脚本并且在执行完毕后输出完成
#!/bin/bash
> ip_up_list.txt
> ip_down_list.txt

echo "Start testing the host"

for i in {2..254}; do {
    ip_addr=192.168.179.$i
    ping -c 1 -w 1 $ip_addr &> /dev/null
    if [ $? -eq 0 ]; then
        echo "$ip_addr is Up" | tee -a ip_up_list.txt
    else
        echo "$ip_addr is Down" | tee -a ip_down_list.txt
    fi 
} &
done
wait
echo "All hosts in the network segment have been detected"

在Shell中,{}& 经常一起使用,用于创建后台进程和命令组合。

  1. {}(花括号): 用于创建命令组合,允许将一系列命令组合在一起,形成一个代码块。

  2. &(和): 用于将命令放入后台执行,即使命令还没有执行完毕,Shell也会立即返回,继续执行下一个命令。

结合使用 {}& 时,可以创建一个后台进程,执行一个命令组合,而不会阻塞当前Shell。这将在后台执行 {} 中的所有命令,而不会等待它们完成。这对于需要长时间运行的任务或同时执行多个任务的情况非常有用。

  • 使用for循环读取文件每一行内容
#!/bin/bash
echo "Start reading file"

file_path="content.txt"

IFS=$'\n' 
for line in $(cat "$file_path"); do
    echo "The content of the current line is: $line"
done
r123@localhost:~$ bash for_test.sh 
Start reading file
The content of the current line is: 只因你太美 baby
The content of the current line is: 只因你太美 baby
The content of the current line is: 只因你实在是太美 baby
The content of the current line is: 只因你太美 baby
The content of the current line is: 迎面走来的你让我如此蠢蠢欲动
The content of the current line is: 这种感觉我从未有
The content of the current line is: Cause I got a crush on you who you
The content of the current line is: 你是我的我是你的谁
The content of the current line is: 再多一眼看一眼就会爆炸
The content of the current line is: 再近一点靠近点快被融化

IFS(Internal Field Separator)是Shell中的一个特殊变量,用于指定字段之间的分隔符。在这里,IFS=$'\n' 的目的是将换行符 \n 设置为字段分隔符,这样Shell在处理文本数据时会将每一行作为一个独立的字段。原因如下:

  1. 逐行读取文件: 设置IFS为换行符的主要目的是在循环中逐行读取文件。默认情况下,IFS包含空格、制表符和换行符,而将IFS设置为只包含换行符可以确保每次循环迭代时都处理文件的一行。
  2. 防止空格分隔: 如果文件中的某一行包含空格,而IFS没有被修改,Shell将默认使用空格来分隔字段,导致每个单词被当作一个字段。将IFS设置为只包含换行符可以确保整行文本被作为单一字段

循环结构while语法

  • 死循环语法
#!/bin/bash

while :
do
    let number++
    echo "The number is: $number"
done
  • 条件测试循环即接受ture或者false,为真时执行循环,直到条件不成立
#!/bin/bash

while [ 0 -eq 0 ]
do
    let number++
    echo "The number is: $number"
done
  • while循环读文件
#!/bin/bash

file_path="content.txt"

while IFS= read -r line; do
    echo "The content of the current line is: $line"
done < "$file_path"

  • IFS=: 将IFS(Internal Field Separator,内部字段分隔符)设置为空字符串。这是为了确保read命令不会在行中的空白字符上进行分隔。默认情况下,IFS包含空格、制表符和换行符,通过将其设置为空字符串,我们确保整个行被视为一个字段。
  • read -r line: 使用read命令从标准输入读取一行,并将其存储在变量line中。-r选项表示禁用反斜杠转义,确保读取的行保持原样,不进行反斜杠的处理。

循环结构until语法

这种循环结束点为直到条件成立时:

#!/bin/bash

status_flag=0
until [ $status_flag -gt 5 ]; do
    echo "The number is: ${status_flag}"
    let status_flag++
done

执行结果

r123@localhost:~$ bash until.sh 
The number is: 0
The number is: 1
The number is: 2
The number is: 3
The number is: 4
The number is: 5

shell免交互设计expect工具

expect工具的主要用途是自动化需要与用户交互的任务。通过编写脚本,你可以定义期望的输入和对应的输出,从而实现自动化的交互过程。它的语法与shell编程有些许区别,我们先写个测试脚本,然后使用expect来进行免交互设计:

#!/bin/bash

read -p "Are you play with me [yes/no]: " user_input_choose

if [ $user_input_choose == "yes" ]; then
    echo "Yes, it is good"
elif [ $user_input_choose == "no" ]; then
    echo "OH No"
else
    echo ".........."
fi

虽然expect脚本可以使用任何合法的文件扩展名,但是expect脚本的扩展名设置为.exp的原因主要是为了更清晰地标识和识别这些脚本的类型:

r123@localhost:~$ sudo apt install expect
r123@localhost:~$ which expect
/usr/bin/expect

接下来直接上脚本:

#!/usr/bin/expect

spawn bash example.sh
expect {
    "yes/no" { send "yes\r"; exp_continue };
}

这里使用了expect的关键字,以下是更多常见的关键字的详细介绍:

  • spawn:用于启动一个程序,即在新的shell中执行该指令所

  • exp_continue:表示当前匹配模式将继续等待接下来可能的匹配,不中止当前的expect

  • interact:表示不中止spawn开启的程序,等待用户的进一步输入

  • set:在expect中新建一个变量

  • timeout:用于设置最大等待时间的选项

接下来我们以另一种方式使用expect来进行更复杂的操作,假如我们需要向内网中的多台主机进行统一密钥推送,并进行灵活检测:

#!/bin/bash

cd ~/.ssh
> ip_up_list.txt
> ip_down_list.txt

rpm -q expect &> /dev/null
if [ $? -ne 0 ]; then
    yum install -y expect
fi

if [ ! -f /root/.ssh/id_rsa ]; then
    ssh-keygen -P "" -f ~/.ssh/id_rsa
fi

for ((i=3; i<=254; i++)); do
    {
        ip_addr=192.168.179.$i
        ping -c 1 -W 1 $ip_addr &> /dev/null
        if [ $? -eq 0 ]; then
            echo "$ip_addr is Up" >> ip_up_list.txt
            /usr/bin/expect << EOF
                set timeout 3
                spawn ssh-copy-id $ip_addr
                expect {
                    "yes/no" { send "yes\r"; exp_continue }
                    "password" { send "123456\r" }
                }
                expect eof
EOF
        else
            echo "$ip_addr is Down" >> ip_down_list.txt
        fi
    } &
done
wait
mv ./ip_up_list.txt ../ip_up_list.txt
mv ./ip_down_list.txt ../ip_down_list.txt

echo  "execute finished !!!"

普通数组定义与使用

  • 普通数组定义的两种方式
array_var=(Python Java CPP "Hello World"); echo ${array_var[1]}
array_var=(`ls`); echo ${array_var[2]} 		# 将ls命令的结果保存在数组中

注意:直接输出数组名将会输出数组的第一个值,即索引0位置的值,输出数组的所有值可以使用*通配符代替索引位:

array_var=(`ls`); echo ${array_var[*]}
  • 使用变量初始化数组
#!/bin/bash

first_var=100
second_var=200
array_var=($first_var $second_var)
echo ${array_var[1]}
  • 声明索引位的数组
#!/bin/bash

array_var=(1 a b c [7]=233 'shell')
echo ${array_var[2]}
echo ${array_var[5]}
echo ${array_var[7]}
echo ${array_var[8]}

数组建立时会依次获得索引,当我们指定了索引后,将会依据我们自定义的索引值递增,于是得到以下输出:

r123@localhost:~$ bash array_test.sh 
b

233
shell
  • 逐个初始化数组(同样满足上面的索引规则)
r123@localhost:~$ array_var[0]=1
r123@localhost:~$ array_var[1]=2
r123@localhost:~$ array_var[2]=3
r123@localhost:~$ echo ${array_var[*]}
1 2 3
  • 使用declare输出当前环境中的所有数组
r123@localhost:~$ declare -a
declare -a BASH_ARGC=([0]="0")
declare -a BASH_ARGV=()
....
declare -a array_var=([0]="1" [1]="2" [2]="3")

关联数组定义与使用

  • 定义与初始化
#!/bin/bash

declare -A array_var
array_var['Python']='print'
array_var['Java']='System.out'
array_var['CPP']='cout'
echo ${array_var['Python']}
echo ${array_var[*]}

  • 获取值与索引
#!/bin/bash

declare -A array_var

array_var=(['Python']='print' ['Java']='System.out' ['CPP']='cout')
echo ${array_var[*]}	# 获取值
echo ${!array_var[*]}	# 获取索引

循环定义数组

  • 使用while定义处理数组
#!/bin/bash

file_path="content.txt"

# 读取文件内容到数组
i=0
while IFS= read -r line; do
    echo "The content of the current line is: $line"
    content_array[i++]="$line"
done < "$file_path"

# 输出数组内容
let i=0
while [ $i -lt ${#content_array[*]} ]; do
    echo ${content_array[$i]}
    let i++
done

  • for定义处理数组
#!/bin/bash
echo "Start reading file"
file_path="content.txt"

DEFAULT_IFS=$IFS	# 通常情况下需要保留原始的IFS内容,方便还原,以免影响后续操作
IFS=$'\n' 
while read -r line; do
    echo "The content of the current line is: $line"
    content_array[i++]="$line"
done < "$file_path"

for ((i = 0; i < ${#content_array[@]}; i++)); do
    echo $i'  '${content_array[$i]}
done
IFS=$DEFAULT_IFS

也可以对遍历进行改动为:

for i in ${!content_array[@]};do
	echo "${content_array[$i]}"
done

数组取值时两循环的区别

  • for循环输出文件内容
#!/bin/bash

for i in `cat content.txt`; do
    echo $i
done
r123@localhost:~$ bash for_file.sh 
迎面走来的你让我如此蠢蠢欲动
这种感觉我从未有
Cause
I
got
a
crush
on
you
who
you
  • while循环输出文件内容
#!/bin/bash

while IFS= read -r line || [[ -n ${line} ]]; do
    echo $line
done < content.txt

[[ -n ${line} ]]用于解决不输出最后一行的问题

r123@localhost:~$ bash while_test.sh 
迎面走来的你让我如此蠢蠢欲动
这种感觉我从未有
Cause I got a crush on you who you
你是我的我是你的谁
再多一眼看一眼就会爆炸
再近一点靠近点快被融化
想要把你占为己有 baby bae
不管走到哪里
都会想起的人是你 you you

即读取文件内容时,for循环以空格分隔,要想正常输出的话需要改动IFS,while循环则是以行分割

使用数组统计登录shell

#!/bin/bash
declare -A shells_info
while read line || [[ -n ${line} ]]; do
    shell_type=`echo $line | awk -F : '{print $7}'`
    let shells_info[$shell_type]++
done < /etc/passwd

for i in ${!shells_info[*]}; do
    echo $i"  "${shells_info[$i]}
done

定义函数操作

  • 两种函数定义方式
#!/bin/bash

function HelloWorld {
    echo "Hello World";
}

HelloWorld
#!/bin/bash

HelloWorld() {
    echo "Hello World";
}

HelloWorld
  • 传递参数与返回参数
#!/bin/bash

function add(){
    let res=$1+$2
    return $res
}

add 1 2
res=$?
echo $res
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值