SHELL编程-Linux自动化运维基础

SHELL编程-Linux自动化运维基础

变量使用

定义与使用

shell 复制代码
r123@localhost:~$ first_var=aaa
r123@localhost:~$ echo $first_var
aaa
r123@localhost:~$ second_var=123
r123@localhost:~$ echo $second_var
123
r123@localhost:~$ third="bbb ccc"
r123@localhost:~$ echo $third
bbb ccc
  • 定义变量时,变量名不能以数字开头,等号两侧不能出现空格,有空格的变量内容,必须以引号包含
  • 使用变量时必须加上$

拼接变量

shell 复制代码
r123@localhost:~$ first_var=123
r123@localhost:~$ first_var=${first_var}456
r123@localhost:~$ echo $first_var
123456
r123@localhost:~$ first_var="$first_var"789
r123@localhost:~$ echo $first_var
123456789

查看变量

直接使用set命令将输出目前shell中存在的变量,包括环境变量,我这里以grep筛选出来自定义变量(通常情况下没有办法选出所有自定义变量)

shell 复制代码
r123@localhost:~$ set | grep first_var
first_var=123456789

取消变量

shell 复制代码
r123@localhost:~$ unset first_var
r123@localhost:~$ echo $first_var

交互变量

shell 复制代码
#! /bin/bash

read -p "Input a ipaddress: " ip_address

ping -c 1 ${ip_address} &> /dev/null && echo "${ip_address} Up" || echo "${ip_address} Down"

echo success

三种变量引用

  • ""表示对内部内容进行弱引用,只对空格等字符进行转义,$等不受影响
  • ''表示对内部内容进行强引用,对$等也进行转义
  • 表示更高的执行优先级 复制代码
shell 复制代码
r123@localhost:~$ first_var=123
r123@localhost:~$ echo "$first_var"456
123456
r123@localhost:~$ first_var=123
r123@localhost:~$ echo '$first_var'456
$first_var456
r123@localhost:~$ echo `ls`456
MyMainClass.java content.txt456

变量运算

  • 整数运算
shell 复制代码
r123@localhost:~$ expr 1 + 1
2
r123@localhost:~$ var_1=123
r123@localhost:~$ var_2=123
r123@localhost:~$ expr $var_1 + $var_2
246

使用expr时,作为运算两个值或变量必须与运算符有空格隔开,还有以下三种方式进行运算

shell 复制代码
r123@localhost:~$ echo $(($var_1 + $var_2))
246
r123@localhost:~$ echo $((2 ** 2))
4
r123@localhost:~$ echo $[1+2]
3
shell 复制代码
r123@localhost:~$ let sum=$var_1+$var_2
r123@localhost:~$ echo $sum
246
r123@localhost:~$ let sum++; echo $sum
247

注意,虽然let常用于整数运算场景,但是也有人习惯用来声明变量,似乎是为了提高可读性,但是实际上容易混淆

  • 浮点运算

一般浮点运算需要借助bc计算器程序

shell 复制代码
r123@localhost:~$ sudo apt install bc
r123@localhost:~$ echo "2^4" | bc
16
r123@localhost:~$ echo "scale=3; 2/4" | bc
.500
r123@localhost:~$ echo "scale=3; 5/4" | bc
1.250
r123@localhost:~$ echo "scale=2; 5/4" | bc
1.25

字串切片

在定义的变量中,字串中的每个字符存在索引,我们可以通过以下方式进行切片:

shell 复制代码
${字串名:起始索引:索引步长}
shell 复制代码
r123@localhost:~$ str_var='helloworld'; echo ${str_var:2:3}
llo

变量类型

环境变量

环境变量是操作系统中的一种配置参数,用于存储系统和应用程序的配置信息。它们是在操作系统运行时由操作系统或用户设置的键值对。这些键值对包含了一些重要的信息,例如系统路径、临时文件夹位置、语言设置等。在计算机程序中,环境变量通常用于指定应用程序的运行时行为,例如定义特定的路径、设置默认值或启用某些功能。程序可以在运行时读取这些环境变量的值,以便适应不同的运行环境或用户需求

