python3 pexpect spawn object line reference gets skipped - python-3.x

Python version: 3.8.0
pexpect Version: 4.8.0
def runCmd( self, cmd, thisTimeout = None ):
output = ""
if not thisTimeout:
thisTimeout = self.conn.timeout
try:
print("debug: %s" % cmd)
self.conn.sendline(cmd)
print( "before: %s " % self.conn.before.decode() )
index = self.conn.expect( self.expList, timeout = thisTimeout )
output += self.conn.before.decode()
print( "after: %s " % self.conn.after.decode() )
print( "before after: %s" % self.conn.before.decode() )
except Exception as e:
#expect exception thrown
print( "Error running command %s" % cmd )
print( e )
output = "Error: %s" % str(self.conn)
print("yo man %s" % self.conn.before.decode() )
output = output.replace(cmd, "").strip()
print("this has to print %s " % output)
return output
This function executes the cmd through the pexpect interface and returns the output.
Version of Python/pxpect that worked:
Python version: 3.6.9
pexpect version: 4.2.1
After an update of the python script to run on Python 3.8.0/pexpect 4.8.0, the first command sent to pexpect sometimes returns empty output. The reason is when the variable self.conn.before.decode() gets referenced, the python code does not get executed or ineffective.
An example output from described situation:
debug: cat /etc/hostname
before:
after: ubuntu#ip-172-31-1-219:~$
this has to print
An expected behavior:
debug: cat /etc/hostname
after: ubuntu#ip-172-31-1-219:~$
before after: cat /etc/hostname
ip-172-31-1-219
yo man cat /etc/hostname
ip-172-31-1-219
this has to print ip-172-31-1-219
But this time, the line before: gets skipped.
What is going on here?!
Downgrade is not possible as async(pexpect(<=4.2.1) used async as function/variable signature) becomes a keyword.
Update:
The lines are getting executed but it's printing out after I print it as byte string.
before after: b' \r\x1b[K\x1b]0;ubuntu#ip-172-31-1-219: ~\x07'
Where the correct one is printing out
before after: b' cat /etc/hostname\r\nip-172-31-1-219
\r\n\x1b]0;ubuntu#ip-172-31-1-219: ~\x07'

