Nested for-loop gets executed only once

65 Views Asked by At

I'm writing a simple Python:Pexpect script that is a replacement of an old TCL:Expect working script I used to push configuration changes or command to our network switches.

If I write:

h_ls = open(hostls,"r")
c_ls = open(commands,"r")
for host in h_ls:
  host = host.strip()
  try:
    s = pxssh.pxssh(timeout=5,logfile = sys.stdout,options={
                "StrictHostKeyChecking": "no",
                "UserKnownHostsFile": "/dev/null",
                "AddKeysToAgent": "yes"},encoding='utf-8')
    s.login (host, user, password,\
            auto_prompt_reset=False,\
            original_prompt=r'.*#')
    print (">>>>> Working on "+host+" @ "+str(now)+" <<<<<\n")
    s.prompt()
    for cmd in c_ls:
     s.sendline(cmd+"\n")
     s.prompt()
     print(s.before)
    s.logout()
  except pxssh.ExceptionPxssh as e:
    print("***pxssh failed on login***")
    #traceback.print_exc()
    print("***"+str(e)+" "+host+"***")

commands contained in the "commands" variable get executed only once, in the first host of the list "hostls"

Instead if I write:

h_ls = open(hostls,"r")
for host in h_ls:
  host = host.strip()
  try:
    s = pxssh.pxssh(timeout=5,logfile = sys.stdout,options={
                "StrictHostKeyChecking": "no",
                "UserKnownHostsFile": "/dev/null",
                "AddKeysToAgent": "yes"},encoding='utf-8')
    s.login (host, user, password,\
            auto_prompt_reset=False,\
            original_prompt=r'.*#')
    print (">>>>> Working on "+host+" @ "+str(now)+" <<<<<\n")
    s.prompt()
    c_ls = open(commands,"r")
    for cmd in c_ls:
     s.sendline(cmd+"\n")
     s.prompt()
     print(s.before)
    s.logout()
  except pxssh.ExceptionPxssh as e:
    print("***pxssh failed on login***")
    #traceback.print_exc()
    print("***"+str(e)+" "+host+"***")

commands get correctly executed for every host of the "hostls" variable. What is that I do not understand?

Just for completeness, in the Tcl:Expect brother of this script, I've a logic similar to the one of the first example and it works correctly, so probably I'm missing something in my knowledge of Python. Has it something to do on how the block "try except" is managed? I searched info about it, but I didn't find anything meaningful about this specific argument.

2

There are 2 best solutions below

2
Marcin Orlowski On

It's not about error handling. Pay attention to scope of c_ls - in the first example, you're opening the file outside the loop, so object is created once, and when you iterate over it, it gets exhausted after the first iteration, leaving no commands to execute for the remaining hosts. In 2nd code however, you're opening the file inside the that loop, which means that a new c_ls object is created for each host, so it contains all the commands to be iterated over.

0
Roel Schroeven On

The open() function returns a file object, which can be used to iterate over the lines in the file. You can only iterate over them once, though. In the first iteration of the outer loop you iterate over them, exhausting the iterator. From the second iteration on, no elements remain.

Solution: either re-open the file on every iteration of the outer loop (which you did in your second code sample), or better store the commands in a list, which can be iterated over again and again:

with open(commands, "r") as f:
    c_ls = f.readlines()

(Using 'with' ensures that the file is closed properly even in the case of exceptions)

Edit: f.readlines() is clearer than list(f).