通常我们使用export设置临时环境变量用来达到跨shell使用目的,即在当前shell中开启一个子shell后,可以在子shell中使用该变量,我们可以使用pstree来展示shell的层级关系,使用exit来退出当前shell

shell 复制代码
r123@localhost:~$ export Base_var=233
r123@localhost:~$ ps | grep bash
   40 pts/0    00:00:00 bash
r123@localhost:~$ bash
r123@localhost:~$ bash
r123@localhost:~$ bash
r123@localhost:~$ ps | grep bash
   40 pts/0    00:00:00 bash
   60 pts/0    00:00:00 bash
   67 pts/0    00:00:00 bash
   73 pts/0    00:00:00 bash
r123@localhost:~$ pstree
init─┬─init───init───bash───bash───bash───bash───pstree
     └─{init}
r123@localhost:~$ exit
exit
r123@localhost:~$ pstree
init─┬─init───init───bash───bash───bash───pstree
     └─{init}
r123@localhost:~$ echo $Base_var
233

如果想要设置的变量永久可用,可以添加到环境变量文件,以下是常见表格:

文件 加载时机 说明
/etc/environment 系统启动时 全局环境变量,对所有用户生效
/etc/profile 用户登录时 全局配置文件,对所有用户生效
/etc/bash.bashrc 用户登录时(bash shell) 全局bash配置文件,对所有用户生效
~/.bashrc 用户登录时(bash shell) 用户级别的bash配置文件,只对当前用户生效
~/.bash_profile 用户登录时(bash shell) 用户级别的bash配置文件,仅在用户登录时执行一次
~/.profile 用户登录时 用户级别的配置文件,通常由sh、bash、ksh等解释执行
~/.zshrc 用户登录时(zsh shell) 用户级别的zsh配置文件,只对当前用户生效
/etc/profile.d/ 用户登录时(bash shell) 目录包含由管理员提供的额外配置文件,以扩展/etc/profile
~/.config/environment.d/ 用户登录时 目录包含用户特定的环境变量配置文件,以扩展~/.profile

查看当前shell加载的的环境变量内容:

shell 复制代码
r123@localhost:~$ env
SHELL=/bin/bash
WSL_DISTRO_NAME=Ubuntu
...

修改环境变量,这里以当前用户环境变量配置为例:

shell 复制代码
r123@localhost:~$ vim .profile
...
fi
export myself_var=2333
...   
:wq
r123@localhost:~$ source .profile
r123@localhost:~$ echo $myself_var
2333

预定义变量

预定义变量是在Shell脚本中由Shell环境提前定义好的一些特殊变量,用于存储系统信息、脚本运行时的状态等。这些变量在脚本执行期间自动设置,可以用于获取有关执行环境的信息或控制脚本的行为(其中$1$9也称为位置变量):

变量 含义 示例
$0 脚本或命令本身的名称 如果脚本名为 myscript.sh,则 $0myscript.sh
$1, $2, ... 传递给脚本或命令的位置参数,9之后的需要特殊定义 $1 表示第一个参数,$2 表示第二个参数,以此类推
$# 传递给脚本或命令的位置参数的总数,或变量的长度${#变量} 如果有三个参数,$# 是 3
$* 所有位置参数的单个字符串 如果有参数为 arg1arg2$*arg1 arg2
$@ 所有位置参数的列表,每个参数作为一个单独的词 如果有参数为 arg1arg2$@arg1arg2
$? 上一个命令的退出状态(返回值),0是成功,非0即为失败 echo $? 可以用于获取上一个命令的退出状态
$$ 当前Shell进程的进程ID echo $$ 显示当前Shell的进程ID
$! 后台运行的上一个命令的进程ID 在后台运行的命令结束后,$! 包含其进程ID
$USER 当前用户的用户名 echo $USER 显示当前登录用户的用户名
$HOME 当前用户的主目录路径 echo $HOME 显示当前用户的主目录路径
$SHELL 当前用户使用的Shell echo $SHELL 显示当前用户使用的Shell
$PATH Shell查找可执行文件的路径列表 echo $PATH 显示Shell查找可执行文件的路径列表

在书写脚本时,我们可以做出以下的效果

