Dari dalam Python, sering kali kita perlu menjalankan program lain: git untuk membaca status repositori, ffmpeg untuk mengonversi video, atau skrip Python kedua sebagai tool terpisah. Module subprocess dari standard library adalah jembatan resmi antara proses Python kita dan proses eksternal. Pada Chapter ini kita menjalankan perintah, menangkap outputnya, menangani kegagalan dan timeout, sampai menghubungkan dua proses dengan pipe.
subprocess.run dan CompletedProcess #
Fungsi utama module ini adalah subprocess.run(): jalankan satu perintah, tunggu sampai selesai, lalu kembalikan object CompletedProcess. Berkas run_dasar.py:
import subprocess
hasil = subprocess.run(["python3", "-V"], capture_output=True, text=True)
print("returncode:", hasil.returncode)
print("stdout :", hasil.stdout.strip())
print("stderr :", repr(hasil.stderr))Output yang Dihasilkan:
returncode: 0
stdout : Python 3.12.15
stderr : ''Mari kita bedah baris-baris pentingnya:
- Baris 3 (
["python3", "-V"]): perintah ditulis sebagai list — nama program diikuti setiap argumennya sebagai elemen terpisah, tanpa shell yang ikut menafsirkan. - Baris 3 (
capture_output=True): output stdout dan stderr anak ditampung ke memory sehingga bisa dibaca lewathasil.stdoutdanhasil.stderr; tanpa parameter ini, output langsung mengalir ke terminal. - Baris 3 (
text=True): hasil dibuka sebagai string, bukan bytes; tanpa parameter inihasil.stdoutberupa bytes yang harus didekode lebih dulu.
Contoh cepat lainnya dengan echo:
import subprocess
hasil = subprocess.run(["echo", "Halo dari subprocess"], capture_output=True, text=True)
print(hasil.stdout, end="")Output yang Dihasilkan:
Halo dari subprocessObject CompletedProcess menyimpan empat atribut: args (perintah yang dijalankan), returncode (kode keluar anak), stdout, dan stderr.
check=True dan CalledProcessError #
Secara default subprocess.run() tidak peduli apakah perintah gagal; yang berubah hanya returncode. Parameter check=True mengubah perilaku itu: bila exit code bukan nol, Python memicu exception CalledProcessError. Berkas run_check.py:
import subprocess
perintah = ["python3", "-c", "import sys; sys.exit(3)"]
hasil = subprocess.run(perintah, capture_output=True, text=True)
print("tanpa check, returncode:", hasil.returncode)
try:
subprocess.run(perintah, check=True, capture_output=True)
except subprocess.CalledProcessError as galat:
print("CalledProcessError, returncode:", galat.returncode)Output yang Dihasilkan:
tanpa check, returncode: 3
CalledProcessError, returncode: 3Exception CalledProcessError membawa galat.cmd, galat.returncode, galat.output, dan galat.stderr sehingga penangan galat punya semua konteks. Gunakan check=True setiap kali kegagalan anak berarti kegagalan program kita — kebiasaan yang membuat alur kontrol jujur.
shell=False Selalu #
Parameter shell=True menjalankan perintah melalui shell sistem, yang berarti karakter seperti ;, |, dan $(...) ditafsirkan sebagai sintaks shell. Gabungkan ini dengan input dari pengguna dan kita mendapatkan celah command injection. Simulasi berikut menunjukkan mengapa mode aman (shell=False, yang merupakan default) benar-benar aman:
import subprocess
nama_berkas = "laporan.txt; rm -rf /" # input berbahaya dari pengguna
# shell=False: string tetap satu argumen, tidak ditafsirkan shell
hasil = subprocess.run(["ls", "-l", nama_berkas], capture_output=True, text=True)
print("returncode:", hasil.returncode)
print("perintah tambahan tidak pernah dijalankan")Output yang Dihasilkan:
returncode: 2
perintah tambahan tidak pernah dijalankanDengan shell=False, string "laporan.txt; rm -rf /" utuh diperlakukan sebagai satu nama berkas (yang memang tidak ada, sehingga ls keluar dengan kode 2). Bagian rm -rf / tidak pernah dieksekusi karena tidak ada shell yang memecah kalimat tersebut.
Membatasi Waktu dengan timeout #
Proses eksternal bisa menggantung: server tidak menjawab, atau berkas korup membuat tool berputar selamanya. Parameter timeout= (satuan detik) membunuh anak yang melewati batas waktu dan memicu exception TimeoutExpired. Berkas run_timeout.py:
import subprocess
try:
subprocess.run(["sleep", "5"], timeout=0.3, capture_output=True)
except subprocess.TimeoutExpired as galat:
print("TimeoutExpired setelah", galat.timeout, "detik")Output yang Dihasilkan:
TimeoutExpired setelah 0.3 detikException ini membawa galat.cmd dan galat.timeout sehingga log kesalahan menyebut perintah mana yang menggantung dan berapa lama batasnya.
Popen dan Pipe antar Proses #
subprocess.run() menutup semua kebutuhan eksekusi tunggal. Class Popen adalah mesin di baliknya untuk kasus lanjutan: streaming output sambil anak masih berjalan, atau menghubungkan dua proses. Contoh berikut mempipe output proses_satu menjadi input proses_dua, persis seperti program1 | program2 di shell:
from subprocess import PIPE, Popen
proses_satu = Popen(
["python3", "-c", "print('jeruk')\nprint('apel')"],
stdout=PIPE,
text=True,
)
proses_dua = Popen(
["python3", "-c", "import sys; print('total baris:', sys.stdin.read().count(chr(10)))"],
stdin=proses_satu.stdout,
stdout=PIPE,
text=True,
)
proses_satu.stdout.close()
keluaran, _ = proses_dua.communicate()
print("hasil pipe:", keluaran.strip())Output yang Dihasilkan:
hasil pipe: total baris: 2Baris stdin=proses_satu.stdout menyambungkan stdout anak pertama ke stdin anak kedua. Baris proses_satu.stdout.close() memastikan anak kedua menerima tanda akhir input (EOF) setelah anak pertama selesai, dan communicate() membaca output sambil menunggu proses berakhir.
Panduan memilih cepat:
| Kebutuhan | Alat |
|---|---|
| jalankan satu perintah, tunggu hasil | subprocess.run() |
| baca output baris demi baris sambil bekerja | Popen + iterasi stdout |
| sambungkan dua program | Popen + stdout=PIPE |
Dengan bekal run(), check=True, timeout, dan pipe, kita siap masuk ke resep praktis menjalankan program eksternal di dunia nyata pada Chapter Menjalankan Program Eksternal.