Simulating CLI Shell with python - python

I was asked to simulate CLI with Python.
This is what I did
def somefunction(a,b):
//codes here
//consider some other functions too
print "--- StackOverFlow Shell ---"
while True:
user_input = raw_input("#> ")
splitit = user_input.split(" ")
if splitit[0] == "add":
firstNum = splitit[1]
sNum = splitit[2]
result = somefunction(firstNum, sNum)
print result
//consider some other elif blocks with "sub", "div", etc
else:
print "Invalid Command"
I do also check the length of the list, here "splitit" I will allow only 3 argumets, first will be the operation, and second and third are the arguments with which some functions are to be performed, in case the argument is more than 3, for that i do put a check.
Though Somehow I manage to make it work, but is there a better way to achieve the same?

Use python CMD Module:
Check few examples given on the below pages
http://docs.python.org/library/cmd.html # Support for line-oriented command interpreters
http://www.doughellmann.com/PyMOTW/cmd - # Create line-oriented command processors
prompt can be set to a string to be printed each time the user is asked for a new command.
intro is the “welcome” message printed at the start of the program.
eg:
import cmd
class HelloWorld(cmd.Cmd):
"""Simple command processor example."""
prompt = 'prompt: '
intro = "Simple command processor example."

You should check out the VTE lib:
http://earobinson.wordpress.com/2007/09/10/python-vteterminal-example/
It works really well and you can very easily customize its look. This is how easy it is:
# make terminal
terminal = vte.Terminal()
terminal.connect ("child-exited", lambda term: gtk.main_quit())
terminal.fork_command()
# put the terminal in a scrollable window
terminal_window = gtk.ScrolledWindow()
terminal_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
terminal_window.add(terminal)

Related

Load item description from json file

Recently i saw a post about someone making a program that could control a computer it was launched on. (it was this one) Add commands to user input
I was really interested in it and I wanted to replicate it and improve my python skills on the way.
After watching some tutorials I had the ability to send and recieve emails and started working on some commands. First I added the ability to take screenshots as its the most important one. I then added functions and commands to do other stuff. Then I wanted to add a help command to display all commands if there is no args and the description of a specific command if there is an args. I first added the one without args and this is the code for it:
import json
user_input = "$say hello\n$help"
def help(*args):
if args == ():
for func_name, aliases in info_json.items():
print(func_name)
else:
pass
#print the description for the command
def command1():
print("I am command 1.")
def command2():
print("I am command 2.")
def command3():
print("I am command 3.")
def say(*args):
print(f"You said i should say \"{' '.join(args)}\"! Very cool :D")
def pause(sec):
print(f"I waited for {sec} seconds!")
commands = {
"$help":help,
"$pause":pause,
"$say":say,
"$command1":command1,
"$command2":command2,
"$command3":command3,
}
with open("commands.json") as json_file:
help_json = json.load(json_file)
def call_command(BEFEHL):
function, *args = BEFEHL.split(' ')
commands[function](*args)
for line in user_input.split("\n"):
try:
call_command(line)
except KeyError:
print("This command does not exist.")
I replaced the actual functions with print statements like the original author did :D
This code worked very well and I started to work on the description on specific functions. I created the commands.json example:
{
"command1": ["This command is command 1. It prints out 'I am command 1' "],
"command2": ["This command is command 2. It prints out 'I am command 2' "],
"command3": ["This command is command 3. It prints out 'I am command 3' "]
}
Is there any way you can print out the stuff in the json which stands behind the command? An example use would be:
>>> $help command1
print("This is command 1. It prints out 'I am command 1' ")
I would really appreciate to know if this is possible! :D
When you load a json, it basically acts like a Python dictionary, so you can retrieve the description of the command from its key, which you are passing as parameter.
Your help() function should look something like this:
def help(*args):
if args == ():
for func_name, aliases in help_json.items():
print(func_name)
else:
print(help_json.get(args[0], "Command does not exist"))
The second argument "Command does not exist" is the default value to print when the get() cannot find the key in the dictionary.

Prevent Command Line Usage During Function Run in Python/Terminal?

