shell脚本编程 ============================================================================================================================================== 一、基础知识 1、编程语言介绍 机器语言 汇编语言 高级语言: 静态语言:编译型语言。编译器(编译器一般都是静态语言开发)直接将代码转换成二进制代码,即可运行。 执行之前完全、全部转换成机器识别的代码后才能执行。 例如:C、C++、C# 动态语言:解释型语言。执行过程中才一步一步的进行转换成机器识别的代码。边解释边执行。 例如:PHP、python、shell、perl 编程语言风格 面向过程:shell、C 面向对象:java(完全)、python(完全)、C+ 2、shell程序 shell编程语言没有依靠内建的库去实现功能,而是依赖于系统的二进制程序去完成功能。有shell程序的内部机制将系统 内部命令组织起来完成一定的功能。 3、shebang机制 #!/bin/bash shell脚本是纯文本文件,而CPU可以执行的只能是二进制程序,所以需要在纯文本的shell开头指明系统解释执行的时候 所有调用的解释器,即由解释器去执行shell脚本。 4、执行脚本 默认创建的纯文本脚本文件没有执行权限,需要手动添加执行权限,然后指定路径执行脚本。 执行方式: 1、添加执行权限,指定脚本路径执行 2、运行解释器将脚本作为解释器的参数运行 5、变量:命名的内存空间 6、变量类型: 强类型 弱类型 shell -- 运算内部隐式转换,把所有要存储的数据当作字符进行。shell编程语言不支持浮点数运算。 7、运算类型 与&&、或||、非!、 异或 8、脚本执行 1、执行命令 bash -n file.sh # 检测脚本中的语法错误 bash -x file.sh # 执行过程中显示详细过程 bash -v file.sh # 执行完毕之后将脚本内容显示出来 2、脚本执行方式: 1、绝对路径 /home/dmtsai/shell.sh 2、相对路径 . ./shell.sh 3、将脚本放在path指定的目录内。将脚本目录加进path变量或者将脚本放在pash指定的目录内 4、以 bash 程序来执行:通过『 bash shell.sh 』或者『 sh shell.sh 』来执行 3、不同执行方式的异同: 1、直接执行脚本的方式 实际执行是在一个新的bash环境中执行的, 在父程序的子程序的bash内执行。当子程序执行完后,子程序中的变量不会回传给父程序 2、利用source执行脚本 和 . 的作用类似 各项动作都在本bash中执行。环境变量都存在于本bash中 4、注 1、语法错误会导致脚本执行终止,命名错误不会导致脚本执行终止。 2、脚本中不支持别名的使用 9、随机数生成 1、bash内建随机数生成器 $RANDOM 生成1-32767之间的数 例如:echo $[$RANDOM%70+1] 10、shell中条件测试[]和[[]]的区别 [] 和test的功能一样 []中的逻辑运算符号需为-a -o [[]] 逻辑运算符号可以使用&& || [[ ]] 是逻辑短路操作,而 [ ] 不会进行逻辑短路 ============================================================================================================================================== 二、bash中的变量 1、编程语言的变量 强类型:定义变量时必须指明类型,参与运算必须符合要求 弱类型:定义时不需要指明类型,默认都是字符型,参与运算时会自动进行隐士类型转换,shell就是弱类型的编程语言 注: 变量被重新赋值之后,不会覆盖原先的内存空间,而是重新找内存空间存放新的值。 2、变量种类 1、本地变量:在当前shell进程中生效,当前shell进程终止,则变量失效,在其子进程中不生效,man bash可以查看到shell 的所有变量 2、环境变量:在当前shell进程和其子进程中生效 定义环境变量 : declare -x varname=var 或者使用 export varname 3、局部变量:在代码块内生效 4、位置变量:用作参数传递给脚本进行引用的变量。例如:$1、$2、$3.... 5、特殊变量 $? # 上一条命令执行的退出码 $0 # 当前脚本的路径 $* # 当前脚本的所有参数,把所有参数当作一个字符串 $@ # 当前脚本的所有参数,每一个参数都是一个独立的字符串 $# # 当前shell脚本中的参数中个数 $$ # 当前shell进程的PID $N # 获取第n个参数的值。$10需要写成${10} $! # 执行上一个指令的PID set -- # 清空所有位置参数 3、变量引用 1、varname=${VARNAME}或者varname=$VARNAME # 大多数情况下{}可以省略 2、varname=`COMD` 或者 varname=$(COMD) 3、注: 1、将变量放入双引号内和直接使用变量的区别:如果变量内容是多行的,则双引号会保留换行符,例如:echo $name 和 echo "$name" 2、name=xuejinwei;(echo $name;name=xuekaixin;echo $name),变量中的内容只在括号内生效,括号内的命令在新的子进程中执行 4、变量追加 变量名=${变量名}追加内容 5、显示变量 显示所有变量: set # 查看变量 unset # 删除变量 注: 在编写脚本过程中,使用后的变量尽量使用unset删除 显示所有环境变量: export env printenv declare -x 6、变量的定义 export varname=VALUE declare -x VARNAME=VALUE declare -a # 声明或显示数组 -A # 声明一个关联数组 -i # 整数类型 注:当明确声明为整数类型时,在数字运算中,可自动识别,不需要加$符号引用 -x # 环境变量 -r # 声明或查看只读类型 -f # 查看系统中定义的函数 -F # 仅显示已定义的所有函数名 -g # 定义全局变量 -l # 将变量声明为小写字母 -u # 将变量声明为大写字母 7、变量命名法则 数字、字母、下户线且不能以数字开头,不能使用编程语言的关键字。 8、只读变量 只读变量不能修改,不能删除。生命周期是当前bash的生命周期。 readonly varname 或者 declare -r varname=... readonly -p # 显示所有只读变量 9、变量偏移 shift:造成参数变量偏移 shitf会移动变量,且shift后面可以接数字,代表偏移的量,表示拿掉前面的几个参数的意思 例如:./script.sh a b c d shift 2之后,$1是c 10、使用read进行变量赋值 read 使用read把输入值分配给[一个或多个]shell变量 选项: -p 指定要显示的提示 -s 静默输入,一般用于密码 -n N 指定输入的字符长度N -d '字符’'输入结束符 -t N TIMEOUT 为N秒 注: 1、所有剩余单词都被分配给最后一个变量 2、read从标准输入中读取值,给[每个单词]分配一个变量 ============================================================================================================================================== 三、语法结构 1、过程式编程语言 1、顺序执行 2、选择执行 1、if语句 1、单分支 if COMDITION; then EXEC_CODE fi 2、双分支 if COMDITION; then EXEC_CODE else EXEC_CODE fi 3、多分支 if COMDITION; then EXEC_CODE elif COMDITION; then EXEC_CODE elif COMDITION; then EXEC_CODE else EXEC_CODE fi 2、case多重选择 case支持glob风格的通配符。case语句相当于多分支的if语句。 * 任意长度任意字符 ? 任意单个字符 [] 指定范围内的任意单个字符 a|b 字符串a或者字符串b case $1 in "PATTERN1") EXEC_CODE ;; "PATTERN2") EXEC_CODE ;; "*") EXEC_CODE ;; esac 3、循环执行 每个循环体都需要有进入条件和退出条件。控制语句依次将列表中的元素赋值给变量,然后依次执行循环体代码。 1、for 1、格式 for VAR_NAME in LIST;do COMD_CODE done 2、生成列表的方法 1、{1..10} 2、`echo user{1..10}` 3、$(seq 1 100) 或者 `seq 1 2 100` 4、能够返回列表的命令,例如:`ls /var` 5、通配符号机制,例如:/var/* 6、变量引用,例如:$@ $* 3、特殊用法 for ((控制变量初始值;条件判断表达式;执行步阶修正表达式)) # 类似C语言for循环中,变量初始值、条件、变量的增量 do 执行步骤 done 示例: for ((i=1;i<=100;i++)); do let sum+=$i done 2、while 1、格式 while COMDITION;do EXEC_CODE done 2、条件满足,则执行循环体 3、特殊用法:遍历文件的每一行,将每一行的值赋值给变量,然后在循环体中进行处理 while read VARNAME; do EXEC_CODE done < /path/file # 也可以用重定向或者管道的方式 3、until 1、格式 until COMDITION;do EXEC_CODE done 2、条件不满足,则执行循环体 3、特殊用法 until read VARNAME; do EXEC_CODE done < /path/file # 也可以用重定向的方式 4、循环控制语句 1、continue N 终止某一次循环的执行,而直接跳到下一次循环。N表示跳出当前的几层循环,默认终止最内层的 当前循环。 2、break N 终止循环体,提前结束循环。N表示结束第几层循环。 2、算数运算 1、算数运算符号:+ - * / % ** 2、算数运算使用 1、let命令 注:如果let计算的结果是0,则退出码是1 格式: let 运算式 --> 使用let命令可以不使用$符号引用数字 例如: var1=3 var2=2 let sum=$var1+$var2 2、num=$[算数表达式] --> []中括号内的算数表达式也可以不使用$符号引用变量 例如: sum=$[$var1+$var2] 3、num=$((算数表达式)) --> (())内的算数表达式也可以不使用$符号引用变量 例如: num=$(($a+$b)) 4、$(expr 操作数 运算符号 操作数) --> expr中不能去掉$符号引用变量 例如: 1、num=$(expr $num1 \* $num2) 2、expr 0 + $a # 判断a变量中是否是一个非零的整数 (0 + 0)的退出码大于0 3、expr支持< > >= <= != 5、通过重定向的方式将运算表达式传给bc命令 3、增强型赋值 += -= *= /= %= 例如: let sum+=1 或者 let sum++ 4、自增、自减(如果对于一个变量每一次都是加1或者减1,则可以使用自增自减符号) let nu++ let nu-- 注:i++表示先对i进行操作,然后自增;++i表示先对i进行自增,然后对i进行操作。 3、条件测试 测试方法 注:expression前后必须有空格,否则认为是语法错误 1、test expression 2、[ expression ] 测试种类: 1、数字测试 -eq # 相等 -ne # 不相等 -gt # 大于 -lt # 小于 -ge # 大于等于 -le # 小于等于 2、字符串测试 注:字符串测试时,尽量将字符串用引号引起来 =~ # 表示左侧的字符串是否被右侧的PATTERN所匹配,支持的是扩展的正则表达式使用[[]] -z # 判断字符串是否为空 -n # 判断字符串是否为非空 =或者== # 判断两个字符串是否相等 != # 是否不相等 > # 判断字符串的首字母按字母表排序,是否在前面。注:需要使用双中括号比较[[]] < 3、文件测试 1、文件存在性 -e # 文件是否存在,存在true,不存在false -f # 文件是否为普通文件,是true,不是false -d # 文件是否存在且为为目录,是为0,否为1 注:判断文件类型前先判断是否是链接文件 2、文件时间戳 -nt # file1 是否比 file2 新 -ot # file1 是否比 file2 旧 -ef # file1 和 file2 是否指向同一个inode 3、文件权限 -r -w -x -g # SGID -u # SUID -k # sticky -s # 是否为空文件,空false,不空true 注:当前测试身份对测试结果有影响,例如以root用户身份测试,可能就会出现不符合实际的情况 4、文件类型 -b # 文件是否为块设备文件 -c # 是否为字符设备文件 -S # 是否为Socket文件 -p # 是否为管道文件 -L|-h # 是否为软链接文件 -o # 是否属于当前用户 -g # 是否属于当前属组 4、组合条件测试 -a # 且 -o # 或 ! # 非 3、逻辑运算 1、test expression1 -a expression2 或者 [ expression1 -a expression2 ] test expression1 -o expression2 或者 [ expression1 -o expression2 ] test !expression1 -o expression2 或者 [ !expression1 -o expression2 ] 2、command1 && command2 command1 || command2 !command1 && !command2 [[ command1 ]] || [[ command2 ]] 3、异或 ^ 相同为假,不同为真。 4、注: 1、[]和[[]]的区别 1、[]和test命令等同; 比较运算符号有=和!=,只能用于字符串比较,不能用于数字比较;逻辑符号有-a和-o; 建议中括号中的变量加引号; 使用> 和 < 比较字符串需要转义; 2、[[]]是bash程序语言的关键字; 使用=~支持正则表达式,有时不需要加引号; 能够使用&&和||等逻辑操作符号; 支持使用> 和 < 号比较字符串; 2、(()) 双小括号中可以使用> < 等比较符号 3、在脚本中括号内的exit指令并不会退出,因为括号内的指令是当前脚本进程的子进程 例如:(exit 100) ============================================================================================================================================== 三、bash环境 1、bash命令行展开的过程(优先级依次变低)  把命令行分成单个命令词  展开别名  展开大括号的声明{}  展开波浪符声明~  命令替换$() 和 ``  再次把命令行分成命令词  展开文件通配* 、? 、[abc] 等  准备I/0 重导向< 、>  运行命令 2、登录环境配置文件登录顺序 交互式: /etc/profile --> /etc/profile.d/*.sh -->~/.bash_profile --> ~/.bashrc --> /etc/bashrc 非交互式:例如:su xuejw | 图形界面 | 执行脚本 | 其他任何bash进程 ~/.bashrc --> /etc/bashrc -->/etc/profile.d/*.sh 3、退出时执行的脚本 ~/.bash_logout 4、bash变量 $- # 显示bash的设置项 h hashall ,打开这个选项后,Shell 会将命令所在的路径hash 下来,避免每次都要查询。通过set +h 将h 选项关闭 i interactive-comments的 ,包含这个选项说明当前的 shell的是一个交互式的 shell 。所谓的交互式shell, 在脚本中,i 选项是关闭的。 m monitor ,打开监控模式,就可以通过Job control 来控制进程的停止、继续,后台或者前台执行等。 B braceexpand ,大括号扩展 H history ,H 选项打开,可以展开历史列表中的命令,可以通过! 感叹号来完成,例如“!!” 返回上最近的一个历史命令,“!n”第 返回第 n 个历史命令 ============================================================================================================================================== 四、bash脚本函数 1、在过程变成语言中,函数是实现代码重用的重要手段。 2、语法结构 表示方法一: function FUNCTION_NAME { 函数体 } 表示方法二:(Linux系统中自带脚本中的函数语法表示方式) FUNCTION_NAME() { 函数体 } 表示方法三: function FUNCTION_NAME () { 函数体 } 3、特性 1、函数没有自主执行的能力,必须需要调用函数,方可执行 2、书写函数名即表示调用函数,函数名的位置会被自动替换为函数代码 3、函数的生命周期从被调用开始,返回值时结束 3、函数退出状态码默认返回函数中最后一条命令执行的状态结果 4、可以使用return命令自定义退出状态码 5、函数中可以再次调用函数 4、传递参数给函数 调用函数时,在函数名后面以空白分割给定参数列表即可。 在函数中可以使用$1 $2 $3调用参数。 还可以使用$@ $* $# 等特殊变量参数。 5、变量作用域 1、在函数的生命周期内有效,函数生命周期结束,变量自动销毁 2、函数中可以修改脚本定义的本地变量 3、在函数中定义局部变量的方法:local VAR_NAME=VALUE 。局部变量只在函数内部有效 4、函数中使用定义 declare 定义的变量默认都是局部变量,可以使用 declare -g 定义全局变量 6、函数递归 函数直接或者间接的调用自身 # n的阶乘 fun() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(fun $[$1-1])] fi } # 斐波那契数列 fab() { if [ $1 -eq 1 ]; then echo 1 elif [ $1 -eq 2 ]; then echo 1 else echo $[$(fab $[$1-1])+$(fab $[$1-2])] fi } bash 中递归最多递归的层级为8446 7、命令行定义函数 FUNCTION_NAME () { comd1;comd2; } 8、对函数执行的状态 或 对函数的返回结果判断 # 对函数执行结果判断 [ `function_name` == " " ] # 对函数执行状态(返回状态码) #!/bin/bash test() { ping -c 1 -W 1 172.18.12414.1 &> /dev/null } if test; then echo OK else echo NO fi 9、定义环境函数,使得子进程也可以使用 declare -xf FUNCTION_NAME 10、函数返回状态码 exit N N可以是命令执行的结果。例如:exit `echo 3`。exit N 会退出脚本 11、函数返回值 return {N|COMD} **注意:函数返回结果将赋值给$?,return只是推出函数本身 ============================================================================================================================================== 五、bash脚本 数组 1、数组:存储多个元素的连续的内存空间 2、数组的引用 索引编号从0开始 ARRAY_NAME[INDEX] 引用数组中的值 ${ARRAY_NAME[INDEX]} 引用0号索引的数组值 ${ARRAY_NAME} # 省略index表示引用下标为0的元素 引用数组中的所有元素 ${ARRAY_NAME[*]}或者${ARRAY_NAME[@]} 元素偏移取属组元素 ${ARRAY_NAME[@]:2:3} # 表示从所有元素中跳过2个元素,取3个元素出来,3可以省略,表示跳过2过后将数组元素全部取出来 3、数组声明 declare -a # 显示数组 declare -a ARRAY_NAME # 声明数组 declare -A ARRAY_NAME # 声明关联数组 注:关联数组必须先声明在调用 4、数组的赋值 一次赋值一个元素: ARRAY_NAME[INDEX]=VALUE | ARRAY[2]="xue" 一次赋值全部元素: ARRAY_NAME=("VAL1" "VAL1" "VAL1" "VAL1" . . .) 只赋值特定元素: ARRAY_NAME=([0]="VAL1" [1=]="VAL1" [2]="VAL1" . . .) 向数组中追加元素: ARRAY_NAME[${#ARRAY_NAME[*]}] 删除数组中的某元素: -----> 稀疏格式的数组 unset ARRAY_NAME[INDEX] 删除整个数组: unset ARRAY_NAME 交互式赋值: read -a ARRAY_NAME 生成列表赋值给数组: ARRAY_NAME{seq N M} # 数字 ARRAY_NAME{/root/bin/*.sh} # 命令生成的列表结果赋值给数组 5、数组长度(元素个数) ${#ARRAY_NAME[*]} 或者 ${#ARRAY_NAME[@]} 6、关联数组 declare -A ARRAY_NAME=([INDEX_NAME1]="VAL1" [INDEX_NAME2]="VAL1" [INDEX_NAME3]="VAL1" . . .) ============================================================================================================================================== 六、字符串处理 1、字符串切片 # 跳过几个字符,开始取字符串 ${VAR_NAME:1:3} 或者 ${VAR_NAME:1:} # 1表示跳过几个,3表示取3三个 # 取字符串的最右侧几个字符 ${VAR_NAME: -3} 冒号之后须加引号 # 取出字符串长度 ${#VAR_NAME} # 从最左侧跳过N字符,一直向右取到距离最右侧M个字符之前的内容 ${VAR_NAME:N:-M} # 先从最右侧向左取到N个字符开始,再向右取到距离最右侧M个字符之间的内容 注:-N 之前必须要有空格 ${var: -N:-M}: 例如: name=xuejinwei echo ${name: -5:-2} jinw 2、基于模式取子串 # WORD可以指定任意字符。功能:自左而右,查找VAR_NAME变量所存储的字符串中,第一次出现WORD的位置,将最 左侧到这个单词处全部删除 ${VAR_NAME#*WORD} # WORD可以指定任意字符。功能:自左而右,查找VAR_NAME变量所存储的字符串中,最后一次出现WORD的位置,将最 左侧到这个单词处全部删除 ${VAR_NAME##*WORD} # WORD可以指定任意字符。功能:自右而左,查找VAR_NAME变量所存储的字符串中,第一次出现WORD的位置,将最 右侧到这个单词处全部删除 ${VAR_NAME%WORD*} # WORD可以指定任意字符。功能:自右而左,查找VAR_NAME变量所存储的字符串中,最后出现WORD的位置,将最右侧到 这个单词处全部删除 ${VAR_NAME%%WORD*} 3、查找替换 # 查找var所表示的字符串中,将第一次被PATTERN匹配到的字符串替换为WORD ${VAR_NAME/PATTERN/WORD} # 查找var所表示的字符串中,将所有被PATTERN匹配到的字符串替换为WORD ${VAR_NAME//PATTERN/WORD} # 查找var所表示的字符串中,只将[行首]被PATTERN匹配到的字符串替换为WORD ${VAR_NAME/#PATTERN/WORD} # 查找var所表示的字符串中,只将[行尾]被PATTERN匹配到的字符串替换为WORD ${VAR_NAME/%PATTERN/WORD} 注: 不指定替换后的单词时,即表示删除匹配到的单词 例如:${VAR_NAME/PATTERN} 例如:${VAR_NAME//PATTERN} 例如:${VAR_NAME/#PATTERN} 例如:${VAR_NAME/%PATTERN} 4、大小写转换 # 小写转换为大写 ${VAR_NAME^^} # 大写转换为小写 ${VAR_NAME,,} 5、变量测试 x=${y:-STRING} # 如果y没有被赋值,则x的值为STRING;如果y被赋值则x的值为y;如果y被赋值但是空值,则x的值为STRING x=${y:+STRING} # 如果y没有被赋值,则x的值为空;如果y被赋值则x的值为STRING;如果y被赋值但是空值,则x的值为空 x=${y:=STRING} # 如果y没有被赋值,则x的值为STRING,y的值也为STRING;如果y被赋值则x的值为y,y的值不变; 如果y被赋值但是空值,则x的值为STRING,y的值也为STRING x=${y:?STRING} # 如果y没有被赋值或者为空,则返回错误信息;否则返回y的值 ============================================================================================================================================== 1、在配置文件中定义了变量之后,可以在脚本中通过 source 指明将配置文件读取到脚本中,从而在脚本中 生效。 ============================================================================================================================================== 八、制作菜单 1、命令格式 select variable in list; do EXEC_CODE (可以使用case语句进行选择判断) done 3、PS3变量决定菜单的提示符号 4、用户输入被保存在内置变量 REPLY 中 5、select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环 select 经常和 case 联合使用 与 for 循环类似,可以省略 in list,此时使用位置参量 ============================================================================================================================================== 九、信号捕捉 trap  trap '接收到信号后触发的指令' 信号 自定义进程收到系统发出的指定信号后,将执行触发指令 ,而不会执行原操作 trap -p 后面跟脚本执行的命令 trap '' 信号 忽略信号的操作 trap '-' 信号 恢复原信号的操作 ============================================================================================================================================== 十、脚本技巧 1、匹配整数数字 num=123;[[ $num =~ ^[0-9]+$ ]] && echo OK 2、条件测试双重判断 if [ -e "/selinux/enforce" ] && [ "$(cat /proc/self/attr/current)" != "kernel" ]; then 3、(())和[[]] 支持使用 > < >= <= 比较符号 4、生成列表 seq -s + 1 20 # 生成1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20的列表 5、let 命令 i++ 或者 ++i 的运算式作为条件判断时,要特别注意 如果 let 的计算结果是0的话,则其退出码为 1 ++i 先对 i 进行加操作,然后返回 i 的值 i++ 先返回 i 的值,然后对 i 进行加操作 6、状态码返回恒为0或者1 false : true `` 7、脚本中尽量使用 unset 将变量删除 ============================================================================================================================================== 十一、eval命令 eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量, 该命令对变量进行两次扫描。  示例: [root@server ~]# CMD=whoami [root@server ~]# echo $CMD whoami [root@server ~]# eval $CMD root [root@server ~]# n=10 [root@server ~]# echo {0..$n} {0..10} [root@server ~]# eval echo {0..$n} 0 1 2 3 4 5 6 7 8 9 10 =============================================================================================================================================== 十二、变量间接引用 bash Shell提供了两种格式实现间接变量引用 1、eval tempvar=\$$variable1 2、tempvar=${!variable1} ============================================================================================================================================== 十三、expect expect 是由Don Libes基于Tcl(Tool Command Language)语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令, 可以将交互过程如:ssh登录,ftp 登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以 大大提高系统管 理人员的工作效率。 1、expect中相关命令 spawn # 启动新的进程 send # 用于向指定进程发送字符串 expect # 从进程接收字符串 interact # 允许用户交互 exp_continue # 匹配多个字符串在执行动作后加此命令 2、常见语法 单一分支模式语法 expect "hi" {send "You said hi\n"} 匹配到hi后,会输出“you said hi”,并换行 多分支模式语法  匹配hi,hello,bye任意字符串时,执行相应输出。等同如下: expect { "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send “Good bye\n"} } 示例: expect &> /dev/null <