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中,
{}
和&
经常一起使用,用于创建后台进程和命令组合。
{}
(花括号): 用于创建命令组合,允许将一系列命令组合在一起,形成一个代码块。
&
(和): 用于将命令放入后台执行,即使命令还没有执行完毕,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在处理文本数据时会将每一行作为一个独立的字段。原因如下:
- 逐行读取文件: 设置
IFS
为换行符的主要目的是在循环中逐行读取文件。默认情况下,IFS
包含空格、制表符和换行符,而将IFS
设置为只包含换行符可以确保每次循环迭代时都处理文件的一行。- 防止空格分隔: 如果文件中的某一行包含空格,而
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