当前位置:   article > 正文

【shell】实现交互|read读取键盘输入_shell脚本交互式输入命令

shell脚本交互式输入命令

目录

一、SHELL

1.1 输入单个指令

1.2 输入多行指令

限制输入内容的个数

控制输入内容的可见性

 二、Expect (自动输入yes/no、密码等)

简介

for 中嵌套 expect

在expect中使用shell的环境变量

实例

SSH登录

 FTP文件同步

如何自动输入密码

自动交互方法一:利用命令的自带参数,将标准输入作为手动输入的内容

自动交互方法2:利用expect脚本自动登陆

给用户SDS_Admin做免密登录

Shell脚本read读取从键盘输入

一、SHELL

1.1 输入单个指令

自动输入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 字符;

1.2 输入多行指令

输入多行指令我们需要借助输入重定向操作符 <<

以下面这个脚本为例

multi.sh

  1. #!/bin/bash
  2. read -p "enter number:" no
  3. read -p "enter name:" name
  4. echo "you have entered $no, $name"

借助 << 符号进行自动化输入

  1. #!/bin/bash
  2. sh multi.sh << EOF
  3. 1
  4. mutoe
  5. EOF

但是有时候这种方法并不生效,比如 ssh-keygen 命令,那只有借助强大的 expect 命令了

  1. read -p "you are sure you wang to xxxxxx?[y/n]" input
  2. echo $input
  3. if [ $input = "y" ];then
  4. echo "ok "
  5. fi

限制输入内容的个数

我们还可以使用 read命令的-n 选项,此选项后面需要接一个数字,可以限制输入内容的个数。

  1. #!/bin/bash
  2. read -p "May I ask your name: " name
  3. echo "Hello $name"
  4. read -n1 -p "Press any key to exit"
  5. echo
  6. exit 0

控制输入内容的可见性

目前,我们输入的内容都是可见的,但有些敏感的数据,如密码,信用卡号等信息,输入时并不想可见。那么可以使用read -s

这时再输入时,就有一个钥匙的标识,而且输入时不可见。
 

 二、Expect (自动输入yes/no、密码等)

   /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:

  • send:用于向进程发送字符串,注意一定要在末尾加\r回车
  • expect:从进程接收字符串
  • spawn:启动新的进程
  • interact:允许用户继续交互

expect 有两种用法,一种是直接写 expect 解释器的脚本,和 bash 类似,以 #!/usr/bin/expect 开头

下面是一个合格的 expect 脚本示例

  1. #!/bin/expect
  2. set IP [lindex $argv 0] # 读取第1个参数设置为 IP 变量
  3. set PASSWD [lindex $argv 1] # 读取第2个参数设置为 PASSWD 变量
  4. set CMD [lindex $argv 2] # 读取第3个参数设置为 CMD 变量
  5. spawn ssh $IP $CMD # spawn 来给命令加壳,以便于断言输出
  6. expect { # expect 是断言命令
  7. # 如果读取到屏幕上输出 (yes/no) 信息,则输入 "yes" 并按下回车键
  8. # exp_continue 是继续等待花括号内的断言, 如果不加这一句会直接跳出 expect
  9. "(yes/no)?" { send "yes\r"; exp_continue }
  10. "password:" { send "$PASSWD\r" } # 如果读取到屏幕上输出 password 信息,则输入 PASSWD 变量中的内容
  11. "*host " { exit 1 } # 如果读取到 "No route to host" 等内容, 就以非0状态退出
  12. }
  13. expect eof # 等待命令执行结束

需要注意的是,在 expect 解释器内, 除了几个特定关键字的命令,其他命令都不可用,这种方式适用于执行命令较少,单次需要交互较多的自动化脚本

第二种用法是在 bash 脚本中执行 expect 配合重定向操作符, 在有大量脚本需要执行的情况下推荐使用该方式

下面是我在 certbot 命令时使用的 shell 脚本,以供参考

  1. #!/bin/bash
  2. sudo expect << EOF
  3. spawn certbot --nginx
  4. expect {
  5. "Enter email address" { send "mutoe@foxmail.com\n";exp_continue}
  6. "Please read the Terms of Service" {send "A\n";exp_continue}
  7. "Would you be willing to share your email address" {send "N\n";exp_continue}
  8. "Which names would you like to activate HTTPS for" {send "\n";exp_continue}
  9. "You have an existing certificate that has exactly the same domains" {send "1\n";exp_continue}
  10. "Please choose whether or not to redirect HTTP traffic to HTTPS" {send "2\n";exp_continue}
  11. eof
  12. }

 expect是关键的部分,在英文中,expect有“期待”的意思,采用了tcl的模式-动作语法,此语法有以下几种模式:
单一分支语法:

expect "hello" {send "you said hello"}

多分支模式语法:

  1. expect {
  2. "hello" {send "hello\r"; exp_continue}
  3. "world" {send "world\r"; exp_continue}
  4. "how are you ?" {send "Fine,thanks\r"}
  5. }

for 中嵌套 expect

#!/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

在expect中使用shell的环境变量

两种方法:

https://groups.google.com/g/comp.lang.tcl/c/qxmoNrsT0Kg
1.) 环境变量:

hostname=10.0.0.1; export hostname
expect -c '
set timeout 15

spawn 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.

方法一实例:

