Python Forum
Problem with user defined main menu function
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Problem with user defined main menu function
#1
Hello,

I am writing a little python script to perform various sysadmin tasks. I have defined the main menu where you pick the choice of task to perform as a function, so I can easily call the main menu again after the specific task that the user picked was performed.

The function looks like this :

import getpass
import os
import subprocess
import sys

import paramiko

print("""

     M A I N - M E N U
     *****************


     1. Run a command on a remote server(s).
     2. IP scanning
     3. Quit
    """)


def main_menu():
    print("""
 M A I N - M E N U
 *****************


 1. Run a command on a remote server(s).
 2. IP Scanning
 3. Quit
 """)


## Get input ###
choice = input('Enter your choice [1-3] : ')

### Convert string to int type ##
choice = int(choice)

### Take action as per selected menu-option ###
if choice == 1:

    ssh = paramiko.SSHClient()

    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    command = input("Enter a command to run on the remote server(s): ")
    username = input("Enter your username: ")
    password = getpass.getpass(prompt='Enter your password: ')

    while True:

        servers = input("Enter the full path of your servers file: ")
        if not os.path.exists(servers):
            print("The provided file does not exist")
            continue

        else:
            break

    with open(servers, 'r') as fp:
        for ip in [line.strip() for line in fp.readlines()]:
            ssh.connect(ip, 22, username, password)
            stdin, stdout, stderr = ssh.exec_command(command)
            print(ip, stdout.readline())
            ssh.close()
        main_menu()

elif choice == 2:

    subnet = input("Enter a subnet to scan (up to the third octet). Example 127.0.0 : ")
    dot = "."
    for ping in range(1, 5):
        address = subnet + dot + str(ping)
        res = subprocess.call(['ping', '-c', '3', address], stdout=open(os.devnull, 'wb'))
        if res == 0:
            print("ping to", address, "OK!")
        elif res == 2:
            print("no response from", address)
        else:
            print("ping to", address, "FAILED!")

    main_menu()


elif choice == 3:
    sys.exit()
The Problem is that after the user picks a choice and then the code is executed, the function returns only the print function and then the program finishes, so the user is not able to pick another choice, as seen below :

Output:
M A I N - M E N U ***************** 1. Run a command on a remote server(s). 2. IP scanning 3. Quit Enter your choice [1-3] : 2 Enter a subnet to scan (up to the third octet). Example 127.0.0 : 192.168.1 ping to 192.168.1.1 OK! ping to 192.168.1.2 FAILED! ping to 192.168.1.3 OK! ping to 192.168.1.4 OK! M A I N - M E N U ***************** 1. Run a command on a remote server(s). 2. IP Scanning 3. Quit Process finished with exit code 0
As I'm a noob I suspect that this may be just an indentation mistake , but I'm not able to find it. I have reformatted with PyCharm, but still...

Any help is appreciated.

Thanks.
Reply
#2
What you are doing currently - your main_menu function has just one print function. everything else is in the global scope. so you get user choice once, execute whatever it is, at the end you call main_menu. It prints the menu and program ends.

you shouldn't be calling main_menu() recursively, however (that was your intent obviously). Use a loop instead. Also define the action associated with each choice as separate function, then just call that function from the menu
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#3
Hi Buran,

Although your reply seemed vague (because I'm a newb) at first, it was actually extremely helpful!

I have completely changing the code by putting all of it's "tools" into separate functions as per your advice, including the main menu. I have also red and learned about globals and locals.

The code now looks like this :

import getpass
import os
import subprocess
import sys
import paramiko


def run_remote():
    ssh = paramiko.SSHClient()

    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    command = input("Enter a command to run on the remote server(s): ")
    username = input("Enter your username: ")
    password = getpass.getpass(prompt='Enter your password: ')

    while True:

        servers = input("Enter the full path of your servers file: ")
        if not os.path.exists(servers):
            print("The provided file does not exist")
            continue

        else:
            break

    with open(servers, 'r') as fp:
        for ip in [line.strip() for line in fp.readlines()]:
            ssh.connect(ip, 22, username, password)
            stdin, stdout, stderr = ssh.exec_command(command)
            print(ip, stdout.readline())
            ssh.close()
            input("Press any key to continue...")
            main_menu()


def ip_scan():
    subnet = input("Enter a subnet to scan (up to the third octet). Example 127.0.0 : ")
    dot = "."
    for ping in range(1, 5):
        address = subnet + dot + str(ping)
        res = subprocess.call(['ping', '-c', '3', address], stdout=open(os.devnull, 'wb'))
        if res == 0:
            print("ping to", address, "OK!")
        elif res == 2:
            print("no response from", address)
        else:
            print("ping to", address, "FAILED!")
            continue
    input("Press any key to continue...")
    main_menu()


def main_menu():
    print("""
     M A I N - M E N U
     *****************
     1. Run a command on a remote server(s).
     2. IP scanning
     3. Quit
     """)
    global choice
    choice = input('Enter your choice [1-3] : ')
    choice = int(choice)

    if choice == 1:
        run_remote()

    if choice == 2:
        ip_scan()

    elif choice == 3:
        sys.exit()


main_menu()
And more importantly - it does want I want it to do! :)

Thank you!
Reply
#4
Glad that it work. However you didn't understood my advise 100%, maybe it was a bit vague indeed.