I'm using Terminal to run a python script with a series of print statements, separated by the time.sleep function.
If I'm printing various items over a period of 10 seconds, I would like to be able to prevent the user from inputting new commands into the command line during this time.
Is this possible in Terminal? Is there a work-around?
My goal here is to be able to provide the user with a lot of print statements, then have them answer a question only after the question is asked.
Because I don't want to overwhelm the user, I want to time delay the print statements so it appears more manageable (well, it's really for theatrical effect).
ie
for i in range(10):
print "Eating cheeseburger..."
time.sleep(1)
response = raw_input("What is your favorite color?")
if response == "blue":
blah blah blah etc.
Right now, the user can input a response before the question is asked, and while the cheeseburger is still being eaten. I want to prevent this.
The question is a platform specific one, as different operating systems handle standard input and output differently. I will attempt to answer your question for Linux:
You can use os.system to access the linux command stty -echo to make any text entered on the terminal invisible, and stty echo to make it visible again.
The next thing you want to achieve is to clear the stdin buffer when user input is asked. This can be achieved through the termios function tcflush that can be used to flush all input that has been received but not read by the terminal yet.
import os
import time
import termios
import sys
os.system("stty -echo")
for i in range(10):
print(i)
time.sleep(1)
os.system("stty echo")
termios.tcflush(sys.stdin, termios.TCIOFLUSH)
print(raw_input("Answer now:"))
The following is a version of Saurabh Shirodkar's answer written for the Windows console using ctypes.
import sys
import msvcrt
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
ENABLE_ECHO_INPUT = 0x0004
def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
kernel32.GetConsoleMode.errcheck = _check_bool
kernel32.GetConsoleMode.argtypes = (ctypes.c_void_p,
ctypes.POINTER(ctypes.c_ulong))
kernel32.SetConsoleMode.errcheck = _check_bool
kernel32.SetConsoleMode.argtypes = (ctypes.c_void_p, ctypes.c_ulong)
kernel32.FlushConsoleInputBuffer.errcheck = _check_bool
kernel32.FlushConsoleInputBuffer.argtypes = (ctypes.c_void_p,)
def echo_input(enable=True, conin=sys.stdin):
h = msvcrt.get_osfhandle(conin.fileno())
mode = ctypes.c_ulong()
kernel32.GetConsoleMode(h, ctypes.byref(mode))
if enable:
mode.value |= ENABLE_ECHO_INPUT
else:
mode.value &= ~ENABLE_ECHO_INPUT
kernel32.SetConsoleMode(h, mode)
def flush_input(conin=sys.stdin):
h = msvcrt.get_osfhandle(conin.fileno())
kernel32.FlushConsoleInputBuffer(h)
if __name__ == '__main__':
import time
if sys.version_info[0] == 2:
input = raw_input
echo_input(False)
for i in range(10):
print(i)
time.sleep(1)
echo_input(True)
flush_input()
print(input("Answer now: "))

Python3 Tkinter - Write input (y) to console for subprocess

I've been looking around for an answer to my problem but have been unlucky. I would like for the answer to work with native python and hopefully be simple.
My problem is that I'm using subprocess in my tkinter application, but one of the commands require you to write Y/N to be sure you want to proceed with the action.
So I'm looking for a way to write y into the terminal when a message like this appears:
Are you sure you want to continue? (y/N)
I've tried by running subprocess.run("y") but that doesn't seem to work.
I'm testing this on Debian Linux and to call the command that asks if I want to proceed, is subprocess.getoutput() so that I can check for errors.
CODE
class RemovePublicKeyDialog:
def __init__(self, parent):
top = self.top = Toplevel(parent)
Label(top, text="Who to remove?").pack()
self.e = Entry(top)
self.e.pack(padx=5)
b = Button(top, text="REMOVE", command=self.ok)
b.pack(pady=5)
def ok(self):
#print("value is " + self.e.get())
key = self.e.get()
cmd = subprocess.getoutput("gpg --delete-keys " + key)
print(cmd)
if ("key \"" + key + "\" not found" in cmd):
messagebox.showerror("Error", "No such public key.")
elif ("Delete this key from keyring?" in cmd):
#subprocess.run("echo 'y'")
messagebox.showinfo("Success", "Public key \"" + key + "\" deleted from keyring.")
else:
messagebox.showerror("Error", "Unknown error, did you input a key?")
self.top.destroy()
This is the "main" code, everything works but it's just that I need to input Y to get it to proceed.
Many command line utilities have a flag that automatically answers yes to any prompts - if you have access to the source code of your particular command, adding such a flag if it doesn't have one (or simply making a custom version that never prompts) may be the easiest solution. Some commands automatically do this if not run directly from a terminal - are you sure that this is even a problem?
If you know that there will be a single prompt, you could try:
subprocess.run("echo y | your-command", shell=True)
If there may be multiple prompts, you'd have to use one of the more complex options in the subprocess module, reading and parsing the command output to know when a reply is needed.