how to use shell variable in expect session?--https://stackoverflow.com/questions/40376587/how-to-use-shell-variable-in-expect-session

  1. #!/bin/sh
  2. IP_LIST=('127.0.0.x' '127.0.0.y' '127.0.0.z')
  3. for ip_addr in "${IP_LIST[@]}"
  4. do
  5. echo "$ip_addr"
  6. ip_addr=$ip_addr expect << 'EOS' #这一句的ip_addr=$ip_addr不能省
  7. set timeout 10
  8. spawn scp -p /home/foo/bar/baz.txt user@$::env(ip_addr):/home/destdir
  9. expect "*password*"
  10. send "pasword\r"
  11. expect eos; # do you mean `eof'?
  12. exit 0
  13. EOS
  14. 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

实例

SSH登录

例:expect脚本ssh.exp内容:

  1. #!/usr/bin/expect
  2. set timeout 2
  3. spawn ssh wan@10.229.130.107
  4. expect {
  5. "[Pp]assword" {send "123456\r";}
  6. "[Yy]es/no" {send "yes\r";exp_continue}
  7. }
  8. ...
  9. ...
  10. ...
  11. send "exit\r"
  12. exit 0
  13. #send:用于向进程发送字符串,注意一定要在末尾加\r回车
  14. #expect:从进程接收字符串
  15. #spawn:启动新的进程
  16. #interact:允许用户继续交互

 FTP文件同步

经常要面临在某个环境编辑代码然后需要自动同步更新到其他机器的情况。对于这种问题,我们也可以借助expect脚本来实现。

例:
配置文件为sync.cfg:

  1. #","分隔的内容分别为需要同步的文件、目标路径和目标主机密码
  2. /home/long/tmp/*.py,wan@10.229.130.107:/home/wan/tmp,123456

shell脚本sync_ftp.sh内容:

  1. #!/bin/bash
  2. #读取配置信息
  3. for line in `cat sync.cfg`
  4. do
  5. #删除存在的信息文件,避免数据污染
  6. if [ -f info.dat ]
  7. then
  8. rm -rf info.dat
  9. fi
  10. touch info.dat
  11. #获取路径和密码
  12. srcaddr=`echo $line | cut -d , -f 1`
  13. destaddr=`echo $line | cut -d , -f 2`
  14. passwd=`echo $line | cut -d , -f 3`
  15. #查找指定日期需要同步的文件
  16. for tmp_file in `find $srcaddr -mtime 1 -type f`
  17. do
  18. echo $tmp_file >> info.dat
  19. done
  20. #调用expect脚本进行同步
  21. expect sync_ftp.exp $destaddr $passwd
  22. #判断是否同步成功
  23. if [ $? -eq 0 ]
  24. then
  25. rm -rf info.dat
  26. echo "all success!"
  27. else
  28. echo "sync $destaddr fail!"
  29. fi
  30. done
  31. exit 0

expect脚本sync_ftp.exp:

  1. #!/usr/bin/expect
  2. set timeout 20
  3. #读取输入参数
  4. set remotehost [lindex $argv 0]
  5. set remotepass [lindex $argv 1]
  6. #启动新进程,运行sftp协议
  7. spawn sftp $remotehost
  8. expect "[Pp]assword*"
  9. send "$remotepass\r"
  10. #发送文件
  11. expect "sftp>"
  12. set content [ open info.dat ]
  13. while { [ gets $content local_file ] != -1 }
  14. {
  15. send "put $local_file\r"
  16. expect "sftp>"
  17. }
  18. send "exit\r"
  19. exit 0

由于文件同步是每天都需要进行的,因此可以结合定时任务crond来进一步简化工作。crond是类unix系统下用来周期执行某种任务或者事件的一个守护进程,配置信息存放在/etct/crontab文件中。crontab是系统服务crond的控制命令。关于crontab的使用,在进程章节已经进行了介绍,这里就不再赘述了。

Expect命令是很强大的,可以实现各种自动化操作。自此,你不仅可以写出各种厉害的shell脚本,还能让它们自动执行,对于日常工作的处理将会有大大的提升。

原文:自动化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自动登陆就可以找到答案。

或者用下面的办法。

自动交互方法2:利用expect脚本自动登陆

这个 expect基本属于一种编程了

基本任何需要交互式登陆的场合,他都可以解决,是相当的牛逼。

只要是第一种办法解决不了的,基本都可以用这种办法。

这里不介绍。因为另写了一遍关于expect 的。

给用户SDS_Admin做免密登录

  1. #!/bin/bash
  2. #把本机的公钥拷贝到/etc/ceph/ceph.conf 中 all_manage_ip 指定的所以IP的主机上
  3. line=`cat /etc/ceph/ceph.conf |grep -E 'all_manage_ip'`&&HOSTS=$(echo ${line#*=}|sed s/[[:space:]]//g)
  4. OLD_IFS="$IFS" #备份原值
  5. IFS="," #设分隔符为“,”
  6. for ip_addr in ${HOSTS}
  7. do
  8. echo "$ip_addr"
  9. ip_addr=$ip_addr expect << 'EOS'
  10. set timeout 10
  11. spawn ssh-copy-id SDS_Admin@$::env(ip_addr)
  12. expect "*password*"
  13. send "Admin@123stor\r"
  14. expect eof;
  15. #exit 0
  16. EOS
  17. done
  18. IFS="$OLD_IFS"

Shell脚本read读取从键盘输入

(摘自: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
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/308750
推荐阅读
相关标签
  

闽ICP备14008679号