【问题标题】:while loop issues in expect script期望脚本中的while循环问题
【发布时间】:2026-01-29 16:05:02
【问题描述】:

我编写了一个期望脚本,其工作原理如下:

  1. ssh 到 server1
  2. 从 server1 ssh 到另一个服务器 server2
  3. 从 server2 到 server3,然后 sudo 进入用户并运行命令。

在脚本中,我从名为 hostnames.out 和 commands.out 的两个文件中读取了要执行的主机名和命令。我使用 while 循环遍历 hostnames.out 中的每个条目并运行 commands.out 文件中的命令。

我在 hostnames.out 中使用单个条目测试了我的脚本,它工作正常,但是当我添加多行时,它不会从第二行开始运行主机名上的命令。

commands.out 文件的格式是(每行一个命令):

ls -lrt 主机名 我是谁

hostnames.out 文件的格式为:

server1 用户密码 server2 用户密码 server3 用户密码

我已附上脚本以供参考。请告诉我问题出在哪里。

#!/usr/bin/expect
#####################################################
# script to automate manual works - remote 2        #
# Gopinath                                          #
#####################################################
#Variable declaration:

#Setting variable "prompt" for multiple prompts:
set prompt {[]$#%]\s*$}

#Reading commands list from file:

set fp1 [open "commands_list_2.out" "r"]
set file_data [read $fp1]
close $fp1


#  read the hosts file one line at a time
#  There should be no new line at the end of the hostnames.out file

     set fp [open "hostnames_2.out" "r"]

     while { [gets $fp data] >= 0 } {

          set ssh1 [lindex $data 0]
          set ssh1_usr [lindex $data 1]
          set ssh1_pwd [lindex $data 2]
      set ods [lindex $data 3]
      set ods_usr [lindex $data 4]
          set ods_pwd [lindex $data 5]
      set serv1 [lindex $data 6]
      set serv1_usr [lindex $data 7]
      set serv1_pwd  [lindex $data 8]

          puts $ssh1 
      puts $ssh1_usr
          puts $ssh1_pwd
      puts $ods
      puts $ods_usr
      puts $ods_pwd
      puts $serv1
      puts $serv1_usr
          puts $serv1_pwd

     spawn -noecho ssh $ssh1_usr@$ssh1
     expect { 

        "*password:" { send "$ssh1_pwd\r"}
        "*route*" { puts "login failed"; exit 1 }
        "timed out" { puts "login failed timed out"; exit 1 }
         }

      expect {
               -re $prompt { send "whoami\r"}
             }            


                  expect -re $prompt  { 

                            send  "ssh $ods_usr@$ods\r" }           
                  expect {
                     "password:" { send "$ods_pwd\r" }
                         }
                 }


           expect {
         -re $prompt { send "whoami\r"}
          }


                   expect -re $prompt  {

                            send  "ssh $serv1_usr@$serv1\r" }
                  expect {
                     "password:" { send "$serv1_pwd\r" }
                         }

                   expect -re $prompt 


         foreach a [list $file_data] {
         send "$a"
         expect -re prompt
         }
          expect -re prompt {   
              send "exit\r"
              }
     expect eof
     close $fp
`

【问题讨论】:

  • 您是否收到任何错误消息?
  • 不,当我将第二行添加到 hostnames.out 文件时,我没有收到任何错误消息。

标签: tcl


【解决方案1】:

据我所知,主要问题是您在不同主机上的这个复杂的子进程链没有被正确清理。不一致的缩进使您的代码更难阅读,这使情况更加复杂。

关键是您希望 server1 和 server2 上的 ssh 会话只是 ssh 到链中的下一个服务器,而 server3 上的 ssh 会话只是做一个须藤。最简单的解决方法是使用exec ssh 代替sshexec sudo 代替sudo。然后,在最内层执行exit 将导致所有这些中间程序也终止,您的expect eof 将触发。

spawn -noecho ssh $ssh1_usr@$ssh1
expect {
    "*route*" { puts "login failed"; exit 1 }
    "timed out" { puts "login failed timed out"; exit 1 }
    "*password:" { send "$ssh1_pwd\r"; exp_continue }
     -re $prompt { send "whoami\r" }
}
expect -re $prompt  { 
    send "exec ssh $ods_usr@$ods\r"
}
expect {
    "password:" { send "$ods_pwd\r"; exp_continue }
    -re $prompt { send "whoami\r" }
}
expect -re $prompt  {
    send "exec ssh $serv1_usr@$serv1\r"
}
expect {
    "password:" { send "$serv1_pwd\r"; exp_continue }
    -re $prompt { send "whoami\r" }
}

# You seem to be missing the sudo here?

foreach a [split $file_data "\n"] {
    expect -re $prompt
    send "$a"
}

expect -re $prompt
send "exit\r"
expect eof
close;          ### TERMINATE THE SPAWN. IMPORTANT!

您还遇到了使用[list $file_data] 的问题,而您应该将其与[split $file_data "\n"] 分开。


上面的代码需要为每组凭据循环。这部分最容易写成这样(假设您真的没有足够的数据行来需要流处理):

set fp [open "hostnames_2.out" "r"]
set hostnamedata [split [read $fp] "\n"]
close $fp

foreach line $hostnamedata {
    lassign $line  ssh1 ssh1_usr ssh1_pwd  ods ods_usr ods_pwd  serv1 serv1_usr serv1_pwd

    # The code from above goes here
}

单独建议考虑在所有中间服务器上设置 SSH 公钥,并使用 -A 选项来 ssh。这可以在不影响安全性的情况下大大简化身份验证/授权部分。

【讨论】:

  • 还请注意,您可以将要执行的命令传递给 ssh,例如 ssh user1@serv1 ssh user2@serv2 sudo yourcommand。现在你只需要处理密码的东西。