The reason the before and before after lines get skipped is that they contain the carriage return character \r and the escape sequence \x1b[K.
The carriage return is used to move the cursor to the start of the line. If there are characters after it in the string to be written, they get printed from the position of the cursor onward replacing existing printed characters.
The ANSI Control sequence \x1b[K erases the line from the position of the cursor to the end of the line. This clears the already printed strings in your particular case.

Related

Python output is not redirected in order

I am trying to format output from the following Python 3.x script. Here is the function that I'm calling:
def daily_backup():
# Header for /var/log/me/rsync_backup.log
print(" ")
print("#" * 75)
print(get_dt())
print("#" * 75)
print(" ")
# Update timestamp on backup file, notify user that backup has started
Path("/var/log/me/rsync_backup_ran").touch()
notify_send("Daily backup started","%s" % get_dt(),"info")
# Every week, start over with current files
if today_name != "Sunday":
rsync_command = "rsync -avbH --stats --exclude-from=/home/me/bin/rsync_backup.ecl"
else:
rsync_command = "rsync -avH --stats --exclude-from=/home/me/bin/rsync_backup.ecl " \
"--delete --delete-excluded"
# System configs, crontabs, installed software lists
INSTALLED = "/home/me/.config/installed"
if not os.path.isdir(INSTALLED):
os.mkdir(INSTALLED)
os.system("dpkg --get-selections > %s/installed-software" % INSTALLED)
os.system("ls /home/me/.local/share/flatpak/app > %s/installed-flatpaks" % INSTALLED)
os.system("rsync -avb /etc/apt/sources.list.d %s" % INSTALLED)
os.system("rsync -avb /var/spool/cron %s" % INSTALLED)
os.system("rsync -avb /etc/libvirt/qemu %s" % INSTALLED)
# My data
for i in range(3):
cmd_format = "%s %s %s/%s/daily"
cmd_info = (rsync_command, backup_paths[i], backup_root, backup_folders[i])
os.system(cmd_format % cmd_info)
notify_send("Daily backup ended","%s" % get_dt(),"info")
daily_backup()
The script works, except for that the header I create (at the top of the function) doesn't get printed until after all of the os.system calls. Is this normal behavior? How can I correct this?
It's probably a problem with the stdout.
Try to force it following this SO previous post

Last line of STDOUT is not getting printed if there's an STDIN after it

While trying to integrate sqlmap with my automation tool, when tried to run the command and save the output into a file, the line after which user's input is required is not getting printed on the console; It is getting printed after the arguments are passed. It is required that the console output to be printed on both the places (terminal and output file). As sqlmap requires the user's input during execution cannot use subprocess.check_output()
image
Code snippet:
[try:
cmd = cmd.rstrip()
process = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
while True:
out = process.stdout.readline()\[0:\]
if out == '' and process.poll() is not None:
break
if out:
output += out
print(out.strip())
except Exception as e:
exception_message = str(e)
output += exception_message
if 'exit status 1' not in exception_message:
self.utilities.print_color("\[!\] Error executing the command: " + cmd,'red')
output += '\r\n'
print(output)
return output][1]

How to escape special characters (e.g., string.whitespace) in log messages?

I'm trying to log strings with special characters in them. I know python has a logging module but for simplicity I've defined the following log function which takes a file handle and the message to be logged:
def log(logfp, msg):
logfp.write(f'{msg}\n')
fp = open('logfile.txt', 'w')
log(fp, 'Hello World!')
log(fp, 'World:\nHello, Bob!')
fp.close()
logfile.txt
Hello World!
World:
Hello, Bob!
What I would like is:
Hello World!
World:\nHello, Bob!
So that each line of the logfile corresponds exactly to a single call to log().
I tried using string.replace(r'\', r'\\') but that did not work:
def log(logfp, msg):
msg = msg.replace(r'\\', r'\\\\')
logfp.write(f'{msg}\n')
I tried Cid's suggestion which worked for \n but not other whitespace chars:
import os
import string
def log(fp, msg):
msg = msg.replace("\n", "\\n")
fp.write(f'{msg}\n')
# Replaces \t \n \r \x0b \x0c with a backslash counterpart (not including space chars)
def log2(fp, msg):
replacement = {ch:f'\\{ch}' for ch in string.whitespace[1:]}
for ch in string.whitespace[1:]:
msg = msg.replace(ch, replacement[ch])
fp.write(f'{msg}\n')
os.chdir(r'C:\Users\mtran\Desktop') # Change working directory
logfp = open('logfile.txt', 'w')
log(logfp, 'Hello World!')
log(logfp, 'World:\nHello, Bob!')
log2(logfp, 'World:\t\n\r\x0b\x0cHello, Everyone!')
logfp.close()
logfile.txt
Hello World!
World:\nHello, Bob!
World:\ \
\
\Hello, Everyone!
in the string "\n", you can't directly replace \ by \\ because \n is one character. You see it composed with \ and n but it's interpreted as one character.
You'd rather replace \n by \\n :
def log(msg):
msg = msg.replace("\n", "\\n")
print(msg)
log('Hello World!')
log('World:\nHello, Bob!')
This outputs
Hello World!
World:\nHello, Bob!

Setting timeout when using os.system function

Firstly, I'd like to say I just begin to learn python, And I want to execute maven command inside my python script (see the partial code below)
os.system("mvn surefire:test")
But unfortunately, sometimes this command will time out, So I wanna to know how to set a timeout threshold to control this command.
That is to say, if the executing time is beyond X seconds, the program will skip the command.
What's more, can other useful solution deal with my problem? Thanks in advance!
use the subprocess module instead. By using a list and sticking with the default shell=False, we can just kill the process when the timeout hits.
p = subprocess.Popen(['mvn', 'surfire:test'])
try:
p.wait(my_timeout)
except subprocess.TimeoutExpired:
p.kill()
Also, you can use in terminal timeout:
Do like that:
import os
os.system('timeout 5s [Type Command Here]')
Also, you can use s, m, h, d for second, min, hours, day.
You can send different signal to command. If you want to learn more, see at:
https://linuxize.com/post/timeout-command-in-linux/
Simple answer
os.system not support timeout.
you can use Python 3's subprocess instead, which support timeout parameter
such as:
yourCommand = "mvn surefire:test"
timeoutSeconds = 5
subprocess.check_output(yourCommand, shell=True, timeout=timeoutSeconds)
Detailed Explanation
in further, I have encapsulate to a function getCommandOutput for you:
def getCommandOutput(consoleCommand, consoleOutputEncoding="utf-8", timeout=2):
"""get command output from terminal
Args:
consoleCommand (str): console/terminal command string
consoleOutputEncoding (str): console output encoding, default is utf-8
timeout (int): wait max timeout for run console command
Returns:
console output (str)
Raises:
"""
# print("getCommandOutput: consoleCommand=%s" % consoleCommand)
isRunCmdOk = False
consoleOutput = ""
try:
# consoleOutputByte = subprocess.check_output(consoleCommand)
consoleOutputByte = subprocess.check_output(consoleCommand, shell=True, timeout=timeout)
# commandPartList = consoleCommand.split(" ")
# print("commandPartList=%s" % commandPartList)
# consoleOutputByte = subprocess.check_output(commandPartList)
# print("type(consoleOutputByte)=%s" % type(consoleOutputByte)) # <class 'bytes'>
# print("consoleOutputByte=%s" % consoleOutputByte) # b'640x360\n'
consoleOutput = consoleOutputByte.decode(consoleOutputEncoding) # '640x360\n'
consoleOutput = consoleOutput.strip() # '640x360'
isRunCmdOk = True
except subprocess.CalledProcessError as callProcessErr:
cmdErrStr = str(callProcessErr)
print("Error %s for run command %s" % (cmdErrStr, consoleCommand))
# print("isRunCmdOk=%s, consoleOutput=%s" % (isRunCmdOk, consoleOutput))
return isRunCmdOk, consoleOutput
demo :
isRunOk, cmdOutputStr = getCommandOutput("mvn surefire:test", timeout=5)

problems with optparse and python 3.4

Since upgrading to Python 3.4.3 optparse doesn't appear to recognise command line options. As a simple test i run this (from the optparse examples)
# test_optparse.py
def main():
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
help="write report to FILE", metavar="FILE")
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
(options, args) = parser.parse_args()
print(options)
if __name__ == '__main__':
main()
When I run test_optparse.py -f test I get
{'verbose': True, 'filename': None}
But running within my IDE i get
{'filename': 'test', 'verbose': True}
I first noted this in a script where I concatenated a run command, for example;
run_cmd = 'python.exe ' + '<path to script> + ' -q ' + '<query_name>'
res = os.system(run_cmd)
But when I displayed the run_cmd string it displayed in the interpreter over 2 lines
print(run_cmd)
'python.exe <path to script> -q '
' <query_name>'
So it may be that the passing of the command line is being fragmented by something and only the first section is being passed (hence no query name) and so the run python script fails with 'no query specified'.
I've changed all this to use subprocess.call to get around this, but it useful to have the run_query script for command line use as was. Any ideas or suggestions?

Resources