busybox ash via paramiko not emitting prompt on stdout - python

I created Python GUI that takes a list of commands as input and executes the list through a Telnet or SSH session. While opening a SSH session (using Paramiko in Python) I run commands on various devices using this code in a for loop:
stdin.write(command+"\n")
then = time.time()
timeout=10.0
while not stdout.channel.exit_status_ready():
if timeout is not None:
timeTest =time.time() - then
if timeout <= timeTest:
break
# Only print data if there is data to read in the channel
if stdout.channel.recv_ready():
# Write data from stdout
temp=str(stdout.channel.recv(1024))
print temp
if (temp.endswith(delim)) or (temp.endswith("# ")) :
print "successful exit"
break
The code is designed to be used on modems that have BusyBox installed. Thus it's common for a user to enter a command to open busybox and run a sequence of commands in the BusyBox shell. As you can see in this line of code "if (temp.endswith(delim)) or (temp.endswith("# "))" the while loop is suppose to break when the command prompt of busybox is detected which is a "# " (this means the command has finished outputting).
The problem I'm having is that BusyBox isn't printing the command prompt to stdout or in the debug line "print temp". Why is this? The command outputs (an example is ls -l) are successfully printed to stdout but not the command prompt or busybox intro message: when a user enters busybox on these modems a introduction message is printed "BusyBox v1.17.2 (2014-10-02 10:50:35 PDT) built-in shell (ash)
Enter 'help' for a list of built-in commands." which is also not printed to STDOUT. This is forcing the code to utilize the timeout for each command executed in busybox which is undesirable, i.e. it's slow and there could be command outputs that take longer than the desired timeout so it's best to look for the command prompt.
Is this issue due to the implementation of ash in BusyBox? Is there a way to receive the command prompt text?