Here is one way:
import getpass
import os
import subprocess
import paramiko
 
 
def run_remote():
    ssh = paramiko.SSHClient()
 
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    command = input("Enter a command to run on the remote server(s): ")
    username = input("Enter your username: ")
    password = getpass.getpass(prompt='Enter your password: ')
 
    while True:
        servers = input("Enter the full path of your servers file: ")
        if not os.path.exists(servers):
            print("The provided file does not exist")
        else:
            break
 
    with open(servers, 'r') as fp:
        for ip in [line.strip() for line in fp.readlines()]:
            ssh.connect(ip, 22, username, password)
            stdin, stdout, stderr = ssh.exec_command(command)
            print(ip, stdout.readline())
            ssh.close()
            input("Press any key to continue...")

 
 
def ip_scan():
    subnet = input("Enter a subnet to scan (up to the third octet). Example 127.0.0 : ")
    dot = "."
    for ping in range(1, 5):
        address = subnet + dot + str(ping)
        res = subprocess.call(['ping', '-c', '3', address], stdout=open(os.devnull, 'wb'))
        if res == 0:
            print("ping to", address, "OK!")
        elif res == 2:
            print("no response from", address)
        else:
            print("ping to", address, "FAILED!")
    input("Press any key to continue...")

 
def main():
    while True:
        print("""
        M A I N - M E N U
        *****************
        1. Run a command on a remote server(s).
        2. IP scanning
        3. Quit
        """)

        # no need to convert to int.
        # This will prevent errors from input that cannot be converted to int
        choice = input('Enter your choice [1-3] : ') 
    
        if choice == '1': # note '1', not 1
            run_remote()
        elif choice == '2': # note using elif, not if
            ip_scan()
        elif choice == '3':
            break
        else:
            print('Invalid choice! Try again')
 
 
if __name__ == '__main__':
    main()
or you can skip the main function

import getpass
import os
import subprocess
import paramiko
 
 
def run_remote():
    ssh = paramiko.SSHClient()
 
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    command = input("Enter a command to run on the remote server(s): ")
    username = input("Enter your username: ")
    password = getpass.getpass(prompt='Enter your password: ')
 
    while True:
        servers = input("Enter the full path of your servers file: ")
        if not os.path.exists(servers):
            print("The provided file does not exist")
        else:
            break
 
    with open(servers, 'r') as fp:
        for ip in [line.strip() for line in fp.readlines()]:
            ssh.connect(ip, 22, username, password)
            stdin, stdout, stderr = ssh.exec_command(command)
            print(ip, stdout.readline())
            ssh.close()
            input("Press any key to continue...")

 
 
def ip_scan():
    subnet = input("Enter a subnet to scan (up to the third octet). Example 127.0.0 : ")
    dot = "."
    for ping in range(1, 5):
        address = subnet + dot + str(ping)
        res = subprocess.call(['ping', '-c', '3', address], stdout=open(os.devnull, 'wb'))
        if res == 0:
            print("ping to", address, "OK!")
        elif res == 2:
            print("no response from", address)
        else:
            print("ping to", address, "FAILED!")
    input("Press any key to continue...")
 
 
if __name__ == '__main__':
    while True:
        print("""
        M A I N - M E N U
        *****************
        1. Run a command on a remote server(s).
        2. IP scanning
        3. Quit
        """)

        # no need to convert to int.
        # This will prevent errors from input that cannot be converted to int
        choice = input('Enter your choice [1-3] : ') 
    
        if choice == '1': # note '1', not 1
            run_remote()
        elif choice == '2': # note using elif, not if
            ip_scan()
        elif choice == '3':
            break
        else:
            print('Invalid choice! Try again')
I also remove unnecessary use of continue.
There are small issues here and there that can be done differenty or in more pythonic way, but that may advise to not call main_menu recursively
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How to Randomly Print a Quote From a Text File When User Types a Command on Main Menu BillKochman 13 666 Yesterday, 07:07 AM
Last Post: Bronjer
  Variable is not defined error when trying to use my custom function code fnafgamer239 4 576 Nov-23-2023, 02:53 PM
Last Post: rob101
  [PROBLEM] Removing IDLE from context menu WebSpider 1 461 Sep-28-2023, 03:35 PM
Last Post: deanhystad
  Printing the variable from defined function jws 7 1,281 Sep-03-2023, 03:22 PM
Last Post: deanhystad
Information How to take url in telegram bot user input and put it as an argument in a function? askfriends 0 1,074 Dec-25-2022, 03:00 PM
Last Post: askfriends
  Getting NameError for a function that is defined JonWayn 2 1,092 Dec-11-2022, 01:53 PM
Last Post: JonWayn
Question Help with function - encryption - messages - NameError: name 'message' is not defined MrKnd94 4 2,874 Nov-11-2022, 09:03 PM
Last Post: deanhystad
  How to print the output of a defined function bshoushtarian 4 1,279 Sep-08-2022, 01:44 PM
Last Post: deanhystad
  User-defined function to reset variables? Mark17 3 1,644 May-25-2022, 07:22 PM
Last Post: Gribouillis
  Multiple user defined plots with secondary axes using for loop maltp 1 1,442 Apr-30-2022, 10:19 AM
Last Post: maltp

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020