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.

 

Verwante artikelen