shell 复制代码
ping -c 1 $1 &> /dev/null && echo "${1} is Up" || echo "${1} is Down"
echo success
shell 复制代码
r123@localhost:~$ vim ping.sh
r123@localhost:~$ bash ping.sh 192.168.179.11
192.168.179.11 is Down
success

条件控制

注意:条件测试不支持浮点值

数值比较

操作符 描述 用法
-eq 等于(equal to) [ "$a" -eq "$b" ]
-ne 不等于(not equal to) [ "$a" -ne "$b" ]
-lt 小于(less than) [ "$a" -lt "$b" ]
-le 小于等于(less than or equal to) [ "$a" -le "$b" ]
-gt 大于(greater than) [ "$a" -gt "$b" ]
-ge 大于等于(greater than or equal to) [ "$a" -ge "$b" ]

如果想要直接使用上述比较,需要通过 $?来获取运算结果:

shell 复制代码
r123@localhost:~$ [ 20 -gt 10 ]; echo $?
0
r123@localhost:~$ [ 20 -gt 30 ]; echo $?
1
r123@localhost:~$ [ 20-gt30 ]; echo $?
0

需要注意的是,这里的[]运算符需要与运算内容有空格,否则运算将无法识别

文件验证

指令 描述 用法
-e 文件路径 检查文件或目录是否存在 [ -e "$文件路径" ]
-f 文件路径 检查文件是否为普通文件 [ -f "$文件路径" ]
-d 文件路径 检查文件是否为目录 [ -d "$文件路径" ]
-s 文件路径 检查文件是否存在且大小不为零 [ -s "$文件路径" ]
-r 文件路径 检查文件是否可读 [ -r "$文件路径" ]
-w 文件路径 检查文件是否可写 [ -w "$文件路径" ]
-x 文件路径 检查文件是否可执行 [ -x "$文件路径" ]
-L 文件路径 检查文件是否为符号链接 [ -L "$文件路径" ]
-b 文件路径 检查文件是否为块设备文件 [ -b "$文件路径" ]
-g 文件路径 检查文件是否具有设置了 SGID 位 [ -g "$文件路径" ]

文件类型表格:

文件类型 符号 说明
普通文件 - 包含文本、二进制数据等,没有特殊属性。
目录 d 包含其他文件和目录的容器。
符号链接 l 指向另一个文件或目录的链接。
块设备文件 b 提供对设备的带缓冲的访问,如硬盘分区。
字符设备文件 c 提供对设备的不带缓冲的访问,如串口。
套接字文件 s 用于进程间通信的特殊文件。
管道文件 p 用于进程间通信的命名管道。

使用示例:

shell 复制代码
r123@localhost:~$ ll
total 44
drwxr-x--- 2 r123 r123 4096 Jan 20 00:13 ./
drwxr-xr-x 3 root root 4096 Jan 16 01:32 ../
-rw------- 1 r123 r123 3116 Jan 20 10:44 .bash_history
-rw-r--r-- 1 r123 r123  220 Jan 16 01:32 .bash_logout
-rw-r--r-- 1 r123 r123 3771 Jan 16 01:32 .bashrc
-rw------- 1 r123 r123   20 Jan 20 00:13 .lesshst
-rw-r--r-- 1 r123 r123    0 Jan 20 10:43 .motd_shown
-rw-r--r-- 1 r123 r123  807 Jan 19 15:34 .profile
-rw-r--r-- 1 r123 r123    0 Jan 19 14:47 .sudo_as_admin_successful
-rw------- 1 r123 r123 1444 Jan 19 15:44 .viminfo
-rw-r--r-- 1 r123 r123  127 Jan 17 22:52 MyMainClass.java
-rw-r--r-- 1 r123 r123  152 Jan 17 20:37 content.txt
-rw-r--r-- 1 r123 r123   83 Jan 19 15:44 ping.sh
r123@localhost:~$ [ -f MyMainClass.java ]; echo $?
0
r123@localhost:~$ [ -e MyMainClass.java ]; echo $?
0
r123@localhost:~$ [ -x MyMainClass.java ]; echo $?
1
shell 复制代码
r123@localhost:~$ [ -g MyMainClass.java ]; echo $?
1
r123@localhost:~$ sudo chmod g+s MyMainClass.java
r123@localhost:~$ [ -g MyMainClass.java ]; echo $?
0