How do i output to console while providing input as well?

Essentially, i want what is in this thread: Output to console while preserving user input in ruby, but in Python. I have googled for quite a while, and found an ALMOST working solution, except that it blocked the main thread, as long as i wasn't typing anything in and pressing enter.
Some output of what i don't want to happen is here:
/raw:jtv!jtv#jtv.tmi.twitch.tv PRIVMSG #cobaltstreak :USERCOLOR ullr_son_of_sif #DAA520
Some example input of what i want is:
:jtv!jtv#jtv.tmi.twitch.tv PRIVMSG #cobaltstreak :USERCOLOR ullr_son_of_sif #DAA520
/raw
PRIV:jtv!jtv#jtv.tmi.twitch.tv PRIVMSG #cobaltstreak :SPECIALUSER nightbot subscriber
MSG #cobaltstreak :This shouldn't be here, but on the same line with /raw
This meaning, i want the bottom line of the console to preserve input, while outputting everything happening in the main thread without affecting input.
My current code is:
def console(q, m, lock):
while 1:
raw_input() # After pressing Enter you'll be in "input mode"
with lock:
i = raw_input('> ')
cmd = i.split(' ')[0]
msg = i.strip(cmd + ' ')
q.put(cmd)
m.put(msg)
if cmd == 'quit':
break
as well has:
cmd = cmd_queue.get()
msg = msg_queue.get()
action = cmd_actions.get(cmd)
if action is not None:
action(stdout_lock, msg)
Note the code above is the very first couple of lines in my while loop.
I am on Windows and using python 2.7.6

Python / Pexpect before output out of sync