First: If you're trying to recognize shell prompts when invoking a shell programmatically, you're Doing It Wrong.
If you use exec_command() on a new channel (over the same transport) for each command you want to run, you'll get a separate stdout, stderr, etc. for each command; have the exit status for that command individually reported, and never need to guess whether a piece of output is from the command or from the outer shell.
Second: If you really want to do it that way (and, again, you shouldn't!), you should be using invoke_shell() after a get_pty() call, not exec_command('sh').

See the entry on PS1 (the variable specifying the prompt to emit) in IEEE standard 1003.1 (emphasis added):
PS1
Each time an interactive shell is ready to read a command, the value of this variable shall be subjected to parameter expansion and written to standard error. The default value shall be "$ ". For users who have specific additional implementation-defined privileges, the default may be another, implementation-defined value. The shell shall replace each instance of the character '!' in PS1 with the history file number of the next command to be typed. Escaping the '!' with another '!' (that is, "!!" ) shall place the literal character '!' in the prompt. This volume of POSIX.1-2008 specifies the effects of the variable only for systems supporting the User Portability Utilities option.
Thus, any POSIX-compliant shell -- not just busybox ash, but ksh, dash, bash, and all the rest -- must write its prompt to stderr, not stdout.
Your simple fix, then, is to either read from stderr, or to use redirection to combine the streams (running exec 2>&1, for instance).

Related

Run script for send in-game Terraria server commands

In the past week I install a Terraria 1.3.5.3 server into an Ubuntu v18.04 OS, for playing online with friends. This server should be powered on 24/7, without any GUI, only been accessed by SSH on internal LAN.
My friends ask me if there is a way for them to control the server, e.g. send a message, via internal in-game chat, so I thought use a special character ($) in front of the desired command ('$say something' or '$save', for instance) and a python program, that read the terminal output via pipe, interpreter the command and send it back with a bash command.
I follow these instructions to install the server:
https://www.linode.com/docs/game-servers/host-a-terraria-server-on-your-linode
And config my router to forward a dedicated port to the terraria server.
All is working fine, but I really struggle to make python send a command via "terrariad" bash script, described in the link above.
Here is a code used to send a command, in python:
import subprocess
subprocess.Popen("terrariad save", shell=True)
This works fine, but if I try to input a string with space:
import subprocess
subprocess.Popen("terrariad \"say something\"", shell=True)
it stop the command in the space char, output this on the terminal:
: say
Instead of the desired:
: say something
<Server>something
What could I do to solve this problem?
I tried so much things but I get the same result.
P.S. If I send the command manually in the ssh putty terminal, it works!
Edit 1:
I abandoned the python solution, by now I'll try it with bash instead, seem to be more logic to do this way.
Edit 2:
I found the "terrariad" script expect just one argument, but the Popen is splitting my argument into two no matter the method I use, as my input string has one space char in the middle. Like this:
Expected:
terrariad "say\ something"
$1 = "say something"
But I get this of python Popen:
subprocess.Popen("terrariad \"say something\"", shell=True)
$1 = "say
$2 = something"
No matter i try to list it:
subprocess.Popen(["terrariad", "say something"])
$1 = "say
$2 = something"
Or use \ quote before the space char, It always split variables if it reach a space char.
Edit 3:
Looking in the bash script I could understand what is going on when I send a command... Basically it use the command "stuff", from the screen program, to send characters to the terraria screen session:
screen -S terraria -X stuff $send
$send is a printf command:
send="`printf \"$*\r\"`"
And it seems to me that if I run the bash file from Python, it has a different result than running from the command line. How this is possible? Is this a bug or bad implementation of the function?
Thanks!
I finally come with a solution to this, using pipes instead of the Popen solution.
It seems to me that Popen isn't the best solution to run bash scripts, as described in How to do multiple arguments with Python Popen?, the link that SiHa send in the comments (Thanks!):
"However, using Python as a wrapper for many system commands is not really a good idea. At the very least, you should be breaking up your commands into separate Popens, so that non-zero exits can be handled adequately. In reality, this script seems like it'd be much better suited as a shell script.".
So I came with the solution, using a fifo file:
First, create a fifo to be use as a pipe, in the desired directory (for instance, /samba/terraria/config):
mkfifo cmdOutput
*/samba/terraria - this is the directory I create in order to easily edit the scripts, save and load maps to the server using another computer, that are shared with samba (https://linuxize.com/post/how-to-install-and-configure-samba-on-ubuntu-18-04/)
Then I create a python script to read from the screen output and then write to a pipe file (I know, probably there is other ways to this):
import shlex, os
outputFile = os.open("/samba/terraria/config/cmdOutput", os.O_WRONLY )
print("python script has started!")
while 1:
line = input()
print(line)
cmdPosition = line.find("&")
if( cmdPosition != -1 ):
cmd = slice(cmdPosition+1,len(line))
cmdText = line[cmd]
os.write(outputFile, bytes( cmdText + "\r\r", 'utf-8'))
os.write(outputFile, bytes("say Command executed!!!\r\r", 'utf-8'))
Then I edit the terraria.service file to call this script, piped from terrariaServer, and redirect the errors to another file:
ExecStart=/usr/bin/screen -dmS terraria /bin/bash -c "/opt/terraria/TerrariaServer.bin.x86_64 -config /samba/terraria/config/serverconfig.txt < /samba/terraria/config/cmdOutput 2>/samba/terraria/config/errorLog.txt | python3 /samba/terraria/scripts/allowCommands.py"
*/samba/terraria/scripts/allowCommands.py - where my script is.
**/samba/terraria/config/errorLog.txt - save Log of errors in a file.
Now I can send commands, like 'noon' or 'dawn' so I can change the in-game time, save world and backup it with samba server before boss fights, do another stuff if I have some time XD, and have the terminal showing what is going on with the server.

How to read the send command output in expect script

I have an expect script that will login to the remote server and execute a python script. In python script I have set sys.exit(1) for a certain condition so that it would stop execution when that condition is met. Is there way to know if python script stopped execution or ran fine?
(or) is there a way to read the output that python script produces (that is I would like to read output of send "python pythonscript.py arg1\r"). Any approach is fine. Please suggest ideas
expectscript.exp
#!/usr/bin/expect
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no user#******
expect "Password:"
send "password\r"
expect "$username$"
set prompt ":|#|\\\$"
set timeout -1
send "cd /Users/username/Documents/folder/\r"
send "python pythonscript.py arg1\r"
expect $prompt
This is where you need to expect -re to capture the output before the prompt:
send "cd /Users/username/Documents/folder/\r"
expect $prompt
send "python pythonscript.py arg1\r"
expect -re "(.+)$prompt"
set pythonOutput $expect_out(1,string)
expect buffers output in the expect_out array, and the array key 1,string (note, no space there) contains the contents of the first capturing parentheses of the regex pattern.
Also note, the output will include the python command, and lines are ended with \r\n: so you can do something like:
set outputLines [lrange [lmap line [split $pythonOutput \n] {regsub {\r$} $line ""] 1 end]
Run as command
spawn ssh -c "python pythonscript.py arg1"
expect $prompt
Try to use spawn -c for executable commands as this will fetch the result back, sending them terminates after execution.

Can python run a "persistent shell"

I'm trying to run a sequence of shell commands in the same environment:
same exported variables, persistent history, etc.
And I want to work with each commands output before running the next command.
After looking over python subprocess.run and Pexpect.spawn neither seem to provide both features.
subprocess.run allows me to run one command and then examine the output, but not to keep the environment open for another command.
Pexpect.spawn("bash") allows me to run multiple commands in the same environment, but i can't get the output until EOF; when bash itself exits.
Ideally i would like an interface that can do both:
shell = bash.new()
shell.run("export VAR=2")
shell.run("whoami")
print(shell.exit_code, shell.stdout())
# 0, User
shell.run("echo $VAR")
print(shell.stdout())
# 2
shell.run("!!")
print(shell.stdout())
# 2
shell.run("cat file -")
shell.stdin("Foo Bar")
print(shell.stdout())
# Foo Bar
print(shell.stderr())
# cat: file: No such file or directory
shell.close()
Sounds like a case for Popen. You can specify bufsize to disable buffering, if it gets in the way.
Example from the linked page:
with Popen(["ifconfig"], stdout=PIPE) as proc:
log.write(proc.stdout.read())
There's also proc.stdin for sending more commands, and proc.stderr.

what's different between os.system(' ') and commands.getstatusoutput(' ') in python on linux?

what's different between os.system(' ') and commands.getstatusoutput(' ') in python on linux?
I would be really thankful if you could give an example.
thanks
os.system runs the external shell command and returns an integer which is 0 for success and any other value is failure. If the command being ran sends something to stdout or stderr it will only be printed but you cannot assign that to a variable.
command.getstatusoutput does the same "pretty much" thing that os.system does and returns a tuple with the status code integer being the first element and the status message being the second element.
Under the hood, command.getstatusoutput does things differently than os.system. It uses the os module. It uses os.popen in particular and redirects all stderr output to be combined with stdout output and reads the status message from stdout.
It must be said that the subprocess module is the new way of doing whatever these two commands above can do and much more.

Find an xterm window by title, enter one command and press return using python/bash

I have an xterm window with a custom title. The xterm was fired up the following way: xterm -T customTitle.
I would like to enter a command (for example ls -l) into this xterm window from a python/bash script running on another xterm or gnome terminal, and press Return (Enter) so the command executes.
Is this possible? Is there a way of finding an active xterm by window title or pid and executing a command from another terminal?
If this cant be done programmatically, is there some python libraries out there that would let me find my window by the given title, and simulate my keyboard as if someone was entering the text?
Thanks!
You can try this , run a command and redirect output to the xterm terminal-
>>> fp = open('/dev/pts/5','w')
>>> subprocess.Popen('ls -l',shell=True,stdout=fp, stderr=fp)
/dep/pts/5 is the file descriptor obtained from 'tty' command on your xterm !
Hope this helps !
Expanding slightly on Aro's answer, if you have a PID and want to find out the tty that process is using, a ps command like ps o tty= «PID» will serve. Substitute the actual PID number in place of «PID».
The o option to ps tells it that an output format list follows. The list in this case is tty=, which selects the controlling tty (terminal) for output and specifies a blank label for the column, which suppresses the title line.
Here's an example transcript (with > prompt) from my system:
> ps o tty= 8797
pts/8
> T=$(ps o tty= 8797); echo $T
pts/8
Regarding the question of responding to a prompt, the expect command may be relevant. However, I believe you would need to write an expect script that runs on the target terminal rather than on some other controlling terminal. To do so, you might include your xterm -T customTitle command in the expect script, but it probably would be easier to say xterm -T customTitle -e somescript where somescript is an expect script or a shell script that sets stuff up and then runs expect. If the command that you are trying to automate is intended to run without any user interaction, then leave out the xterm command and just run it in a background process.

Categories

Resources