实验环境准备 虚拟机固定IP设置 使用 VMware 创建4个虚拟机,每个虚拟机添加3块30G的虚拟盘。首先在虚机中给每个主机加上固定IP,控制节点修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-ens160 TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no BOOTPROTO=static # 静态分配 DEFROUTE=yes IPV4_FAILURE_FATAL=no IPV6INIT=yes IPV6_AUTOCONF=yes IPV6_DEFROUTE=yes IPV6_FAILURE_FATAL=no IPV6_ADDR_GEN_MODE=eui64 NAME=ens160 UUID=cdea53bc-a032-4987-b69a-b01c55f67950 DEVICE=ens160 ONBOOT=yes # 改为yes IPADDR=192.168.26.140 # 以下为新增 PREFIX=24 GATEWAY=192.168.26.2 DNS1=114.114.114.114 [root@localhost ~]# nmcli connection down ens160 [root@localhost ~]# nmcli connection up ens160 # 重启网络(也可以用nmtui)
集群节点基础环境配置 依次修改其他三台虚拟机,分配内网IP为141~143,并给每台主机进行命名,方便通过主机名访问节点:
1 2 3 4 5 6 7 8 [root@localhost ~]# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.26.140 center 192.168.26.141 sp-1 192.168.26.142 sp-2 192.168.26.143 sp-3
为了之后的多节点操作,需要将这个/etc/hosts
文件下发到受控节点sp-1
至sp-3
上。先通过 Ansible 完成这样的多节点操作,后续再深入了解。
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@localhost ~]# yum install epel-release [root@localhost ~]# yum makecache # 刷新缓存 [root@localhost ~]# yum install ansible [root@localhost ~]# ansible --version ansible [core 2.14.2] config file = /etc/ansible/ansible.cfg configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.11/site-packages/ansible ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible python version = 3.11.2 (main, Feb 18 2023, 08:12:16) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/usr/bin/python3.11) jinja version = 3.1.2 libyaml = True
新建一个目录autoops
,在其中准备配置文件和 hosts 文件。
/etc/ansible/ansible.cfg
中有如下说明:
Since Ansible 2.12 (core): To generate an example config file (a “disabled” one with all default settings, commented out): $ ansible-config init –disabled > ansible.cfg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [root@localhost autoops]# ansible-config init --disabled > ansible.cfg [root@localhost autoops]# cat hosts [master] # 指定组,下面是对应组内的主机 center [nodes] # 指定组,下面是对应组内的主机 sp-[1:3] [master:vars] # 指定组变量,下面是组变量,组内节点共享变量 ansible_connection=local [nodes:vars] # 指定组变量 ansible_ssh_pass=YourPassword # 指定远程ssh连接密码,使用时去掉注释 [root@localhost autoops]# ansible --version ansible [core 2.14.2] config file = /root/autoops/ansible.cfg # 确认配置文件 configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.11/site-packages/ansible ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible python version = 3.11.2 (main, Feb 18 2023, 08:12:16) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/usr/bin/python3.11) jinja version = 3.1.2 libyaml = True
第一次尝试连接所有节点,受控节点报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@localhost autoops]# ansible all -i hosts -m ping sp-1 | FAILED! => { "msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host's fingerprint to your known_hosts file to manage this host." } sp-2 | FAILED! => { "msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host's fingerprint to your known_hosts file to manage this host." } sp-3 | FAILED! => { "msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host's fingerprint to your known_hosts file to manage this host." } center | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "ping": "pong" }
由于 sshd 服务在初次连接时会要求用户接受一次对方主机的指纹信息,需要输入 yes 确认。例如,正常的第一次SSH远程连接过程是这样的:
1 2 3 4 5 [root@localhost autoops]# ssh sp-1 The authenticity of host 'sp-1 (192.168.26.141)' can't be established. ECDSA key fingerprint is SHA256:8IrsRv/9KyqKu8mK0tdN8p5QXIB3hrs6+SmwwKdjsrI. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'sp-1,192.168.26.141' (ECDSA) to the list of known hosts.
在配置文件中将修改以下参数,设置成默认不需要SSH协议的指纹验证,无需重启服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [root@localhost autoops]# vi ansible.cfg # (boolean) Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host host_key_checking=False [root@localhost autoops]# ansible all -i hosts -m ping center | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "ping": "pong" } sp-1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "ping": "pong" } # 其余略
接下来是下发文件以及对所有节点进行改名操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [root@localhost autoops]# ansible nodes -i hosts -m copy -a "src=/etc/hosts dest=/etc/hosts" sp-2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "checksum": "d55b6c38d88db076cac42dcfe03fdd979ca9ed34", "dest": "/etc/hosts", "gid": 0, "group": "root", "md5sum": "c85a4c67b0df03cfae33928f975e7eed", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:net_conf_t:s0", "size": 245, "src": "/root/.ansible/tmp/ansible-tmp-1685295184.8170862-2271-210696309543931/source", "state": "file", "uid": 0 } # 其余略 [root@localhost autoops]# ansible all -i hosts -m hostname -a "name={{ inventory_hostname }}" center | CHANGED => { "ansible_facts": { "ansible_domain": "", "ansible_fqdn": "center", "ansible_hostname": "center", "ansible_nodename": "center", "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "name": "center" } sp-2 | CHANGED => { "ansible_facts": { "ansible_domain": "", "ansible_fqdn": "sp-2", "ansible_hostname": "sp-2", "ansible_nodename": "sp-2", "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "name": "sp-2" } # 其余略
断开 Shell 重连,可以发现节点的重命名已经生效。通过 Ansible 可以批量查看所有的节点的主机名:
1 2 3 4 5 6 7 8 9 [root@center autoops]# ansible all -i hosts -m shell -a "hostname" center | CHANGED | rc=0 >> center sp-1 | CHANGED | rc=0 >> sp-1 sp-2 | CHANGED | rc=0 >> sp-2 sp-3 | CHANGED | rc=0 >> sp-3
VSCode 配置 在 VSCode 中安装 Python 和 SFTP (@Natizyskunk) 插件。按 ctrl+shift+p 弹出的框中选择SFTP配置。
在当前打开的目录下会生成一个.vscode
目录,同时在该目录中会生成一个sftp.json
配置文件,默认内容如下:
1 2 3 4 5 6 7 8 9 10 11 { "name" : "My Server" , "host" : "localhost" , "protocol" : "sftp" , "port" : 22 , "username" : "username" , "remotePath" : "/" , "uploadOnSave" : false , "useTempFile" : false , "openSsh" : false }
要将本地代码映射到 center 节点,修改配置内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "name" : "本地虚拟机" , "host" : "192.168.26.140" , "protocol" : "sftp" , "port" : 22 , "username" : "root" , "password" : "YourPassword" , "remotePath" : "/root/autoops/imooc-ops" , "passive" : false , "interactiveAuth" : false , "uploadOnSave" : true , "syncMode" : "update" , "ignore" : [ "**/.vscode/**" , "**/.git/**" ] }
右键SFTP插件的相关选项,可以实现目录同步以及上传或者下载目录。
选择Upload Folder会将整个目录下的文件上传到远端服务器上的对应目录。执行该操作后的结果如下:
1 2 [root@center autoops]# ls # 会自动创建目录 ansible.cfg hosts imooc-ops
Python 虚拟环境搭建 安装依赖环境 :
1 yum install make gcc patch zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel libuuid-devel gdbm-devel
Pyenv 的安装配置:
1 2 3 4 5 6 7 8 curl https://pyenv.run | bash echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrcecho 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrcecho 'eval "$(pyenv init -)"' >> ~/.bashrcexec "$SHELL "
构建虚拟环境:
1 2 3 4 5 6 7 8 9 10 11 12 pyenv install -l pyenv install 3.8.16 pyenv virtualenv 3.8.16 imooc-ops pyenv virtualenvs pyenv activate imooc-ops pyenv deactivate
echo 命令 空格压缩 1 2 3 4 5 6 echo "hello, world" echo hello, world, 你好 var1="a b c d" echo $var1 echo "$var1 "
常用选项 1 2 3 4 5 6 7 8 9 10 11 12 echo "hello" echo -n "hello" echo echo -n "please input your name: " read name echo "hello, $name " echo "hello\nwor\tld" echo -e "hello\nwor\tld"
输出带颜色的字符 使用ANSI转义序列。
1 2 3 4 5 6 7 8 9 10 11 echo -e "\033[31mhello\033[0m" echo -e "\033[30m 黑色字 \033[0m" echo -e "\033[31m 红色字 \033[0m" echo -e "\033[32m 绿色字 \033[0m" echo -e "\033[33m 黄色字 \033[0m" echo -e "\033[34m 蓝色字 \033[0m" echo -e "\033[35m 紫色字 \033[0m" echo -e "\033[36m 青色字 \033[0m" echo -e "\033[37m 白色字 \033[0m"
变量 ${} ${}
用于对变量进行操作。例如,${var}
可以用于替换变量var
的值。
1 2 3 4 5 6 7 8 var1=hello echo $var1 var1_xyz=abc echo "输出\$var1_xyz=$var1_xyz " echo "继续输出: ${var1} _xyz"
declare 与 set declare
命令用于声明和设置变量的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 var2=123 declare -i var3var3=xyz echo "\$var3=$var3 " var3=245 echo "\$var3=$var3 " declare -r var4=abc echo $var4
1 2 3 4 5 6 7 [root@center chap01]# declare # 显示当前系统中已定义的全部变量信息 [root@center chap01]# var1=hello [root@center chap01]# declare | grep ^var1= var1=hello [root@center chap01]# var2=123 [root@center chap01]# declare | grep ^var2= var2=123
set
命令用于设置或修改 shell 的运行时选项。
1 2 3 [root@center chap01]# set # 与declare类似,也可以列出Shell中定义的变量 [root@center chap01]# unset var1 # 删除已定义的变量 [root@center chap01]# declare | grep ^var1= # 没有输出
`` 与 $() 反引号``与$()
的作用类似,都可以返回命令输出的结果。
反引号容易与其他引号产生歧义,而$()
没有这个问题。在嵌套命令时,反引号需要反斜杠转义,而$()
不需要,因此更推荐使用$()
。
1 2 3 4 LOCAL_IP_1=`cat /etc/hosts | grep \`hostname\` | awk '{print $1}' ` LOCAL_IP_2=$(cat /etc/hosts | grep $(hostname) | awk '{print $1}' ) echo "\$LOCAL_IP_1=$LOCAL_IP_1 , \$LOCAL_IP_2=$LOCAL_IP_2 "
数组 数组的定义与长度 使用${array[@]}
或${array[*]}
表示整个数组。${array[i]}
表示第i个元素。数组变量前加#
表示数组或其中某个元素的长度。
*
通常表示将整个数组作为一个整体。当使用*
作为索引时,它会展开为数组的所有元素,每个元素之间用空格分隔。这意味着将数组视为单个参数,而不是逐个处理数组的元素。
@
表示数组的所有元素,每个元素都是独立的。使用@
作为索引时,它会将数组的每个元素视为一个独立的实体,可以逐个处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 arr1=(xx xyz "hello, world" 1234 20.0 "你好" ) echo "数组arr1的长度: ${#arr1[@]} " echo "数组arr1的长度: ${#arr1[*]} " echo "${#arr1} " len=${#arr1[@]} last=`expr $len - 1` echo "arr1的最后一个元素: ${arr1[$last]} " arr2[0]=abc arr2[1]=xyz arr2[2]="世界,你好" arr2[3]=1234 echo "arr2数组内容: ${arr2[@]} " echo "数组arr2的长度: ${#arr2[@]} "
数组的遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 len_2=${#arr2[@]} last_2=$((len_2-1 )) for i in `seq 0 $last_2 `do pos=$((i+1 )) echo "arr2中第${pos} 个元素为: ${arr2[$i]} " done count=1 for ele in "${arr1[@]} " do echo "arr1中第${count} 个元素为: ${ele} " ((count++)) done
函数 函数的定义与调用 Shell 中函数不设返回类型、函数参数。
1 2 3 4 5 6 xyz () { echo "hello, function xyz!" } xyz
函数传参 使用位置参数 向定义的函数传参。
位置参数是指在执行脚本时传递给脚本的参数,按照顺序分配到特殊变量 $1
、$2
、$3
等中。其中,$1
表示第一个参数,$2
表示第二个参数,以此类推。
例如,如果你执行以下命令:
1 ./myscript.sh arg1 arg2 arg3
那么在 myscript.sh
脚本中,$1
的值将是 arg1
,$2
的值将是 arg2
,$3
的值将是 arg3
。
需要注意的是,$0
表示脚本的名称,$#
表示传递给脚本的参数个数,$*
表示所有参数的值,$@
表示所有参数的值(但是每个参数都被双引号包围)。
1 2 3 4 5 6 7 8 9 xyz_with_params () { echo "函数传入参数个数为:$# " for i in `seq 1 $# ` do echo "第${i} 个位置参数: $1 " shift done } xyz_with_params abc xyz 123
shift
能够将命令接收到的参数逐个向左移动一位,即原本的$3
变量会覆盖$2
变量,原本的$2
变量会覆盖$1
变量。这样我们只需要每执行一次shift
命令后调用$1
变量,就能够实现对全部参数的处理工作了。
函数的返回值 函数可以返回一个状态码$?
。在大多数 Unix/Linux 系统中,命令的状态码范围是 0 到 255。其中,状态码 0 表示命令执行成功,非 0 表示命令执行失败。如果状态码超出了这个范围,它将对 256 取余数,截断为 0 到 255 之间的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 xyz_with_return () { echo "设置返回值 1000" return 1000 } xyz_with_return echo "函数返回值: $?" if xyz_with_return then echo "返回值零,成功" else echo "返回值非零,失败" fi
计算 expr 命令 expr
是进行数学运算和字符串操作的工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 a=$(expr 3 + 2) echo "a = $a " b=$(expr $a + 10) echo "b = $a + 10 = $b " c=$(expr $a \* 10) echo "c = $a * 10 = $c " d=$(expr $a / 3) echo "d = $a / 3 = $d " e=$(expr $a % 3) echo "e = $a % 3 = $e "
需要注意,在进行乘法运算时需要使用反斜杠转义符号,否则会被 Shell 解释为通配符。
let 命令 let
命令是一个用于计算数学表达式的 Bash 内置命令。变量计算中,因变量不需要加上$
来表示。如果表达式中包含了空格或其他特殊字符,则必须引起来。
1 2 3 4 5 6 7 8 9 10 11 let "a += 10" echo "a + 10 = $a " let "a /= 10" echo "a / 10 = $a " let "a++" echo "a++ = $a " let "b = $a + $b + $c " echo "b = a + b + c = $b "
$(()) $(())
是 Shell 中的一种算术扩展方式,可以用于执行数学运算。它的基本语法如下:
其中,expression
是一个数学表达式,可以包含数字、变量、算术运算符和括号等元素,Shell 会对这个表达式进行求值,并将结果输出到标准输出。
可以使用的算术运算符包括:
+
:加法
-
:减法
*
:乘法
/
:除法
%
:取模
**
:幂运算
1 2 3 4 5 6 7 8 9 a=2 b=3 c=4 d=$((a + b * c)) echo $d ((a++)) echo $a ((b %= 2 )) echo $b
expr
、let
、$(())
只能用于整数运算,如果进行浮点数运算,可能会得到不准确的结果。如果需要进行浮点数运算,可以考虑使用 bc
命令。
bc 命令 输入bc
进入交互模式:
1 2 3 [root@center chap01]# bc 1.2345 * 3 3.7035
使用scale
设定精度:
1 2 3 4 [root@center chap01]# bc scale = 3 3 / 8 .375
利用管道符直接取得结果:
1 echo "scale=3; 1.1 * 1.23" | bc
字符串处理 取字符串长度 1 2 3 4 5 6 str1="hello world" echo "原始字符串: $str1 " echo "字符串长度: ${#str1} " str2="我是中文" echo "中文字符串长度: ${#str2} "
还有许多方式可以统计字符串长度,例如expr length "hello world"
。Shell 实践中,1个中文字符占1个长度。
取子串 1 2 echo "从第2个字符开始取字符串: ${str1:2} " echo "从第2个字符开始连续取3个字符: ${str1:2:3} "
此处索引从0开始,即首个字符是第0个字符。
但是通过expr index $string $substring
返回的索引又是从1开始的。
删除子串 #:从开头删除最短匹配的子串
1 echo "从开头删除最短匹配*o的子串: ${str1#*o} "
##:从开头删除最长匹配的子串
1 echo "从开头删除最长匹配*o的子串: ${str1##*o} "
%:从末尾删除最短匹配的子串
1 echo "从末尾删除最短匹配o*的子串: ${str1%o*} "
%%:从末尾删除最长匹配的子串
1 echo "从末尾删除最长匹配o*的子串: ${str1%%o*} "
以上四个符号只能删除字符串开头或结尾与特定模式匹配的子串。如果想要删除字符串中间的子串,可以使用其他命令,如sed
或awk
。
替换子串 1 2 3 4 ${string/substring/replacement} ${string//substring/replacement} ${string/#substring/replacement} ${string/%substring/replacement}
其中string
是要操作的字符串,substring
是要被替换的子串,replacement
是用来替换的文本。
string
后
/
符号表示替换string
中第一个出现的substring
//
符号表示替换string
中所有的substring
/#
符号表示替换string
开头的substring
/%
符号表示替换string
末尾的substring
1 2 3 4 5 str3="Hi World! Hello World! Hello Seres!" echo ${str3/Hello/Goodbye} echo ${str3//Hello/Goodbye} echo ${str3/#Hi/Goodbye} echo ${str3/%Seres!/China!}
默认值
${param:-default}
:如果变量param
定义且不为空,则返回param
的值;否则返回default
。
${param:+default}
:如果变量param
定义且不为空,则返回default
;否则返回空字符串。
${param:?default}
:如果变量param
定义且不为空,则返回param
的值;否则显示错误信息param: default
并终止脚本执行。
1 2 3 4 5 6 7 8 9 10 11 str4="123" echo ${str4:-"abc"} echo ${str5:-"abc"} str6="123" echo ${str6:+abc} echo ${str7:+abc} str8="123" echo ${str8:?abc} echo ${str9:?abc}
判断语句 1 2 3 4 5 6 if [ condition ]then else fi
如果condition
成立,则会执行then
后的代码块,否则执行else
后的代码块。else
语句是可选的,可以省略。
文件测试
操作符
作用
-d
文件是否为目录类型
-e
文件是否存在
-f
是否为一般文件
-r
当前用户是否有权限读取
-w
当前用户是否有权限写入
-x
当前用户是否有权限执行
1 2 3 $ [ -f /etc/fstab ] $ echo $? 0
逻辑运算
逻辑
格式
与
[ condition1 -a condition2]
或[ condition1 ] && [ condition 2 ]
或
[ condition1 -o condition2]
或[ condition1 ] || [ condition 2 ]
非
[ ! condition ]
1 2 3 4 5 6 7 $ [ -d "/root" ] && [ -d "/tmp" ] $ echo $? 0 $ [ -d "/root" -a -d "/tmp" ] $ echo $? 0
整数比较
操作符
作用
-eq
等于
-ne
不等于
-gt
大于
-lt
小于
-ge
大于等于
-le
小于等于
1 2 3 $ [ 10 -eq 10 ] $ echo $? 0
字符串比较
操作符
作用
=
或==
等于
!=
不等于
-z
空串
1 2 3 $ [ -z $String ] $ echo $? 0
循环语句 for 循环 基本语法:
1 2 3 4 for variable_name in item1 item2 ... itemNdo done
遍历数组
1 array=(xx abc "hello, world" 12)
遍历${array[@]}
1 2 3 4 for i in ${array[@]} do echo "$i " done
输出:
遍历"${array[@]}"
1 2 3 4 for i in "${array[@]} " do echo "$i " done
输出:
遍历${array[*]}
1 2 3 4 for i in ${array[*]} do echo "$i " done
输出:
遍历"${array[*]}"
1 2 3 4 for i in "${array[*]} " do echo "$i " done
输出:
输出for_test.txt
1 2 3 hello apple hello banana hello cherry
按分隔符输出
IFS
是一个环境变量,它代表 Internal Field Separator(内部字段分隔符),用于指定用于分隔字符串中字段的字符或字符串。默认情况下,IFS
的值设置为包含空格、制表符和换行符的字符串: \t\n
。
1 2 [root@center chap01]# declare | grep ^IFS IFS=$' \t\n'
1 2 3 4 for line in $(cat for_test.txt)do echo "$line " done
输出:
1 2 3 4 5 6 hello apple hello banana hello cherry
按行输出
更改IFS
之前将其当前值保存到另一个变量中,以便稍后恢复。
1 2 3 4 5 6 7 originalIFS=$IFS IFS=$'\n' for line in $(cat for_test.txt)do echo "$line " done IFS=$originalIFS
输出:
1 2 3 hello apple hello banana hello cherry
也可以使用双括号实现类似C语言风格的for
循环:
1 2 3 4 for ((i = 0 ; i < 5 ; i++))do echo "Iteration: $i " done
while 循环 基本语法:
1 2 3 4 while [ condition ]do done
按分隔符输出
1 2 3 4 while read greeting fruit do echo "greeting: $greeting , fruit: $fruit " done < loop_test.txt
输出:
1 2 3 greeting: hello, fruit: apple greeting: hello, fruit: banana greeting: hello, fruit: cherry
按行输出
1 2 3 4 while read linedo echo "$line " done < loop_test.txt
输出:
1 2 3 hello apple hello banana hello cherry
文件末尾需要有换行符,否则会少读一行。
until 循环 基本语法:
1 2 3 4 until [ condition ]do done
和while
循环类似,不同的是当condition
为假时,执行循环体内的指令。
文件描述符 文件描述符(File Descriptor)是用于标识和操作打开的文件的整数值,允许Shell进程与文件进行输入、输出和错误处理。
有三个默认的文件描述符:
标准输入(stdin):文件描述符为0,用于接收输入数据。通常连接到键盘,允许用户从键盘输入数据。
标准输出(stdout):文件描述符为1,用于发送输出数据。通常连接到终端,可以将结果打印到屏幕上。
标准错误(stderr):文件描述符为2,用于发送错误消息。通常连接到终端,可以将错误信息打印到屏幕上。
重定向操作 使用重定向操作符来改变文件描述符的默认行为,可以将文件重定向到命令,或者将输出发送到文件中(而不是终端)。例如,使用>
符号(等同于1>
)可以将标准输出重定向到文件中,使用<
符号可以将文件内容作为标准输入,使用2>
符号将标准错误重定向到文件。
符号
作用
> file
将标准输出重定向到一个文件中(清空原有文件的数据)
2> file
将错误输出重定向到一个文件中(清空原有文件的数据)
>> file
将标准输出重定向到一个文件中(追加到原有内容的后面)
2>> file
将错误输出重定向到一个文件中(追加到原有内容的后面)
>> file 2>&1
或&>> file
将标准输出与错误输出共同写入到文件中(追加到原有内容的后面)
>& fd
将标准输出重定向到文件描述符中(追加到原有内容的后面)
自定义文件描述符 除了默认的文件描述符0、1和2之外,可以自定义文件描述符。使用exec
命令将一个文件描述符重定向到其他文件。
例如,你可以将文件描述符3和4重定向到一个文件,然后通过该文件描述符进行读取或写入操作。
1 2 3 4 5 6 7 8 9 10 11 12 exec 3> output.txt echo "Hello, World!" >&3 echo -e "hello1\nhello2\nhello3" > input.txtexec 4< input.txt while read linedo echo "Line: $line " done <&4 exec 3>&- exec 4<&-
脚本实例 nginx 状态判断,如不存在则启动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #!/bin/bash check_nginx () { ps -ef | grep "/usr/sbin/nginx" | grep -v grep > /dev/null return $? } while true do if check_nginx then echo "nginx存在 休息2s" sleep 2 continue fi echo "启动nginx..." systemctl start nginx sleep 2 if check_nginx then echo "重新启动nginx成功" continue fi echo "nginx无法启动 请检查配置" break done
日志文件压缩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #!/bin/bash LOG_DIR=./logs [ -d ${LOG_DIR} ] || mkdir ${LOG_DIR} cd ${LOG_DIR} log_out () { log_path="compress.log" echo $1 >> $log_path } produce_log_file () { while true do host_count=$(($RANDOM % 7 + 2 )) now_time=$(date "+%H%M%S" ) declare -a host_arr for i in $(seq 0 $host_count ) do host_arr[$i ]="10.83.26.$(($i + 10) )" done for host in "${host_arr[@]} " do echo "生成日志文件:${host} _${now_time} .access.log" touch ${host} _${now_time} .access.log done sleep 10 done } compress_log_file () { while true do sleep 1 compress_time=$(find . -name "*.access.log" -exec basename {} \; | awk -F _ '{print $2}' | awk -F . '{print $1}' | sort -r | uniq | awk 'NR==1{print}' ) if [ -z "$compress_time " ] then log_out "当前没有日志文件要压缩" continue fi log_out "压缩 ${compress_time} 文件..." tar czf log_compress_${compress_time} .tar.gz $(find . -name "*${compress_time} .access.log" ) if [ $? -eq 0 ] && [ -e "log_compress_${compress_time} .tar.gz" ] then find . -name "*${compress_time} .access.log" -exec rm -f {} \; fi log_out "压缩 ${compress_time} 文件完成" done } produce_log_file & compress_log_file &
EOF