Een shell opdracht uitvoeren met Python
Op deze pagina:
Je kunt externe opdrachten uitvoeren via de subprocess module. Hiermee kun je andere programma's starten vanuit een Python script.
Gebruikswijze:
import subprocess
subprocess.call("opdracht1")
subprocess.call(["opdracht1", "arg1", "arg2"])
Bijvoorbeeld de date opdracht:
import subprocess
subprocess.call("date")
Mogelijk resultaat:
vr 24 jan 2025 20:20:20 CET
Een voorbeeld met argumenten:
import subprocess
subprocess.call(["ls", "-l", "/etc/hosts"])
Mogelijk resultaat:
-rw-r--r-- 1 root root 356 jun 4 2023 /etc/hosts
De uitvoer van een opdracht ook in een variabele opslaan
import subprocess
p = subprocess.Popen("date", stdout=subprocess.PIPE, shell=True)
(uitvoer, err) = p.communicate()
print ("Vandaag is het", uitvoer.decode('utf-8'))
Mogelijk resultaat:
Vandaag is het vr 24 jan 2025 19:16:06 CET
Een voorbeeld met de ping opdracht:
import subprocess
cmdping = "ping -c4 www.web2.nl"
p = subprocess.Popen(cmdping, shell=True, stderr=subprocess.PIPE)
while True:
out = p.stderr.read(1).decode('utf-8')
if out == '' and p.poll() != None:
break
if out != '':
print(out)
Mogelijk resultaat:
PING www.web2.nl (37.16.0.124) 56(84) bytes of data.
64 bytes from 37.16.0.124: icmp_seq=1 ttl=56 time=21.0 ms
64 bytes from 37.16.0.124: icmp_seq=2 ttl=56 time=20.8 ms
64 bytes from 37.16.0.124: icmp_seq=3 ttl=56 time=29.9 ms
64 bytes from 37.16.0.124: icmp_seq=4 ttl=56 time=35.6 ms
--- www.web2.nl ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 20.824/26.860/35.659/6.264 ms
Inhoud van een directory opvragen:
import subprocess
p = subprocess.Popen("ls -l ../test/kees/", stdout=subprocess.PIPE, shell=True)
(uitvoer, err) = p.communicate()
print ("Directory inhoud:\n", uitvoer.decode('utf-8'))
Mogelijke uitvoer:
-->$ ls -l ../test/kees/
totaal 16
-rw-rw-r-- 1 kees kees 2654 sep 22 2023 89abc.txt
-rw-rw-r-- 1 kees kees 2656 okt 3 2023 divve.txt
-rw-rw-r-- 1 kees kees 2566 okt 3 2023 gsv.txt
-rw-rw-r-- 1 kees kees 34 jun 13 2024 'tijdelijk Tekstbestand.txt'
Een pipe gebruiken
import subprocess
p = subprocess.Popen("ls -l ../test/kees/ | grep gsv", stdout=subprocess.PIPE, shell=True)
(uitvoer, err) = p.communicate()
print ("Directory inhoud:\n", uitvoer.decode('utf-8'))
Mogelijke uitvoer:
-->$ ./testje.py
Directory inhoud:
-rw-rw-r-- 1 kees kees 2566 okt 3 2023 gsv.txt
In plaats van Popen wordt het vaak aangeraden om run te gebruiken. De subprocess.run() method is een makkelijke manier om een subprocess te draaien en te wachten op de afloop. Zodra het subprocess is gestart, blokkeert de run() method de verdere uitvoering van het Python script totdat het subprocess klaar is en geeft dan een CompletedProcess object terug. Het CompletedProcess object bevat de resultaat code (returncode) en de uitvoer van het subprocess.
import subprocess
p1 = subprocess.run(["ls","../test/kees"])
print(p1)
print(p1.args)
print(p1.returncode)
print(p1.stdout)
Mogelijke uitvoer:
89abc.txt divve.txt gsv.txt 'tijdelijk Tekstbestand.txt'
CompletedProcess(args=['ls', '../test/kees'], returncode=0)
['ls', '../test/kees']
0
None
In het bovenstaande voorbeeld wordt de opdracht gesplitst geplaatst tussen vierkante haken : ["ls","../test/kees"]. Met shell=True kun je alles aan elkaar houden, de opdracht wordt dan in een nieuwe shell uitgevoerd en kan ook wildcards bevatten zoals '*':
import subprocess
process_1 = subprocess.run("ls ../test/kees/*", shell=True)
print(process_1)
print(process_1.args)
print(process_1.returncode)
print(process_1.stdout)
Mogelijke uitvoer:
89abc.txt divve.txt gsv.txt 'tijdelijk Tekstbestand.txt'
CompletedProcess(args='ls ../test/kees', returncode=0)
ls ../test/kees/*
0
None
De uitvoer naar een bestand sturen
import subprocess
with open('inhoud.txt','w') as bestand:
subprocess.run("ls ../test/kees/*", shell=True,stdout=bestand,text=True)
Mogelijke inhoud van het tekstbestand:
../test/kees/89abc.txt
../test/kees/divve.txt
../test/kees/gsv.txt
../test/kees/tijdelijk Tekstbestand.txt
Fouten afvangen
import subprocess
p2 = subprocess.run(["ls","niet-bestaande-directory"],capture_output=True)
print(p2.returncode)
print(p2.stderr)
Mogelijke uitvoer:
2
b"ls: kan geen toegang krijgen tot 'niet-bestaande-directory': Bestand of map bestaat niet\n"
De shell geeft nu een fout, maar Python niet, als je dat wel wilt, dan kun je check=True gebruiken:
import subprocess
p2 = subprocess.run(["ls","niet-bestaande-directory"],capture_output=True,check=True)
print(p2.returncode)
print(p2.stderr)
Mogelijke uitvoer:
Traceback (most recent call last):
File "/home/kees/Dev/./testje.py", line 4, in <module>
p2 = subprocess.run(["ls","niet-bestaande-directory"],capture_output=True,check=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/subprocess.py", line 571, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['ls', 'niet-bestaande-directory']' returned non-zero exit status 2.
Een time-out geven als het te lang duurt
Een Python script gaat pas verder als het subprocess tot een einde is gekomen. Het kan handig zijn om een fout te geven als het subprocess er te lang over doet. Je kunt een time-out waarde in seconden opgeven om een foutmelding te krijgen, het subprocess blijft op de achtergrond wel draaien.
import subprocess
try:
p3 = subprocess.run(["sleep","7"],capture_output=True, check=True, timeout=3)
except subprocess.TimeoutExpired:
print("subprocess time-out")
Mogelijke uitvoer:
subprocess time-out
Het subprocess ook stoppen bij een time-out kan met Popen en communicate:
import subprocess
p3 = subprocess.Popen(["sleep","7"])
try:
std_out,std_err = p3.communicate(timeout=3)
except subprocess.TimeoutExpired:
p3.kill()
std_out,std_err = p3.communicate()
print("subprocess time-out")
Mogelijke uitvoer:
subprocess time-out
De uitvoer van een subprocess gebruiken als invoer van een ander subprocess
import subprocess
p1 = subprocess.run(["cat","/etc/hosts"],capture_output=True, text=True)
p2 = subprocess.run(["grep","-n","localhost"],capture_output=True,text=True,input=p1.stdout)
print(p2.stdout)
Mogelijke uitvoer:
1:127.0.0.1 localhost
5:::1 ip6-localhost ip6-loopback
Het Environment aanpassen voor het subprocess
Het subprocess erft het environment van het ouder process. Je kunt een subprocess een eigen environment geven. Het eenvoudigste is om het bestaande environment te kopiëren en dan de dingen aanpassen die anders moeten zijn.
Voorbeeld:
import os
import subprocess
nieuw_env = os.environ.copy()
nieuw_env["PATH"] = os.pathsep.join(["/test/",nieuw_env["PATH"]])
p4 = subprocess.run(["ls"],env=nieuw_env)
In het bovenstaande voorbeeld wordt een extra directory "/test/" toegevoegd aan PATH.