I'm not a Perl user, but from this question deduced that it's exceedingly easy to retrieve the standard output of a program executed through a Perl script using something akin to:
$version = `java -version`;
How would I go about getting the same end result in Python? Does the above line retrieve standard error (equivalent to C++ std::cerr) and standard log (std::clog) output as well? If not, how can I retrieve those output streams as well?
Thanks,
Geoff
For python 2.5: sadly, no. You need to use subprocess:
import subprocess
proc = subprocess.Popen(['java', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
Docs are at http://docs.python.org/library/subprocess.html
In Python 2.7+
from subprocess import check_output as qx
output = qx(['java', '-version'])
The answer to Capturing system command output as a string question has implementation for Python < 2.7.
As others have mentioned you want to use the Python subprocess module for this.
If you really want something that's more succinct you can create a function like:
#!/usr/bin/env python
import subprocess, shlex
def captcmd(cmd):
proc = subprocess.Popen(shlex.split(cmd), \
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
out, err = proc.communicate()
ret = proc.returncode
return (ret, out, err)
... then you can call that as:
ok, o, e = captcmd('ls -al /foo /bar ...')
print o
if not ok:
print >> sys.stderr, "There was an error (%d):\n" % ok
print >> sys.stderr, e
... or whatever.
Note: I'm using shlex.split() as a vastly safer alternative to shell=True
Naturally you could write this to suit your own tastes. Of course for every call you have to either provide three names into which it can unpack the result tuple or you'd have to pull the desired output from the result using normal indexing (captcmd(...)[1] for the output, for example). Naturally you could write a variation of this function to combine stdout and stderr and to discard the result code. Those "features" would make it more like the Perl backtick expressions. (Do that and take out the shlex.split() call and you have something that's as crude and unsafe as what Perl does, in fact).
Related
I have an issue with Rsyslog's 'omprog' module when trying to get it to interact with my python (2.7) code. Rsyslog is supposed to send desired messages to python's stdin, yet it does not receive anything. I wonder if anyone else has had better success with this output module?
Rsyslog.conf
module(load="omprog")
template(name="sshmsg" type="string" string="%msg%")
if ($programname == "myprogram") then {
action(type="omprog"
binary="/usr/sshtrack.py"
template="sshmsg")
}
If I replace the binary with a test shell script containing a line below, it works
test.sh
!#/bin/sh
cat /dev/stdin >> /var/log/ssh2.log
I also tried reading stdin in the shell script into a variable using
var="$(</dev/stdin)"
and
var="$(cat /dev/stdin)"
Neither of the above resulted var containing anything
Finally, when trying to read stdin from python script, I get nothing. Sometimes, it says resource unavailable (errno 11) error message.
sshtrack.py
#!/usr/bin/python
import sys
f = open("/var/log/ssh2.log", "a", 0)
while True:
f.write("Starting\n")
for line in sys.stdin:
f.flush()
msg = line.strip()
if not msg:
break
f.write(msg)
f.write("\n")
f.close()
The issue seems similar to can not read correctly from STDIN except adding a non-block flag did nothing.
I notice that your template sshmsg doesn't end with a newline. Try changing it to string="%msg%\n". Though it won't matter to rsyslog, Python will not be able to give you the data until it sees a newline.
Then it should work, but you probably not see any output from your python as it is buffered. Try adding an f.flush() after the last write in the loop, or opening the file unbuffered.
omprog will keep the pipe open, sending multiple lines until your program exits.
Note, not all shells might understand $() syntax.
In case of your shell script you can use read to read into a variable.
#!/bin/bash
# This will read until \n
read log
echo $log
The python source code (tested with python 3.8.2) can be adjusted to:
#!/usr/bin/env python3
import sys
# Changed from unbuffered to buffered as unbuffered is only possible in binary mode Ref (1):
f = open("/var/log/ssh2.log", "a", 1)
while True:
f.write("Starting\n")
for line in sys.stdin:
f.flush()
msg = line.strip()
if not msg:
break
f.write(msg)
f.write("\n")
f.close()
In case you want to have the output of you executed script (debugging) you can adjust the settings in Rsyslog.conf with the output option
module(load="omprog")
template(name="sshmsg" type="string" string="%msg%")
if ($programname == "myprogram") then {
action(type="omprog"
binary="/usr/sshtrack.py"
output="/var/log/sshtrack.log"
template="sshmsg")
}
Ref (1): https://stackoverflow.com/a/45263101/13108341
I've been trying to make a script that can print the Ubuntu SSH key located in ~/.ssh/authorised_keys/
Basically I want the script to print out exactly what cat ~/.ssh/authorised_keys/ would output.
I have tried using subprocess.check_output but it always returns an error.
Thanks
What about this ?
import os
os.system('cat ~/.ssh/authorised_keys')
If you want to capture the output to a variable, use subprocess. If not, you can use os.system as user803422 has mentioned
import os, subprocess
path = '~/.ssh/authorized_keys'
cmd = 'cat ' + os.path.expanduser(path)
output = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
response = output.communicate()
print (response)
You can read the file directly in Python, there is not really a need to use subprocess:
import os
print(open(os.path.expanduser('~/.ssh/authorized_keys')).read())
I'm attempting to use the re module to look through some terminal output. When I ping a server through terminal using ping -n 1 host (I'm using Windows), it gives me much more information than I want. I want just the amount of time that it takes to get a reply from the server, which in this case is always denoted by an integer and then the letters 'ms'. The error I get explains that the output from the terminal is not a string, so I cannot use regular expressions on it.
from os import system as system_call
import re
def ping(host):
return system_call("ping -n 1 " + host) == 0
host = input("Select a host to ping: ")
regex = re.compile(r"\w\wms")
final_ping = regex.search(ping(host))
print(final_ping)
system returns 0, not anything too useful. However, if we were to do subprocess, we can get teh output, and store it to a variable, out, then we can regex search that.
import subprocess
import re
def ping(host):
ping = subprocess.Popen(["ping", "-n", "1", host], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, error = ping.communicate()
return str(out)
host = input("Select a host to ping: ")
final_ping = re.findall("\d+ms",ping(host))[0]
print(final_ping)
Output:
22ms
There are two problems with your code:
Your ping function doesn't return the terminal output. It only returns a bool that reports if the ping succeeded. The ping output is directly forwarded to the terminal that runs the Python script.
Python 3 differentiates between strings (for text, consisting of Unicode codepoints) and bytes (for any data, consisting of bytes). As Python cannot know that ping only outputs ASCII text, you will get a bytes object if you don't specify which text encoding is in use.
It would be the best to use the subprocess module instead of os.system. This is also suggested by the Python documentation.
One possible way is to use subprocess.check_output with the encoding parameter to get a string instead of bytes:
from subprocess import check_output
import sys
def ping(host):
return check_output(
"ping -n 1 " + host,
shell=True,
encoding=sys.getdefaultencoding()
)
...
EDIT: The encoding parameter is only supported since Python 3.6. If you are using an older version, try this:
from subprocess import check_output
import sys
def ping(host):
return check_output(
"ping -n 1 " + host,
shell=True
).decode()
...
Related to this question Command line arguments in python.
With the SYS module, how can I use a command line argument as a function name and function value, within my code - without importing some other module?
I'd like a solution that uses sys only. Also, please no variable-length params answers. Those are confusing. Assume that just the function name and one function variable are specified at the command line.
import sys
def reversal(aaa): return aaa[::-1]
a = sys.argv[1]
b = sys.argv[2]
print a(b)
At the command line
cpu_location$ python blah.py reversal 'abcdefg'
Traceback (most recent call last):
File "blah.py", line 8, in <module>
print a(b)
TypeError: 'str' object is not callable
I want to know how to make sys.argv[1] be considered a function name, thereby calling the function I have defined.
The other posts I see on this are a mash up of:
- dealing with C/C++ and adding some other module
- not using sys at all
- using the argv items as values for functions, and names of other files, instead of names of functions
Better than the eval solution would be:
a = globals()[sys.argv[1]]
a(b)
globals() returns a dictionary mapping global variables names to those global variables. So globals()['reversal'] evaluates to the reversal function.
It's safer than the eval function. With your approach you could do something like:
python blah.py 'lambda x: x+"hi"' foobar
Which would print foobarhi, which is unexpected because that's not a function name.
2 hours later, I find the answer. I think it's worth it to post it here in a very simple fashion.
Basiclaly there is no "function" data type in Python, but someone did mention a function eval, which is built-in. Execute python commands passed as strings in command line using python -c (No -c is needed for my own example)
The solution, is to change
a = sys.argv[1]
to
a = eval(sys.argv[1])
This will make the passed in word, reversal, be evaluated. It will evaluate to a function. Then the a(b) call will be a perfect call of a function on a string, like how it's defined. Output will be like:
cpu_location$ python blah.py reversal unquoted
detouqnu
cpu_location$ python blah.py reversal 'withquotes'
setouqhtiw
use google module: fire
pip install fire
Here's a simple example:
import fire
class Calculator(object):
"""A simple calculator class."""
def double(self, number):
return 2 * number
if __name__ == '__main__':
fire.Fire(Calculator)
Then, from the command line, you can run:
python calculator.py double 10 # 20
python calculator.py double --number=15 # 30
from subprocess import call
import sys
import os
import subprocess
if(call("hg clone --insecure https://mixmaster.netwitness.local/" + "sys.argv[1]", shell=True)):
sys.stdin = sys.argv[2]
sys.stdin = sys.argv[3]
else :
print("error")
Is there a reason why the argument is in quotes? Also use % to replace tokens in your string. Change it to this:
if(call("hg clone --insecure https://mixmaster.netwitness.local/%s" % sys.argv[1], shell=True)):
EDIT
If you want to pass all the arguments separated by spaces, use this
if(call("hg clone --insecure https://mixmaster.netwitness.local/%s" % (" ".join(sys.argv[1:])), shell=True)):
subprocess.call is more easily called with a list of parameters. That way you don't have to worry about spaces in the arguments that you want to give to hg. As you indicate in your comments on #Rajesh answer, that you want 3 arguments passed to hg, the following should work:
from subprocess import call
import sys
import os
import subprocess
cmd = ["hg", "clone", "--insecure", "https://mixmaster.netwitness.local/", sys.argv[1], sys.argv[2], sys.argv[3]]
if not (call(cmd, shell=True)):
print("error")
If you really want to provide sys.argv[2] and sys.argv[3] as the input to hg prompts. You should not use call as it can block the hg process, use Popen.