字符串比较

  • 基础用法
shell 复制代码
r123@localhost:~$ [ "aaa" = "aaa" ]; echo $?
0
  • -z判断字符串长度是0
shell 复制代码
r123@localhost:~$ [ -z "233"  ]; echo $?
1
r123@localhost:~$ [ -z ""  ]; echo $?
0
  • 判断字符串长度不为0,未定义的变量也为0
shell 复制代码
r123@localhost:~$ [ -n "233"  ]; echo $?
0
r123@localhost:~$ [ -n ""  ]; echo $?
1

注意:当比较不存在变量时会出现以下情况

shell 复制代码
r123@localhost:~$ first_var=233
r123@localhost:~$ unset first_var
r123@localhost:~$ echo $first_var

r123@localhost:~$ [ -z $first_var ]; echo $?
0
r123@localhost:~$ [ -n $first_var ]; echo $?
0
r123@localhost:~$ echo $first_var

多条件测试

  • and语法的三种形式
shell 复制代码
r123@localhost:~$ [ 22 -gt 23 -a 23 -gt 22 ]; echo $?
1
r123@localhost:~$ [[ 22 -gt 23 && 23 -gt 22 ]]; echo $?
1
r123@localhost:~$ [ 22 -gt 23 ] && [ 23 -gt 22 ]; echo $?
1
  • or语法的三种形式
shell 复制代码
r123@localhost:~$ [ 22 -gt 23 -o 23 -gt 22 ]; echo $?
0
r123@localhost:~$ [[ 22 -gt 23 || 23 -gt 22 ]]; echo $?
0
r123@localhost:~$ [ 22 -gt 23 ] || [ 23 -gt 22 ]; echo $?
0

模式匹配

利用linux的通配符来进行匹配条件

shell 复制代码
r123@localhost:~$ [[ 2 = [0-9] ]]; echo $?
0
r123@localhost:~$ [[ 20 = [0-9] ]]; echo $?
1

正则匹配

使用~表示进行正则匹配

shell 复制代码
r123@localhost:~$ [[ 20 =~ [0-9] ]]; echo $?
0
r123@localhost:~$ [[ a =~ [0-9] ]]; echo $?
1
r123@localhost:~$ [[ 1 =~ ^[0-9]$ ]]; echo $?
0
r123@localhost:~$ [[ 20 =~ ^[0-9]$ ]]; echo $?
1
r123@localhost:~$ [[ 2 =~ ^[0-9]$ ]]; echo $?
0

流程控制if语法

  • 单分支结构,当条件控制语句成立(返回值为0),输出Hello World
shell 复制代码
#!/bin/bash

if [ 2 -gt 1 ]; then
        echo "Hello World";
fi
shell 复制代码
r123@localhost:~$ cat if_test.sh

#!/bin/bash
read -p "Please enter a username: " username
id $username &> /dev/null
if [ $? -eq 0 ]; then
        echo "The username is exist"
fi

r123@localhost:~$ bash if_test.sh
Please enter a username: r123
The username is exist
  • 逻辑非语法
shell 复制代码
#!/bin/bash

flag=true

if ! $flag; then
    echo "It is false"
else
    echo "It is true"
fi
  • 双分支结构
shell 复制代码
r123@localhost:~$ cat if_test.sh

#!/bin/bash
read -p "Please enter a username: " username
id $username &> /dev/null
if [ $? -eq 0 ]
then
        echo "The username is exist"

else
        echo "The username is not exist"
fi

r123@localhost:~$ bash if_test.sh
Please enter a username: r123
The username is exist
r123@localhost:~$ bash if_test.sh
Please enter a username: aaa
The username is not exist

这里使用了$?来避免了直接将命令作为条件传入if语句,也可以直接将命令作为条件

  • 多分支结构
shell 复制代码
#!/bin/bash
read -p "Please enter a time of only hour: " user_input_hour

if [ $user_input_hour -gt 24 -o $user_input_hour -lt 0 ];
then
    echo "Please enter a correct time"

elif [ $user_input_hour -le 12 ]
then 
    echo "It's ${user_input_hour} o'clock in the morning"

