Use subprocess in Python - python

I am writing a small program in Python, to record an audio WITH printing some text at same time.
But my Print is executed until finishing of audio recording.
Can you please help me to resolve this issue?
import picamera, subprocess, os, sys
a1 = "arecord -f cd -D plughw:0 -d 10 a.wav"
subprocess.call(a1,shell= True)
print("Audio record is only for 10sec")

You're using subprocess.call, which blocks:
Run the command described by args. Wait for command to complete, then return the returncode attribute.
You can use a Popen object, which doesn't block:
proc = subprocess.Popen(a1.split())
# code will proceed
# use proc.communicate later on
Or you can have two things run separately using a Thread (which then spawns a process in it's own context):
import picamera, subprocess, os, sys
import threading
def my_process():
a1 = "arecord -f cd -D plughw:0 -d 10 a.wav"
subprocess.call(a1,shell= True)
thread = threading.Thread(target=my_process)
thread.start()
print("Audio record is only for 10sec")

Related

Live output from Python subprocess that calls Python script

There's quite a bit of posts related to collecting live output from a process that was launched using Python's subprocess module. When I attempt these solutions between my two test scripts, one being a (ba)sh script and the other being a Python script, the Python script fails to have its output read live. Instead when the Python script is ran by subprocess it ends up waiting until the process has completed to flush it to PIPE. The constraints I'm bounded by is that I do want a way to retrieve live output from subprocess for the Python script.
Tested on Ubuntu 20.04 & Windows, Shell script ran on Ubuntu 20.04.
Calling code:
import shlex
import subprocess
# invoke process
process = subprocess.Popen('python test.py',shell=True,stdout=subprocess.PIPE) #Shell true/false results in "not live" output
# Poll process.stdout to show stdout live
while True:
output = process.stdout.readline() # <-- Hangs here on calling test.py, doesn't hang on test.sh
if process.poll() is not None:
break
if output:
print(output.strip())
rc = process.poll()
test.py <-- Waits until it has completed to print out entire output
import time
for x in range(10):
print(x)
time.sleep(1)
test.sh <-- Prints out live in Python script
#!/bin/bash
for i in $(seq 1 5); do
echo "iteration" $i
sleep 1
done
#stochastic13 Provided a very useful link where the -u switch and PYTHONUNBUFFERED variable being set would work. For my needs, I used PYTHONUNBUFFERED which solved my issue entirely. The Python test script actually executes another Python script to run, which I needed the output on. Despite -u helping for the first script, it wouldn't help for the second as I wouldn't have direct access to said script to add the argument. Instead I went with the environment variable, solution below:
def run_command(command):
os.environ['PYTHONUNBUFFERED'] = '1'
process = Popen(command, shell=False, stdout=PIPE, env=os.environ) # Shell doesn't quite matter for this issue
while True:
output = process.stdout.readline()
if process.poll() is not None:
break
if output:
print(output)
rc = process.poll()
return rc
Above the code passes PYTHONUNBUFFERED and sets it to the environment, any spawned process in subprocess with this environment set will inherit PYTHONUNBUFFERED.
Test Script
import subprocess
from io import TextIOWrapper, TextIOBase, StringIO
from subprocess import PIPE, Popen, call
from tempfile import TemporaryFile
from sarge import run, Capture
# process = Popen('python test2.py', shell=False)
# while True:
# if process.poll() is not None:
# break
# rc = process.poll()
subprocess.call('python test2.py')
Test Script 2
import time
import os
print(list(os.environ.keys()))
for x in range(10):
print('test2', x)
time.sleep(1)
The output is a live capture of stdout from any Python process, not just after completion.
...
b'test2 0\r\n'
b'test2 1\r\n'
b'test2 2\r\n'
b'test2 3\r\n'
...
0

kill process do not kill the subprocess and do not close a terminal window

I am working on UBUNTU and I have file main.py with a code inside:
#!/usr/bin/env python3
# coding=utf-8
import os
import time
from subprocess import Popen, PIPE, call, signal
base_path = os.path.abspath('')
path_to_file = base_path + '/test_subprocess.py'
p = Popen(['gnome-terminal', "--", path_to_file])
time.sleep(2)
os.kill(p.pid, signal.SIGKILL)
I have test_subprocess.py with code like that:
#!/usr/bin/env python3
# coding=utf-8
import time
def print_message():
while True:
print('I am working!')
time.sleep(0.5)
print_message()
I tried to kill the subprocess but after
os.kill(p.pid, signal.SIGKILL)
subprocess is still working and prints 'I am working!'
How can I finish subprocess and how to close gnome terminal?
If I selected completely wrong way. Can you show me working example?
New version of test_subprocess.py
#!/usr/bin/env python3
# coding=utf-8
import sys
from subprocess import signal
import time
def print_message():
while True:
print('I am working!')
time.sleep(0.5)
if signal.SIGKILL: # it is braking a loop when parent process terminate!
print('I am killing self!')
break
print_message()
Should I do it like above?
You could try the following:
p = Popen(['gnome-terminal', "--", path_to_file])
PIDs = p.pid
os.system("kill {0}".format(PIDs))
Popen.pid The process ID of the child process.
Note that if you set the shell argument to True, this is the process
ID of the spawned shell.
http://docs.python.org/library/subprocess.html
This will at least kill the correct process. Not sure if it will close the terminal.
Edit: to kill the process and close the terminal:
p = Popen(['gnome-terminal', '--disable-factory', '-e', path_to_file], preexec_fn=os.setpgrp)
os.killpg(p.pid, signal.SIGINT)
Credit to https://stackoverflow.com/a/34690644/15793575, whih I modified for your command:
--disable-factory is used to avoid re-using an active terminal so that we can kill newly created terminal via the subprocess handle
os.setpgrp puts gnome-terminal in its own process group so that
os.killpg() could be used to send signal to this group
Popen.pid
The process ID of the child process.
Note that if you set the shell argument to True, this is the process
ID of the spawned shell.
Try setting the shell argument of the Popen constructor to False. (p = Popen(['gnome-terminal', "--", path_to_file]) -> p = Popen(['gnome-terminal', "--", path_to_file], shell=False)). I had a similar issue not long ago - this fixed it for me.

How do I make a subprocess run for a set amount of time, then return to the loop and wait for a trigger?

This i what I have so far...
from gpiozero import MotionSensor
import subprocess
import threading
import time
pir = MotionSensor(4)
while True:
pir.wait_for_motion()
print("Start Playing Music")
subprocess.call(['mplayer', '-vo', 'null', '-ao', 'alsa', '-playlist', 'myplaylist', '-shuffle'])
The music playing part works great, but as for the timing, I've tried threading and time, but all seem to do is pause the code for a given amount of time. I want to run the subprocess for a given amount of time, then return to wait on motion. I'm still learning. Thanks for your help.
Python 2.7 - 3.x
Create your subprocess command. I have chosen Popen.
Popen doesn't block, allowing you to interact with the process while it's running, or continue with other things in your Python program. The call to Popen returns a Popen object.
You can read the difference between subprocess.Popen and subprocess.call here
You can use shlex module to split your string command - very comfortable.
After that, you can call your command in the thread. From this moment, you can manage your task called in a thread. There is a simple example, how to do it:
Example of code:
import logging
import shlex
import subprocess
import sys
import threading
logging.basicConfig(filename='log.log',
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%H:%M:%S',
level=logging.INFO)
log = logging.getLogger(__name__)
def exec_cmd(command):
try:
cmd = subprocess.Popen(shlex.split(command), # nosec
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
_thread_command(cmd)
out, err = cmd.communicate()
log.error(err) if err else log.info(out)
except subprocess.CalledProcessError as su_err:
log.error('Calledprocerr: %s', su_err)
except OSError as os_error:
log.error('Could not execute command: %s', os_error)
def _thread_command(task, timeout=5):
"""
Thread. If task is longer than <timeout> - kill.
:param task: task to execute.
"""
task_thread = threading.Thread(target=task.wait)
task_thread.start()
task_thread.join(timeout)
if task_thread.is_alive(): # do whatever you want with your task, for example, kill:
task.kill()
logging.error('Timeout! Executed time is more than: %s', timeout)
sys.exit(1)
if __name__ == '__main__':
exec_cmd('sleep 10') # put your string command here
Tested on Centos:
[kchojnowski#zabbix4-worker1 ~]$ cat log.log
11:31:48,348 root ERROR Timeout! Executed time is more than: 5

subprocess: start process in background and start another in one call

This program should echo the pid of sleep immediately:
import subprocess
subprocess.check_output("sleep 1 & echo $!", shell=True)
Running this on the shell directly, it immediately prints the pid, but running it in python, the & is ignored and it takes 1 second before echo is executed.
How can I get this to work with only one execution of check_output (or another function of subprocess)?
(This is a simplified example, in reality instead of sleep 1 I'd put my own executable)
check_output waits for the output pipes to close and sleep has them too. You can redirect to /dev/null for an immediate return.
subprocess.check_output("sleep 1 >/dev/null 2>&1 & echo $!", shell=True)
UPDATE
Its hard to tell if sleep 1 really did run in the background so I wrote a slightly larger test.
test.py - writes time to stdout for 5 seconds
import time
for i in range(5):
print(time.strftime('%H:%M:%S'), flush=True)
time.sleep(1)
print('done', flush=True)
runner.py - runs the test redirecting stdout to a file and monitors the file.
import subprocess as subp
import time
import os
# run program in background
pid = int(subp.check_output("python3 test.py >test.out 2>&1 & echo $!",
shell=True))
print("pid", pid)
# monitor output file
pos = 0
done = False
while not done:
time.sleep(.1)
if os.stat('test.out').st_size > pos:
with open('test.out', 'rb') as fp:
fp.seek(pos)
for line in fp.readlines():
print(line.strip().decode())
done = b'done' in line
pos = fp.tell()
print("test complete")
Running it, I get
td#mintyfresh ~/tmp $ python3 runner.py
pid 24353
09:32:18
09:32:19
09:32:20
09:32:21
09:32:22
done
test complete

Is there a module can be used as FindWindow API in python

On Windows there is a WinAPI: FindWindow that you can use to get window handle of a existing window and use this handle to send message to it. Is there a python module can do that too? Find a window & communicate with it?
If this module do exist, could the same mechainsm be able applied on Ubuntu too?
Thanks a lot!
You can execute your commands with a subprocess:
import subprocess
import time
process = subprocess.Popen("echo 'start' & sleep 60 & echo 'stop'", shell=True)
time.sleep(60) # Maybe you want a timer...
The you have two options of closing, use terminate or kill methods in the Popen returned object or simulate a Ctrl. + C
import subprocess
import time
process = subprocess.Popen(cmd, shell=True)
time.sleep(5)
process.terminate() # Or kill
Simulate de ctrl + c:
import subprocess
import time
import os
import signal
process = subprocess.Popen(cmd, shell=True)
time.sleep(5)
os.kill(process.pid, signal.SIGINT) # Ctrl. + C
If you want to get the output you can use:
process.communicate()[0].strip()
Which gives you a string.
If you want a console GUI, you can use a command like:
gnome-terminal -x sh yourCommand
Or the equivalent for the terminal you have installed.

Categories