赞
踩
目录
自动交互方法一:利用命令的自带参数,将标准输入作为手动输入的内容
自动输入yes
echo "y" | yum install wget ,等同于yum -y install wget
自动输入回车
echo -e "\n" | yum remove wget
**`echo -e` 的小知识**
若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出:\a
发出警告声;\b
删除前一个字符;\c
最后不加上换行符号;\f
换行但光标仍旧停留在原来的位置;\v
与\f 相同;\n
换行且光标移至行首;\r
光标移至行首,但不换行;\t
插入 tab
符号;\\
插入 ‘' 字符;\nnn
插入 nnn(八进制)所代表的 ASCII 字符;
输入多行指令我们需要借助输入重定向操作符 <<
以下面这个脚本为例
multi.sh
- #!/bin/bash
-
- read -p "enter number:" no
- read -p "enter name:" name
- echo "you have entered $no, $name"
借助 <<
符号进行自动化输入
- #!/bin/bash
- sh multi.sh << EOF
- 1
- mutoe
- EOF
但是有时候这种方法并不生效,比如 ssh-keygen
命令,那只有借助强大的 expect
命令了
- read -p "you are sure you wang to xxxxxx?[y/n]" input
- echo $input
- if [ $input = "y" ];then
- echo "ok "
- fi
我们还可以使用 read命令的-n 选项,此选项后面需要接一个数字,可以限制输入内容的个数。
- #!/bin/bash
- read -p "May I ask your name: " name
- echo "Hello $name"
- read -n1 -p "Press any key to exit"
- echo
- exit 0
目前,我们输入的内容都是可见的,但有些敏感的数据,如密码,信用卡号等信息,输入时并不想可见。那么可以使用read -s
。
这时再输入时,就有一个钥匙的标识,而且输入时不可见。
/usr/bin/expect <<EOF
spawn scp - r /usr/local 192.168.1.110:/home/zgabe/ #执行的命令expect {"yes/no" { send "yes\r" } } #若上面的命令弹出“yes/no”询问则输入yes
expect {"password:" { send "$P\r" }} #若上面的命令弹出“password”询问则输入密码$P
expect "*$" ? #普通用户是$, root用户是#
send "exit\r"expect eof
EOF #expect语句结束 注意 这个EOF一定要顶头写,前后不能有空格
#\r : 表确定
expect实现自动和交互式任务通信,安装 yum install -y expect
Expect的语法:
关键命令send、expect、spawn和interact:
expect 有两种用法,一种是直接写 expect 解释器的脚本,和 bash 类似,以 #!/usr/bin/expect
开头
下面是一个合格的 expect 脚本示例
- #!/bin/expect
-
- set IP [lindex $argv 0] # 读取第1个参数设置为 IP 变量
- set PASSWD [lindex $argv 1] # 读取第2个参数设置为 PASSWD 变量
- set CMD [lindex $argv 2] # 读取第3个参数设置为 CMD 变量
-
- spawn ssh $IP $CMD # spawn 来给命令加壳,以便于断言输出
- expect { # expect 是断言命令
- # 如果读取到屏幕上输出 (yes/no) 信息,则输入 "yes" 并按下回车键
- # exp_continue 是继续等待花括号内的断言, 如果不加这一句会直接跳出 expect
- "(yes/no)?" { send "yes\r"; exp_continue }
-
- "password:" { send "$PASSWD\r" } # 如果读取到屏幕上输出 password 信息,则输入 PASSWD 变量中的内容
- "*host " { exit 1 } # 如果读取到 "No route to host" 等内容, 就以非0状态退出
- }
- expect eof # 等待命令执行结束
需要注意的是,在 expect 解释器内, 除了几个特定关键字的命令,其他命令都不可用,这种方式适用于执行命令较少,单次需要交互较多的自动化脚本
第二种用法是在 bash 脚本中执行 expect 配合重定向操作符, 在有大量脚本需要执行的情况下推荐使用该方式
下面是我在 certbot
命令时使用的 shell 脚本,以供参考
- #!/bin/bash
-
- sudo expect << EOF
- spawn certbot --nginx
- expect {
- "Enter email address" { send "mutoe@foxmail.com\n";exp_continue}
- "Please read the Terms of Service" {send "A\n";exp_continue}
- "Would you be willing to share your email address" {send "N\n";exp_continue}
- "Which names would you like to activate HTTPS for" {send "\n";exp_continue}
- "You have an existing certificate that has exactly the same domains" {send "1\n";exp_continue}
- "Please choose whether or not to redirect HTTP traffic to HTTPS" {send "2\n";exp_continue}
- eof
- }
expect是关键的部分,在英文中,expect有“期待”的意思,采用了tcl的模式-动作语法,此语法有以下几种模式:
单一分支语法:
expect "hello" {send "you said hello"}
多分支模式语法:
- expect {
- "hello" {send "hello\r"; exp_continue}
- "world" {send "world\r"; exp_continue}
- "how are you ?" {send "Fine,thanks\r"}
- }
#!/bin/bash
P = "maomaochong"
for i in `seq 100 114` #因为我在自己电脑上测试所以这样写,多台可以写成`seq 100 114` 表示一个范围
do
/usr/bin/expect <<EOF
spawn scp - r / usr / local / zabbix - agent.tar.gz zgabe@192.168.1.$ {i}:/home/zgabe/expect {"yes/no" { send "yes\r" } #\r : 表示确定}
expect {"password:" { send "$P\r" }}
expect "*$" ? #普通用户是$, root用户是#
send "exit\r"expect eof
EOF #expect语句结束 注意 这个EOF一定要顶头写,前后不能有空格
done #shell的for循环结束。
https://blog.51cto.com/maomaochong/1812460
两种方法:
https://groups.google.com/g/comp.lang.tcl/c/qxmoNrsT0Kg
1.) 环境变量:
hostname=10.0.0.1; export hostname
expect -c '
set timeout 15spawn telnet $env(hostname)
...
'
or:
hostname=10.0.0.1 expect -c '
... ditto with $env(hostname)
'
2.) 通过在 sh 中连接不同引用的字符串(by concatenating differently quoted strings in sh):
hostname=10.0.0.1
expect -c '
set timeout 15
spawn telnet '"$hostname"'
...
'
注意事项:
> There are two approaches:
> 1.) per environment:
> hostname=10.0.0.1; export hostname
> expect -c '
> set timeout 15
> spawn telnet $env(hostname)
> ...
> '除非执行完expect后主动 unset hostname,否则其他程序也会读到这个变量,对于公共变量来说,你的修改会影响其他程序。
Unless you explicitly unset hostname after the call
to expect, you might pass the hostname also to other utilities
called from your script later. This is probably no problem
for the hostname, but could be for the password.> hostname=10.0.0.1 expect -c '
> ... ditto with $env(hostname)
> '这个例子中hostname只是当做环境变量传给expect,然后遗忘,如果你在后续sh脚本中还要用这个变量则不要用这个方法。
In this case, hostname is only passed to expect as an environment
variable, and forgotten afterwards, so if you still need the hostname
afterwards in the sh-script, don't use it this way.hostname=10.0.0.1
hostname=$hostname expect -c ' ... '
may appear moronic, but isn't at all. It leaves hostname
as a non-exported variable, while still sending it to expect.> 2.) by concatenating differently quoted strings in sh:
> hostname=10.0.0.1
> expect -c '
> set timeout 15
> spawn telnet '"$hostname"'
> ...
> '这个方法是不安全的:
Actually, I'm sorry for having posted it at all, because it is maximally
unsafe. Any magic characters in the value of hostname could create havoc
inside expect, as expect sees its value as unquoted(!) part of the script
rather than as the contents of some variable or array.For hostnames set explicitly in the shell-script this is a non-issue,
but I don't know, if this wasn't just a simplification of your problem
for posting.
方法一实例:
- #!/bin/sh
- IP_LIST=('127.0.0.x' '127.0.0.y' '127.0.0.z')
- for ip_addr in "${IP_LIST[@]}"
- do
- echo "$ip_addr"
- ip_addr=$ip_addr expect << 'EOS' #这一句的ip_addr=$ip_addr不能省
- set timeout 10
- spawn scp -p /home/foo/bar/baz.txt user@$::env(ip_addr):/home/destdir
- expect "*password*"
- send "pasword\r"
- expect eos; # do you mean `eof'?
- exit 0
- EOS
- done
:: 用于确保您引用的是全局环境变量。它在这里是可选的,因为它不在 proc 中。在 proc 中,如果要使用全局 var,则必须首先将其声明为全局 var 或简单地使用 $::var
"$env(MYPASSWORD)\r":
send "$env(MYPASSWORD)\r"
MYPASSWORD 是在shell中通过export设定的环境变量
bash - 如何在expect会话中使用shell变量? - Thinbug
shell 和expect脚本中的取参数
shell 直接使用$1 $2 来取得参数expect 使用 set name [lindex $argv 1] set host [lindex $argv 2]
.语法的问题。由于脚本头#!只能识别一种脚本,所以怎么让shell识别expect?有一种方式是利用<<将需要的expect脚本输入到os上的expect编译器。shell脚本例子:......shell command......
expect<<EOF
....expect command....
EOF
......shell command......
除了中间的expect command(就是我们需要的交互),其余的都是shell的相关命令语法。这里需要提醒的是,
<<EOF
内容
EOF
不要忘记这个结构。功能是将内容输出到标准输入。这里EOF可以是任何别的字符(没有测试过有没有非法字符)。
原文链接:https://blog.csdn.net/wxliu1989/article/details/21932955
例:expect脚本ssh.exp内容:
- #!/usr/bin/expect
- set timeout 2
- spawn ssh wan@10.229.130.107
- expect {
- "[Pp]assword" {send "123456\r";}
- "[Yy]es/no" {send "yes\r";exp_continue}
- }
- ...
- ...
- ...
- send "exit\r"
- exit 0
-
- #send:用于向进程发送字符串,注意一定要在末尾加\r回车
- #expect:从进程接收字符串
- #spawn:启动新的进程
- #interact:允许用户继续交互
经常要面临在某个环境编辑代码然后需要自动同步更新到其他机器的情况。对于这种问题,我们也可以借助expect脚本来实现。
例:
配置文件为sync.cfg:
- #","分隔的内容分别为需要同步的文件、目标路径和目标主机密码
- /home/long/tmp/*.py,wan@10.229.130.107:/home/wan/tmp,123456
shell脚本sync_ftp.sh内容:
- #!/bin/bash
- #读取配置信息
- for line in `cat sync.cfg`
- do
- #删除存在的信息文件,避免数据污染
- if [ -f info.dat ]
- then
- rm -rf info.dat
- fi
- touch info.dat
-
- #获取路径和密码
- srcaddr=`echo $line | cut -d , -f 1`
- destaddr=`echo $line | cut -d , -f 2`
- passwd=`echo $line | cut -d , -f 3`
-
- #查找指定日期需要同步的文件
- for tmp_file in `find $srcaddr -mtime 1 -type f`
- do
- echo $tmp_file >> info.dat
- done
-
- #调用expect脚本进行同步
- expect sync_ftp.exp $destaddr $passwd
-
- #判断是否同步成功
- if [ $? -eq 0 ]
- then
- rm -rf info.dat
- echo "all success!"
- else
- echo "sync $destaddr fail!"
- fi
- done
-
- exit 0
expect脚本sync_ftp.exp:
- #!/usr/bin/expect
- set timeout 20
-
- #读取输入参数
- set remotehost [lindex $argv 0]
- set remotepass [lindex $argv 1]
-
- #启动新进程,运行sftp协议
- spawn sftp $remotehost
- expect "[Pp]assword*"
- send "$remotepass\r"
-
- #发送文件
- expect "sftp>"
- set content [ open info.dat ]
- while { [ gets $content local_file ] != -1 }
- {
- send "put $local_file\r"
- expect "sftp>"
- }
- send "exit\r"
- exit 0
由于文件同步是每天都需要进行的,因此可以结合定时任务crond来进一步简化工作。crond是类unix系统下用来周期执行某种任务或者事件的一个守护进程,配置信息存放在/etct/crontab文件中。crontab是系统服务crond的控制命令。关于crontab的使用,在进程章节已经进行了介绍,这里就不再赘述了。
Expect命令是很强大的,可以实现各种自动化操作。自此,你不仅可以写出各种厉害的shell脚本,还能让它们自动执行,对于日常工作的处理将会有大大的提升。
注:expect命令
[set timeout 30]
基本上认识英文的都知道这是设置超时时间的,现在你只要记住他的计时单位是:秒
[spawn ssh -l username 192.168.1.1]
spawn command命令会fork一个子进程去执行command命令,然后在此子进程中执行后面的命令;
spawn是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。所以不要用 “which spawn“之类的命令去找spawn命令。好比windows里的dir就是一个内部命令,这个命令由shell自带,你无法找到一个dir.com 或 dir.exe 的可执行文件。
它主要的功能是给ssh运行进程加个壳,用来传递交互指令。
[expect "password:"]
这里的expect也是expect的一个内部命令,有点晕吧,expect的shell命令和内部命令是一样的,但不是一个功能,习惯就好了。这个命令的意思是判断上次输出结果里是否包含“password:”的字符串,如果有则立即返回,否则就等待一段时间后返回,这里等待时长就是前面设置的30秒
[send "ispass\r"]
这里就是执行交互动作,与手工输入密码的动作等效。
温馨提示: 命令字符串结尾别忘记加上 “\r”,如果出现异常等待的状态可以核查一下。
[interact]
执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。如果你只是登录过去执行一段命令就退出,可改为[expect eof]
expect可以让你使用“-c”选项,直接在命令行中执行它,例如:
expect -c '
spawn git pull
expect {
"Usernam*" {send "code_manager@ren001.com\n"; exp_continue }
"Passwor*" {send "$GIT_CODE_PASSWORD\n" ; exp_continue}
}
'
原文链接:https://blog.csdn.net/appke846/article/details/80513099
(适应于带这些参数的命令)
联想到文件重定向,在shell编程中有这样一种用法(参考Linux与UNIXSHELL编程指南 chapt 5.7):"command <
重定向操作符command <
对于需求1 要求的自动登陆ftp,并作系列操作,则可以用这种方法进行自动交互。代码如下:
[yjwan@test ~]$ ftp -i -n 192.168.21.46 <
user dbftp dbftp101
ls
EOF
下面是得到的结果:
Connected to 192.168.21.46.
220 developerjail FTP server (Version 6.00LS) ready.
331 Password required for dbftp.
230 User dbftp logged in.
Remote system type is UNIX.
Using binary mode to transfer files.
229 Entering Extended Passive Mode (|||54281|)
150 Opening ASCII mode data connection for '/bin/ls'.
total 8847424
-rw-r--r-- 1 dbftp www 9055318991 Aug 28 14:28 ewiz90.sql_2010-08-29
226 Transfer complete.
221 Goodbye.
测试可以发现,如上代码使用帐号名dbftp,密码dbftp101成功登陆了ftp服务器,并进入目录,ls出当前目录的文件。
注意事项:
1 这里一定要加-i ,否则必定失败!!
这里ftp用的-I 参数 结束了交互式输入,因此可以用文本的方式自动地输入用户密码以及操作方式
如果不用-I 参数,那么必定要求你手动输入密码的!!那么就达不到自动登陆的要求了
你可以man ftp找到这个参数
-i 关闭多文件传送中的交互式提示。请参考 prompt、mget、mput 和 mdelete 子命令,以取得多文件传送中的提示的描述。
-n 防止在起始连接中的自动登录。否则, ftp 命令会搜索 $HOME/.netrc 登录项,该登录项描述了远程主机的登录和初始化过程。请参考 user 子命令。
2 举一反三:只要是要求输入密码的命令,一般都带有一个参数,允许你从标准输入,输入用户密码,以及操作。
其他常用的自动登陆命令的参数
1) Passwd命令
Linux下 Passwd有参数
--stdin
This option is used to indicate that passwd should read the new password from standard input, which can be apipe.
所以linux下自动改变用户密码的办法就是
Echo 密码 |passwd –stdin 用户名
Freebsd没有以上参数 注意
他的方法是echo passwd |pw mod user username -h 0
2)smbpasswd
-s
This option causes smbpasswd to be silent (i.e. not issue prompts)
and to read its old and new passwords from standard input, rather
than from /dev/tty (like the passwd(1) program does). This option
is to aid people writing scripts to drive smbpasswd
3)sudo
在shell脚本中需要用root用的来执行指令:
sudo 自动输入密码
echo "password" | sudo -S netstat -tlnp
-S
The -S (stdin) option causes sudo to read the password from the standard input instead of the terminal device.
3 标准输入还可以用以下两种写法:
可以将下面的内容写入到一个文本
然后ftp -I –n ip
或者echo “..” |ftp –I –n ip
都是可以的!
因为他们都满足“有一个标准的输入”这个条件
4ssh的自动登陆没有这样的自动输入密码的参数
一般都是使用公钥/私钥的方式自动登录
这种文章满街都是,网上google一下ssh自动登陆就可以找到答案。
或者用下面的办法。
这个 expect基本属于一种编程了
基本任何需要交互式登陆的场合,他都可以解决,是相当的牛逼。
只要是第一种办法解决不了的,基本都可以用这种办法。
这里不介绍。因为另写了一遍关于expect 的。
- #!/bin/bash
- #把本机的公钥拷贝到/etc/ceph/ceph.conf 中 all_manage_ip 指定的所以IP的主机上
-
- line=`cat /etc/ceph/ceph.conf |grep -E 'all_manage_ip'`&&HOSTS=$(echo ${line#*=}|sed s/[[:space:]]//g)
-
- OLD_IFS="$IFS" #备份原值
- IFS="," #设分隔符为“,”
-
- for ip_addr in ${HOSTS}
- do
- echo "$ip_addr"
- ip_addr=$ip_addr expect << 'EOS'
- set timeout 10
- spawn ssh-copy-id SDS_Admin@$::env(ip_addr)
- expect "*password*"
- send "Admin@123stor\r"
- expect eof;
- #exit 0
- EOS
- done
- IFS="$OLD_IFS"
(摘自:Shell脚本-read命令:读取从键盘输入的数据_https://blog.csdn.net/qq_37279783/article/details/125525857)
代码1 使用 read 命令给多个变量赋值
#!/bin/bash
read -p "Enter some information > " name url age
echo "网站名字:$name"
echo "网址:$url"
echo "年龄:$age"
代码3 在指定时间内输入密码
#!/bin/bash
if
read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" && #第一次输入密码
read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" && #第二次输入密码
[ $pass1 == $pass2 ] #判断两次输入的密码是否相等
then
echo "Valid password"
else
echo "Invalid password"
fi
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。