elif [ $user_input_hour -gt 12 ]
then
    echo "It's ${user_input_hour} o'clock in the afternoon"

else
    echo "Failed test"

fi

使用bash -vx 脚本名可以进行调试脚本

shell 复制代码
r123@localhost:~$ bash -vx elif_test.sh
#!/bin/bash
read -p "Please enter a time of only hour: " user_input_hour
+ read -p 'Please enter a time of only hour: ' user_input_hour
Please enter a time of only hour: 12

if [ $user_input_hour -gt 24 -o $user_input_hour -lt 0 ];
then
    echo "Please enter a correct time"
...
elif [ $user_input_hour -gt 12 ]
then
    echo "It's ${user_input_hour} o'clock in the afternoon"

else
    echo "Failed test"

fi
+ '[' 12 -gt 24 -o 12 -lt 0 ']'
+ '[' 12 -le 12 ']'
+ echo 'It'\''s 12 o'\''clock in the morning'
It's 12 o'clock in the morning

在每一句要执行的语句处,将依次输出:原语句、进行操作(以+开头)、执行结果

模式匹配case语法

shell 复制代码
read -p "Please enter a number from 0 to 9 or a letter: "  user_input_value

case $user_input_value in
    [0-9])
        echo "This is a number which in 1-9"
    ;;
    [a-zA-Z])
        echo "This is a letter"
    ;;
    *)
        echo "no"
    ;;
esac

匹配模式为模式匹配,并不能直接使用正则表达式,在每个模式匹配完成后需要使用两个分号,借助以上基础知识,我们可以写一个略微复杂的脚本

shell 复制代码
#!/bin/bash
web1=192.168.179.2
web2=192.168.179.3
web3=192.168.179.144
web4=192.168.179.145
web5=192.168.179.200

cat <<EOF
    1.Web Server 1 ${web1}
    2.Web Server 2 ${web2}
    3.Web Server 3 ${web3}
    4.Web Server 4 ${web4}
    5.Web Server 5 ${web5}
EOF

read -p "Please select a Web Server serial number: " select_res

case $select_res in
1)
    select_res=$web1
    ;;
2)
    select_res=$web2
    ;;
3)
    select_res=$web3
    ;;
4)
    select_res=$web4
    ;;
5)
    select_res=$web5
    ;;
esac

echo "The selected host to connect to is Web Server: "$select_res

ping -c 1 $select_res &> /dev/null && echo "${select_res} Up" || echo "${select_res} Down"

echo "Execution is completed"

循环结构for语法

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

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

shell 复制代码
r123@localhost:~$ bash for_test.sh 
10
20
30
40
50
  • 指定范围内for循环结构示例
shell 复制代码
#!/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

得到运行结果:

shell 复制代码
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段主机进行存活探测

shell 复制代码
#!/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系统中常用的命令,它的主要作用是从标准输入读取数据,并同时将数据写入标准输出和一个或多个文件。这个命令常用于在数据流中插入一个分支,以便同时查看数据并将其存储到文件中。基本的语法结构是:

bash 复制代码
command | tee [OPTION]... [FILE]...

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

一些常见的选项包括:

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

示例:

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

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

  • 改成并发式脚本并且在执行完毕后输出完成
shell 复制代码
#!/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循环读取文件每一行内容
shell 复制代码
#!/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
shell 复制代码
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语法

  • 死循环语法
shell 复制代码
#!/bin/bash

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

while [ 0 -eq 0 ]
do
    let number++
    echo "The number is: $number"
done
  • while循环读文件
shell 复制代码
#!/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选项表示禁用反斜杠转义,确保读取的行保持原样,不进行反斜杠的处理。

运行上述脚本你会发现总是读取不到文件的最后一行,原因与解决方案可以参考:blog.csdn.net/qq_34018840...

1、背景 在计算机出现之前,使用的电传打字机(Teletype Model)每秒可以打10个字符。但是它的问题是打完一行后换行要用去0.2秒,正好可以打两个字符。若在这0.2秒内又有新的字符传过来,那么这个字符将丢失。

