Skrip Python kamu perlu meresize ribuan foto, memanggil git, atau menjalankan ffmpeg. Kamu bisa buka terminal dan ketik satu per satu, atau biarkan Python yang mengerjakannya. Modul subprocess ada di pustaka standar untuk urusan ini: memanggil program eksternal, membaca hasilnya, dan mengontrol cara proses berjalan. Hampir semua skrip otomasi yang saya tulis akhirnya menyentuh subprocess, entah untuk backup, konversi file, atau cek status server.
Kenapa subprocess, bukan os.system()
Kode lama sering pakai os.system("ls -l"). Itu masih jalan, tapi cuma mengembalikan status berupa angka dan tidak bisa menangkap output ke dalam variabel. Mau parsing hasilnya? Redirect ke file dulu, baca lagi, lalu hapus. subprocess.run() jauh lebih kaya: dia mengembalikan objek CompletedProcess yang membawa returncode, stdout, dan stderr sekaligus, plus opsi timeout dan kontrol environment. Dokumentasi resmi Python sendiri mengarahkan kamu ke subprocess ketimbang os.system().
Menjalankan perintah sederhana
Bentuk paling dasar memakai daftar argumen, bukan satu string panjang. Ini penting: subprocess menjalankan perintah langsung tanpa shell kalau kamu kasih list, jadi tidak ada celah injeksi dan tidak ada masalah quoting.
import subprocess
subprocess.run(["ls", "-l"])
Perhatikan ls dan -l menjadi dua elemen terpisah. Kalau argumenmu mengandung spasi — misalnya nama file "laporan bulanan.pdf" — masukkan saja sebagai satu elemen list, dan subprocess yang mengurus escaping-nya. Tidak perlu pusing dengan tanda kutip berlapis.
Menangkap output
Mau pakai hasil perintahnya di dalam kode? Tambah capture_output=True supaya stdout dan stderr masuk ke variabel, lalu text=True supaya hasilnya berupa string, bukan bytes.
import subprocess
hasil = subprocess.run(
["git", "status", "--short"],
capture_output=True,
text=True,
)
print("STDOUT:", hasil.stdout)
print("STDERR:", hasil.stderr)
print("Kode keluar:", hasil.returncode)
Tanpa text=True, stdout dan stderr berupa bytes. Kamu bisa decode manual lewat hasil.stdout.decode(), tapi text=True lebih rapi karena otomatis memakai encoding default sistem. Kalau butuh encoding spesifik, lewati text=True dan decode sendiri dengan hasil.stdout.decode('utf-8').
Menangani exit code dan error
Perintah yang gagal tidak otomatis melempar exception, kecuali kamu set check=True. Tanpa check, returncode yang bukan nol cuma terdiam, dan skripmu bisa lanjut dengan data yang salah. Ini sumber bug yang paling sering saya temukan di kode orang lain.
import subprocess
try:
subprocess.run(["git", "push", "origin", "main"], check=True)
except subprocess.CalledProcessError as e:
print(f"Push gagal, exit code {e.returncode}")
print(e.stderr)
check=True membuat run() melempar CalledProcessError ketika returncode bukan nol, jadi skripmu berhenti di tempat yang benar. Kalau kamu belum nyaman dengan struktur try/except, saya sudah membahasnya tuntas di artikel Menguasai Exception Handling di Python.
Melewatkan input ke perintah
Sebagian program membaca input dari stdin — grep, sort, atau skrip interaktif. Parameter input= memungkinkan kamu mengirim string langsung tanpa perlu pipe di shell.
import subprocess
hasil = subprocess.run(
["grep", "-i", "error"],
input="INFO server jalan\nERROR koneksi gagal\nDEBUG request masuk\n",
capture_output=True,
text=True,
)
print(hasil.stdout) # ERROR koneksi gagal
Pipeline dan kapan shell=True diperlukan
Kadang kamu butuh fitur shell seperti pipe (|), wildcard (*), atau variabel $HOME. Itu gunanya shell=True. Tapi fitur ini membuka celah keamanan kalau ada bagian input yang datang dari pengguna.
import subprocess
# BERBAHAYA: jangan pakai input pengguna di sini
subprocess.run("ls *.txt | wc -l", shell=True)
Jangan pernah memakai shell=True dengan input yang bisa dikontrol pengguna. Seorang penyerang bisa menyisipkan perintah tambahan lewat titik koma — misalnya nama file "foto.jpg; rm -rf /" — dan server kamu bisa dibersihkan. Untuk input dinamis, selalu pakai daftar argumen.
Kalau kebutuhanmu cuma wildcard, perluas dulu polanya dengan modul glob, lalu teruskan hasilnya sebagai list. Tidak perlu shell sama sekali.
import glob
import subprocess
# Wildcard tanpa shell: perluas dulu pakai glob
subprocess.run(["wc", "-l", *glob.glob("*.log")])
Sebagian besar skrip yang saya lihat memakai shell=True sebenarnya tidak butuh. Kalau kamu lebih nyaman menulis perintah di shell daripada di Python, dasar-dasar yang sama juga saya bahas di Menguasai Bash Scripting di Linux.
Menjalankan proses lama dengan Popen
run() memblokir sampai perintah selesai. Untuk proses panjang yang mengeluarkan output bertahap — ping, build, tail -f — kamu butuh Popen supaya bisa membaca output baris per baris selagi proses masih berjalan.
import subprocess
proses = subprocess.Popen(
["ping", "-c", "4", "8.8.8.8"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
for baris in proses.stdout:
print(baris, end="")
proses.wait()
print(f"Exit code: {proses.returncode}")
Popen juga cocok untuk streaming log server. stdout-nya kamu baca seperti file biasa, jadi bisa difilter atau ditulis ke berkas sambil proses jalan. Jangan lupa proses.wait() di akhir untuk memastikan proses benar-benar selesai sebelum skrip berhenti.
Contoh nyata: resize batch gambar
Sekarang rakit jadi satu skrip yang berguna. Skrip ini meresize semua file JPG di folder gambar_asli menjadi thumbnail 300x300 pakai ImageMagick, lalu menyimpan hasilnya di folder gambar_thumbnail. Di ImageMagick versi 7, ganti convert menjadi magick.
import subprocess
from pathlib import Path
SOURCE = Path("gambar_asli")
TARGET = Path("gambar_thumbnail")
TARGET.mkdir(exist_ok=True)
for foto in SOURCE.glob("*.jpg"):
tujuan = TARGET / f"{foto.stem}_thumb.jpg"
cmd = [
"convert", str(foto),
"-resize", "300x300",
"-quality", "85",
str(tujuan),
]
hasil = subprocess.run(cmd, capture_output=True, text=True)
if hasil.returncode == 0:
print(f"OK {foto.name}")
else:
print(f"GAGAL {foto.name}: {hasil.stderr.strip()}")
Belum punya ImageMagick? Install dulu dengan sudo apt install imagemagick di Debian/Ubuntu atau brew install imagemagick di macOS. Skrip ini jalan apa adanya selama perintah convert tersedia di PATH, dan setiap kegagalan dilaporkan per file tanpa menghentikan seluruh proses.
Untuk ribuan file, jalankan loop ini dengan ThreadPoolExecutor dari concurrent.futures supaya beberapa konversi berjalan paralel. Resize gambar relatif ringan di CPU, jadi 4-8 worker biasanya bikin prosesnya jauh lebih cepat.
Langkah berikutnya yang masuk akal: catat output tiap perintah ke file log supaya kamu tahu persis apa yang terjadi saat skrip jalan tanpa pengawasan, misalnya lewat cron. Modul logging standar Python cukup untuk ini, dan saya sudah menulis panduannya di Belajar Python Logging.