Je comprends qu'il existe des sujets similaires sur ce (comme ici) mais ma conception prévue est un peu plus complexe.

Je conçois un script CLI qui sera exécuté dans une fenêtre SSH. Le script sera hébergé et exécuté sur un serveur Ubuntu 14.10. Il est destiné à surveiller activement, au premier plan, l'état actuel des ports et des clients sur un commutateur hôte. Toutes les 30 secondes ou tel que défini par l'utilisateur, il récupère les données via SNMP, puis actualise les informations et les affiche à l'écran. Lorsqu'il attend la prochaine actualisation, un minuteur indique quand il demandera à nouveau des informations à l'appareil.

Je veux permettre à l'utilisateur d'appuyer sur des touches spécifiques pour changer la vue de sortie ou modifier des variables clés à tout moment. (La fonctionnalité est similaire à Unix top.) Par exemple, appuyer sur t leur demanderait d'entrer le nombre de secondes souhaité entre les boucles. h, m ou i basculeraient entre l'affichage / le masquage de certaines colonnes. Celles-ci ne feraient pas interrompre le chronomètre ni quitter la boucle car les modifications seraient appliquées à la prochaine actualisation. r forcerait une actualisation immédiate et appliquerait les modifications. q ou Ctrl+C quitteraient le script.

L'activité principale ressemblerait à ceci:

Query loop <-----------------------------
     |                                   |
     |                              Process Data
     |                                   ^
     |                                   |
     v                               Query Data    #SNMPBULKWALK
   Timer <-------------                  ^
     | |               |                 |          
     | |        Check time remaining     |
     | |               ^                 |
     | |_______________|                 |
     |___________________________________|

Avec les interruptions de pression sur les touches, cela agirait comme ceci:

Query loop <----------------------                      
    |                             |        ???<---Change variables
    |                        (Continue)                  ^
    V                             |                      |
  Timer <---------          !!INTERRUPT!!---------> Identify key
    | |           |               ^
    | |  Check time remaining     |
    | |           ^               |
    | |___________|               |
    |_____________________________|

Je suis un peu perplexe ici. Je suis amené à croire que j'aurai probablement besoin d'implémenter le threading - avec lequel je n'ai pas d'expérience - car une boucle while ne semble pas en elle-même satisfaire ce dont nous avons besoin. Je ne sais pas non plus comment injecter les modifications dans l'objet qui contient les variables (par exemple, la minuterie, les indicateurs pour le formatage de l'affichage) car il sera constamment utilisé par notre boucle.

3
Kamikaze Rusher 22 juil. 2015 à 01:59

2 réponses

Meilleure réponse

Ce n'est rien de compliqué et rien ne nécessite de paquet.

Son seul problème est qu'il nécessite la restauration du terminal à la normale à la sortie du programme.

C'est à dire. Si le programme plante, le terminal ne sera pas restauré et l'utilisateur ne verra pas ce qu'il est en train de taper.

Mais si l'utilisateur sait ce qu'il fait, il peut forcer le redémarrage du shell et tout reviendra à la normale.

Bien sûr, vous pouvez utiliser ce code plus facilement et utiliser raw_input () pour faire les choses.

from thread import start_new_thread as thread
from time import sleep
# Get only one character from stdin without echoing it to stdout
import termios
import fcntl
import sys
import os
fd = sys.stdin.fileno()
oldterm, oldflags = None, None

def prepareterm ():
    """Turn off echoing"""
    global oldterm, oldflags
    if oldterm!=None and oldflags!=None: return
    oldterm = termios.tcgetattr(fd)
    newattr = oldterm[:] # Copy of attributes to change
    newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
    termios.tcsetattr(fd, termios.TCSANOW, newattr)
    oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)

def restoreterm ():
    """Restore terminal to its previous state"""
    global oldterm, oldflags
    if oldterm==None and oldflags==None: return
    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
    oldterm, oldflags = None, None

def getchar():
    """Get character from stdin"""
    prepareterm()
    while 1:
        try:
            c = sys.stdin.read(1)
            break
        except IOError:
            try: sleep(0.001)
            except: restoreterm(); raise KeyboardInterrupt
    restoreterm()
    return c

def control ():
    """Waits for keypress"""
    global running, done
    while running:
        c = getchar().lower()
        print "Keypress:", c
        if c=="q": print "Quitting!"; running = 0; break
    done += 1

def work ():
    """Does the server-client part"""
    global done
    while running:
        # Do your stuff here!!!
        # Just to illustrate:
        print "I am protending to work!\nPress Q to kill me!"
        sleep(1)
    print "I am done!\nExiting . . ."
    done += 1

# Just to feel better
import atexit
atexit.register(restoreterm)

# Start the program
running = 1
done = 0
thread(work, ())
thread(control, ())
# Block the program not to close when threads detach from the main one:
while running:
    try: sleep(0.2)
    except: running = 0
# Wait for both threads to finish:
while done!=2:
    try: sleep(0.001)
    except: pass # Ignore KeyboardInterrupt
restoreterm() # Just in case!

En pratique, le programme ne devrait jamais pouvoir quitter sans restaurer le terminal à la normale, mais la merde arrive.

Maintenant, vous pouvez simplifier les choses en utilisant un seul thread et votre boucle de travail placée dans le thread principal, et utiliser raw_input pour acquérir des commandes de l'utilisateur. Ou peut-être même mieux, mettez votre code serveur-client en arrière-plan et attendez l'entrée dans le thread principal.

Il sera également probablement plus sûr d'utiliser le module de threading au lieu de threads bruts.

Si vous utilisez le module asyncore, vous obtiendrez chaque client fonctionnant pour son propre compte et votre thread principal sera occupé par asyncore.loop (). Vous pouvez le remplacer, c.-à-d. réécrivez-le pour vérifier les entrées et autres choses que vous souhaitez faire, tout en gardant les vérifications asyncores synchronisées. De plus, de lourdes charges nécessitent de surcharger certaines fonctions désagréables à l'intérieur, car son tampon est fixé à 512 octets, si je ne me trompe pas. Sinon, cela peut être une bonne solution à votre problème.

Et, enfin, pour être clair, le code pour aucune entrée utilisateur en écho est pris et adapté du module getpass. Juste un petit peu il y a le mien.

1
Dalen 22 juil. 2015 à 21:20

Ctrl + C est simple: cela lancera une exception que vous pourrez attraper (car le processus reçoit un signal lorsque cela se produit).

Pour fournir de l'interactivité en attendant, vous devriez chercher à écrire du code asynchrone. Twisted est éprouvé et capable, mais a une légère courbe d'apprentissage (IMHO). Il existe également asyncore qui pourrait être plus facile à démarrer, mais plus limité et Je ne suis pas sûr qu'il gère votre cas d'utilisation. Il existe également asyncio, mais il n'existe que dans Python 3.4.

0
Krumelur 21 juil. 2015 à 23:08