于是研发人员想了个办法,就是在每行后面加两个表示结束的字符。一个叫做"回车"(Carriage Return),告诉打字机把打印头定位在左边界;另一个叫做"换行"(Linefeed),告诉打字机把纸向下移一行。这就是"换行"和"回车"的来历。

计算机发明后这两个概念也就被采用,但那时存储器很贵,部分研究人员认为在每行结尾加两个字符太浪费了,加一个就可以,于是就出现了分歧。

Unix及Unix系统里,每行结尾只有"<换行>",即"\n";Windows系统里面,每行结尾是"<回车><换行>",即"\r\n"。一个直接后果是,Unix系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix下打开的话,在每行的结尾可能会多出一个^M符号。

Windows下新建文件,最后一行不会添加回车符或换行符;而linux新建的文件,最后一行还会添加1个换行符。

2、原因: 因为我的目标文件是在windows下创建然后传到服务器上的,这样在利用while read line读取文件时,如果文件最后一行之后没有换行符\n,则read读取最后一行时遇到文件结束符EOF时循环即终止。上面代码中,虽然此时$line内存有最后一行的内容,但程序已经没有机会再处理此行内容,因此导致了最后一行无法读取。

解决方案:

shell 复制代码
#!bin/bash
while read line || [[ -n ${line} ]]
do
    data=`...line...`
    echo "${data}" >> $2.txt
done < $1

循环结构until语法

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

shell 复制代码
#!/bin/bash

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

执行结果

shell 复制代码
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来进行免交互设计:

shell 复制代码
#!/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的原因主要是为了更清晰地标识和识别这些脚本的类型:

shell 复制代码
r123@localhost:~$ sudo apt install expect
r123@localhost:~$ which expect
/usr/bin/expect

接下来直接上脚本:

bash 复制代码
#!/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来进行更复杂的操作,假如我们需要向内网中的多台主机进行统一密钥推送,并进行灵活检测:

shell 复制代码
#!/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 !!!"

普通数组定义与使用

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

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

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

first_var=100
second_var=200
array_var=($first_var $second_var)
echo ${array_var[1]}
  • 声明索引位的数组
shell 复制代码
#!/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]}

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

shell 复制代码
r123@localhost:~$ bash array_test.sh 
b

233
shell
  • 逐个初始化数组(同样满足上面的索引规则)
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输出当前环境中的所有数组
shell 复制代码
r123@localhost:~$ declare -a
declare -a BASH_ARGC=([0]="0")
declare -a BASH_ARGV=()
....
declare -a array_var=([0]="1" [1]="2" [2]="3")

关联数组定义与使用

  • 定义与初始化
shell 复制代码
#!/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[*]}
  • 获取值与索引
shell 复制代码
#!/bin/bash

declare -A array_var

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

循环定义数组

  • 使用while定义处理数组
shell 复制代码
#!/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定义处理数组
shell 复制代码
#!/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

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

shell 复制代码
for i in ${!content_array[@]};do
	echo "${content_array[$i]}"
done

数组取值时两循环的区别

  • for循环输出文件内容
shell 复制代码
#!/bin/bash

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

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

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

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

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

使用数组统计登录shell

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

定义函数操作

  • 两种函数定义方式
shell 复制代码
#!/bin/bash

function HelloWorld {
    echo "Hello World";
}

HelloWorld
shell 复制代码
#!/bin/bash

HelloWorld() {
    echo "Hello World";
}

HelloWorld
  • 传递参数与返回值
shell 复制代码
#!/bin/bash

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

add 1 2
res=$?
echo $res
相关推荐
kirito学长-Java39 分钟前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
海绵波波1071 小时前
flask后端开发(9):ORM模型外键+迁移ORM模型
后端·python·flask
余生H1 小时前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
AI人H哥会Java2 小时前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构
计算机学长felix2 小时前
基于SpringBoot的“大学生社团活动平台”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
sin22012 小时前
springboot数据校验报错
spring boot·后端·python
去哪儿技术沙龙2 小时前
去哪儿机票智能预警系统-雷达系统落地实践
后端
程序员大阳2 小时前
闲谭Scala(1)--简介
开发语言·后端·scala·特点·简介
直裾2 小时前
scala图书借阅系统完整代码
开发语言·后端·scala