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 ,则 $0 是 myscript.sh |
$1 , $2 , ... |
传递给脚本或命令的位置参数,9之后的需要特殊定义 | $1 表示第一个参数,$2 表示第二个参数,以此类推 |
$# |
传递给脚本或命令的位置参数的总数,或变量的长度${#变量} |
如果有三个参数,$# 是 3 |
$* |
所有位置参数的单个字符串 | 如果有参数为 arg1 和 arg2 ,$* 是 arg1 arg2 |
$@ |
所有位置参数的列表,每个参数作为一个单独的词 | 如果有参数为 arg1 和 arg2 ,$@ 是 arg1 和 arg2 |
$? |
上一个命令的退出状态(返回值),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
注意:当比较不存在变量时会出现以下情况
shellr123@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 脚本名
可以进行调试脚本
shellr123@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系统中常用的命令,它的主要作用是从标准输入读取数据,并同时将数据写入标准输出和一个或多个文件。这个命令常用于在数据流中插入一个分支,以便同时查看数据并将其存储到文件中。基本的语法结构是:
bashcommand | 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中,
{}
和&
经常一起使用,用于创建后台进程和命令组合。
{}
(花括号): 用于创建命令组合,允许将一系列命令组合在一起,形成一个代码块。
&
(和): 用于将命令放入后台执行,即使命令还没有执行完毕,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在处理文本数据时会将每一行作为一个独立的字段。原因如下:
- 逐行读取文件: 设置
IFS
为换行符的主要目的是在循环中逐行读取文件。默认情况下,IFS
包含空格、制表符和换行符,而将IFS
设置为只包含换行符可以确保每次循环迭代时都处理文件的一行。- 防止空格分隔: 如果文件中的某一行包含空格,而
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