I'm using Python/Pexpect to spawn an SSH session to multiple routers. The code will work for one router but then the output of session.before will get out of sync with some routers so that it will return the output from a previous sendline. This seems particularly the case when sending a blank line (sendline()). Anyone got any ideas? Any insight would be really appreciated.
Below is a sample of what I'm seeing:
ssh_session.sendline('sh version')
while (iresult==2):
iresult = ssh_session.expect(['>','#','--More--'],timeout=SESSION_TIMEOUT)
debug_print("execute_1 " + str(iresult))
debug_print("execute_bef " + ssh_session.before)
debug_print("execute_af " + ssh_session.after)
thisoutput = ssh_session.before
output += thisoutput
if(iresult==2):
debug_print("exec MORE")
ssh_session.send(" ")
else:
debug_print("exec: end loop")
for cmd in config_commands:
debug_print("------------------------------------------------\n")
debug_print ("running command " + cmd.strip() + "\n")
iresult=2
ssh_session.sendline(cmd.strip())
while (iresult==2):
iresult = ssh_session.expect([prompt+">",prompt+"#"," --More-- "],timeout=SESSION_TIMEOUT)
thisoutput = ssh_session.before
debug_print("execute_1 " + str(iresult))
debug_print("execute_af " + ssh_session.after)
debug_print("execute_bef " + thisoutput)
thisoutput = ssh_session.before
output += thisoutput
if(iresult==2):
debug_print("exec MORE")
ssh_session.send(" ")
else:
debug_print("exec: end loop")
I get this:
logged in
exec: sh version
execute_1 1
execute_bef
R9
execute_af #
exec: end loop
------------------------------------------------
running command config t
execute_1 1
execute_af #
execute_bef sh version
Cisco IOS Software, 1841 Software (C1841-IPBASEK9-M), Version 15.1(4)M4, RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport...
I've run into this before with pexpect (and I'm trying to remember how I worked around it).
You can re-synchronize with the terminal session by sending a return and then expecting for the prompt in a loop. When the expect times out then you know that you are synchronized.
The root cause is probably that you are either:
Calling send without a match expect (because you don't care about the output)
Running a command that produces output but expecting for a pattern in the middle of that output and then not to next prompt that is at end of the output. One way to deal with this is to change your expect pattern to "(.+)PROMPT" - this will expect until the next prompt and capture all the output of the command sent (which you can parse in the next step).
I faced a similar problem. I tried waiting for the command to be printed on the screen and the sending enter.
I you want to execute say command 'cmd', then you do:
session.send(cmd)
index = session.expect([cmd, pexpect.TIMEOUT], 1)
session.send('\n')
index = session.expect([whatever you expect])
Worked for me.
I'm not sure this is the root of your problem, but it may be worth a try.
Something I've run into is that when you spawn a session that starts with or lands you in a shell, you have to deal with quirks of the TERM type (vt220, color-xterm, etc.). You will see characters used to move the cursor or change colors. The problem is almost guaranteed to show up with the prompt; the string you are looking for to identify the prompt appears twice because of how color changes are handled (the prompt is sent, then codes to backspace, change the color, then the prompt is sent again... but expect sees both instances of the prompt).
Here's something that handles this, guaranteed to be ugly, hacky, not very Pythonic, and functional:
import pexpect
# wait_for_prompt: handle terminal prompt craziness
# returns either the pexpect.before contents that occurred before the
# first sighting of the prompt, or returns False if we had a timeout
#
def wait_for_prompt(session, wait_for_this, wait_timeout=30):
status = session.expect([wait_for_this, pexpect.TIMEOUT, pexpect.EOF], timeout=wait_timeout)
if status != 0:
print 'ERROR : timeout waiting for "' + wait_for_this + '"'
return False
before = session.before # this is what we will want to return
# now look for and handle any additional sightings of the prompt
while True:
try:
session.expect(wait_for_this, timeout=0.1)
except:
# we expect a timeout here. All is normal. Move along, Citizen.
break # get out of the while loop
return before
s = pexpect.spawn('ssh me#myserver.local')
s.expect('password') # yes, we assume that the SSH key is already there
# and that we will successfully connect. I'm bad.
s.sendline('mypasswordisverysecure') # Also assuming the right password
prompt = 'me$'
wait_for_prompt(s, prompt)
s.sendline('df -h') # how full are my disks?
results = wait_for_prompt(s, prompt)
if results:
print results
sys.exit(0)
else:
print 'Misery. You lose.'
sys.exit(1)
I know this is an old thread, but I didn't find much about this online and I just got through making my own quick-and-dirty workaround for this. I'm also using pexpect to run through a list of network devices and record statistics and so forth, and my pexpect.spawn.before will also get out of sync sometimes. This happens very often on the faster, more modern devices for some reason.
My solution was to write an empty carriage return between each command, and check the len() of the .before variable. If it's too small, it means it only captured the prompt, which means it must be at least one command behind the actual ssh session. If that's the case, the program sends another empty line to move the actual data that I want into the .before variable:
def new_line(this, iteration):
if iteration > 4:
return data
else:
iteration+=1
this.expect(":")
this.sendline(" \r")
data = this.before
if len(data) < 50:
# The numer 50 was chosen because it should be longer than just the hostname and prompt of the device, but shorter than any actual output
data = new_line(this, iteration)
return data
def login(hostname):
this = pexpect.spawn("ssh %s" % hostname)
stop = this.expect([pexpect.TIMEOUT,pexpect.EOF,":"], timeout=20)
if stop == 2:
try:
this.sendline("\r")
this.expect(":")
this.sendline("show version\r")
version = new_line(this,0)
this.expect(":")
this.sendline("quit\r")
return version
except:
print 'failed to execute commands'
this.kill(0)
else:
print 'failed to login'
this.kill(0)
I accomplish this by a recursive command that will call itself until the .before variable finally captures the command's output, or until it calls itself 5 times, in which case it simply gives up.

Categories

Resources