Dílčí procesy umožňují interakci s operačním systémem na zcela nové úrovni.
Náš počítač neustále aktivuje množství menších procesů. Ve skutečnosti, i pouhým čtením tohoto textu spouštíte celou řadu procesů, například správu síťového připojení nebo samotný internetový prohlížeč.
Fascinující na tom je, že jakákoli aktivita, kterou na počítači provádíme, zahrnuje inicializaci dílčího procesu. Platí to i v případě, že píšeme jednoduchý skript „ahoj světe“ v Pythonu.
Pojem dílčího procesu se může zdát matoucí, i když už se programování nějakou dobu věnujete. Tento článek se hlouběji zaměří na klíčové koncepty dílčích procesů a na to, jak využívat standardní knihovnu `subprocess` v Pythonu.
Po přečtení tohoto tutoriálu:
- Pochopíte princip dílčího procesu
- Seznámíte se se základy knihovny `subprocess` v Pythonu
- Zlepšíte své programátorské dovednosti na praktických příkladech
Pojďme se do toho pustit.
Koncept dílčího procesu
Obecně řečeno, dílčí proces je výpočetní proces, který je vytvářen jiným, nadřazeným procesem.
Dílčí proces si můžeme představit jako strom, kde z každého nadřazeného procesu vyrůstají podřízené procesy. Možná to zní trochu komplikovaně, ale ukážeme si to na jednoduchém grafickém příkladu.
Existuje několik způsobů, jak můžeme vizualizovat procesy běžící na našem počítači. Například v systémech UNIX (Linux a macOS) máme nástroj htop, což je interaktivní prohlížeč procesů.
Stromové zobrazení je nejvhodnější pro sledování spuštěných dílčích procesů. Aktivujeme jej stisknutím klávesy F5.
Když se podíváme pozorně do sekce příkazů, můžeme si povšimnout struktury procesů, které běží na našem počítači.
Vše začíná procesem /sbin/init, který spouští všechny ostatní procesy v našem počítači. Odtud se větví další procesy, jako například xfce4-screenshoter a xfce4-terminál (který následně spouští další dílčí procesy).
V systému Windows máme Správce úloh, který je užitečný pro ukončování nereagujících aplikací.
Nyní máme o celém konceptu jasnou představu. Podívejme se, jak můžeme implementovat dílčí procesy v Pythonu.
Dílčí procesy v Pythonu
Dílčí proces v Pythonu je úkol, který pythonovský skript předává operačnímu systému (OS) k provedení.
Knihovna `subprocess` nám umožňuje spouštět a řídit dílčí procesy přímo z Pythonu. Zahrnuje to i práci se standardním vstupem (stdin), standardním výstupem (stdout) a návratovými kódy.
Není nutné ji instalovat pomocí PIP, protože je součástí standardní knihovny Pythonu.
Díky tomu můžeme začít pracovat s dílčími procesy v Pythonu prostým importováním modulu.
import subprocess # Použití modulu ....
Poznámka: Pro pokračování v tomto článku je nutné mít Python 3.5 a vyšší.
Aktuální verzi Pythonu zjistíte spuštěním tohoto příkazu:
❯ python --version Python 3.9.5 # Můj výsledek
V případě, že získáte verzi Pythonu 2.x, použijte tento příkaz:
python3 --version
Vraťme se k tématu, hlavním záměrem knihovny `subprocess` je umožnit interakci s operačním systémem spouštěním libovolných příkazů přímo z interpretru Pythonu.
To znamená, že můžeme provádět cokoliv, co nám OS dovolí (a pokud si omylem nesmažete kořenový souborový systém 😅).
Podívejme se, jak ji můžeme použít, vytvořením jednoduchého skriptu, který zobrazí soubory v aktuálním adresáři.
První aplikace s dílčími procesy
Nejprve vytvořte soubor list_dir.py. V něm budeme experimentovat se seznamem souborů.
touch list_dir.py
Nyní otevřete tento soubor a vložte následující kód:
import subprocess subprocess.run('ls')
Nejprve importujeme modul `subprocess` a následně použijeme funkci `run`, která spustí předaný příkaz jako argument.
Tato funkce byla představena v Pythonu 3.5 jako přímější alternativa k funkci `subprocess.Popen`. Funkce `subprocess.run` nám umožňuje spustit příkaz a čekat na jeho dokončení, na rozdíl od `Popen`, kde máme možnost zavolat komunikaci později.
Co se týče výstupu kódu, příkaz `ls` je příkaz v systémech UNIX, který zobrazuje soubory v aktuálním adresáři. Takže pokud tento příkaz spustíte, získáte seznam souborů v aktuálním adresáři.
❯ python list_dir.py example.py LICENSE list_dir.py README.md
Poznámka: Mějte na paměti, že pokud jste v systému Windows, budete muset používat jiné příkazy. Například místo příkazu „ls“ můžete použít „dir“.
Může se to zdát příliš jednoduché a máte pravdu. Chcete se přiblížit veškeré síle, kterou vám nabízí shell. Podívejme se, jak můžeme předávat shellu argumenty pomocí `subprocess`.
Pokud například chcete zobrazit i skryté soubory (ty, které začínají tečkou) a také metadata souborů, napíšeme tento kód:
import subprocess # subprocess.run('ls') # Jednoduchý příkaz subprocess.run('ls -la', shell=True)
Tento příkaz spouštíme jako řetězec a používáme argument `shell`. To znamená, že na začátku spuštění našeho dílčího procesu vyvoláme shell a argument příkazu je interpretován přímo shellem.
Použití `shell=True` však má několik nevýhod, z nichž nejhorší jsou potenciální bezpečnostní mezery. Více o nich se dozvíte v oficiální dokumentaci.
Nejlepší způsob, jak předávat příkazy funkci `run`, je použít seznam, kde `lst[0]` je příkaz k volání (v tomto případě `ls`) a `lst[n]` jsou argumenty tohoto příkazu.
Pokud to uděláme, náš kód bude vypadat takto:
import subprocess # subprocess.run('ls') # Jednoduchý příkaz # subprocess.run('ls -la', shell=True) # Nebezpečný příkaz subprocess.run(['ls', '-la'])
Pokud chceme uložit standardní výstup dílčího procesu do proměnné, můžeme to udělat nastavením argumentu `capture_output` na `True`.
list_of_files = subprocess.run(['ls', '-la'], capture_output=True) print(list_of_files.stdout) ❯ python list_dir.py b'total 36ndrwxr-xr-x 3 daniel daniel 4096 may 20 21:08 .ndrwx------ 30 daniel daniel 4096 may 20 18:03 ..n-rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.pyndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .gitn-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignoren-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.pyn-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSEn-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.pyn-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.mdn'
Pro přístup k výstupu procesu používáme atribut instance `stdout`.
V tomto případě chceme uložit výstup jako řetězec, nikoliv jako bajty, čehož dosáhneme nastavením argumentu `text` na hodnotu `True`.
list_of_files = subprocess.run(['ls', '-la'], capture_output=True, text=True) print(list_of_files.stdout) ❯ python list_dir.py total 36 drwxr-xr-x 3 daniel daniel 4096 may 20 21:08 . drwx------ 30 daniel daniel 4096 may 20 18:03 .. -rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.py drwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .git -rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignore -rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.py -rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSE -rw-r--r-- 1 daniel daniel 227 may 20 22:14 list_dir.py -rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.md
Výborně, nyní, když známe základy knihovny `subprocess`, je čas se podívat na některé příklady jejího využití.
Příklady použití dílčích procesů v Pythonu
V této části se podíváme na praktické použití knihovny `subprocess`. Všechny příklady naleznete v tomto úložišti na Githubu.
Kontrola instalace programu
Jedním z hlavních využití této knihovny je schopnost provádět jednoduché operace v OS.
Například jednoduchý skript, který kontroluje, zda je program nainstalován. V Linuxu toho dosáhneme pomocí příkazu `which`.
'''Kontrola programu pomocí subprocess''' import subprocess program = 'git' process = subprocess.run(['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Program "{program}" je nainstalován') print(f'Umístění binárního souboru je: {process.stdout}') else: print(f'Program {program} bohužel není nainstalován') print(process.stderr)
Poznámka: Když je příkaz v systémech UNIX úspěšný, jeho stavový kód je 0. Jinak se během provádění něco pokazilo.
Jelikož nepoužíváme argument `shell=True`, můžeme bezpečně přijímat uživatelský vstup. Také můžeme ověřit, zda je vstup platným programem pomocí regulárního výrazu.
import subprocess import re programs = input('Zadejte programy oddělené mezerou: ').split() secure_pattern = 'St' for program in programs: if not re.match(secure_pattern, program): print("Bohužel nemůžeme ověřit tento program") continue process = subprocess.run( ['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Program "{program}" je nainstalován') print(f'Umístění binárního souboru je: {process.stdout}') else: print(f'Program {program} bohužel není nainstalován') print(process.stderr) print('n')
V tomto případě získáváme programy od uživatele a používáme regulární výraz k ověření, zda řetězec programu obsahuje pouze písmena a číslice. Existenci každého programu ověřujeme pomocí cyklu `for`.
Jednoduchý Grep v Pythonu
Váš kamarád Tom má seznam vzorů v textovém souboru a další velký soubor, ve kterém chce zjistit počet shod pro každý vzor. Strávil hodiny spouštěním příkazu `grep` pro každý vzor.
Naštěstí víte, jak tento problém vyřešit pomocí Pythonu, a pomůžete mu tento úkol splnit během několika sekund.
import subprocess patterns_file="patterns.txt" readfile="romeo-full.txt" with open(patterns_file, 'r') as f: for pattern in f: pattern = pattern.strip() process = subprocess.run( ['grep', '-c', f'{pattern}', readfile], capture_output=True, text=True) if int(process.stdout) == 0: print( f'Vzor "{pattern}" se neshodoval s žádným řádkem v souboru {readfile}') continue print(f'Vzor "{pattern}" se shodoval {process.stdout.strip()} krát')
Když se podíváme na tento soubor, definujeme dvě proměnné, které představují názvy souborů, se kterými budeme pracovat. Poté otevřeme soubor, který obsahuje všechny vzory a iterujeme je. Dále zavoláme dílčí proces, který spustí příkaz `grep` s příznakem „-c“ (počet) a určí výstup shody s podmínkou.
Pokud tento soubor spustíte (nezapomeňte, že si můžete stáhnout textové soubory z úložiště na Githubu)
Nastavení `virtualenv` pomocí `subprocess`
Jednou z nejlepších věcí, které můžete s Pythonem dělat, je automatizace procesů. Tento typ skriptu vám může ušetřit spoustu času týdně.
Například vytvoříme instalační skript, který vytvoří virtuální prostředí a pokusí se nalézt soubor `requirements.txt` v aktuálním adresáři, aby nainstaloval všechny závislosti.
import subprocess from pathlib import Path VENV_NAME = '.venv' REQUIREMENTS = 'requirements.txt' process1 = subprocess.run(['which', 'python3'], capture_output=True, text=True) if process1.returncode != 0: raise OSError('Bohužel, python3 není nainstalován') python_bin = process1.stdout.strip() print(f'Python nalezen v: {python_bin}') process2 = subprocess.run('echo "$SHELL"', shell=True, capture_output=True, text=True) shell_bin = process2.stdout.split('/')[-1] create_venv = subprocess.run([python_bin, '-m', 'venv', VENV_NAME], check=True) if create_venv.returncode == 0: print(f'Vaše virtuální prostředí {VENV_NAME} bylo vytvořeno') pip_bin = f'{VENV_NAME}/bin/pip3' if Path(REQUIREMENTS).exists(): print(f'Soubor s požadavky "{REQUIREMENTS}" nalezen') print('Instaluji požadavky') subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS]) print('Proces dokončen! Nyní aktivujte své prostředí pomocí "source .venv/bin/activate"') else: print("Nebyly specifikovány žádné požadavky ...")
V tomto případě používáme více procesů a analyzujeme data, která potřebujeme v našem pythonovském skriptu. Používáme také knihovnu `pathlib`, která nám umožňuje zjistit, zda soubor `requirements.txt` existuje.
Pokud spustíte pythonovský soubor, dostanete několik užitečných zpráv o tom, co se děje s OS.
❯ python setup.py Python nalezen v: /usr/bin/python3 Vaše virtuální prostředí .venv bylo vytvořeno Soubor s požadavky "requirements.txt" nalezen Instaluji požadavky Collecting asgiref==3.3.4 ....... Proces dokončen! Nyní aktivujte své prostředí pomocí "source .venv/bin/activate"
Všimněte si, že výstup získáváme z instalačního procesu, protože nepřesměrováváme standardní výstup do proměnné.
Spuštění jiného programovacího jazyka
Můžeme spouštět jiné programovací jazyky pomocí Pythonu a získat výstup z těchto souborů. Je to možné, protože dílčí procesy komunikují přímo s operačním systémem.
Vytvořme si například program „hello world“ v C++ a Javě. Pro spuštění následujícího souboru musíte mít nainstalované kompilátor pro C++ a kompilátor pro Javu.
helloworld.cpp
#include <iostream> int main(){ std::cout << "Toto je hello world v C++" << std::endl; return 0; }
helloworld.java
class HelloWorld{ public static void main(String args[]){ System.out.println("Toto je hello world v Javě"); } }
Vím, že se to zdá být hodně kódu ve srovnání s jednoduchým pythonovským jednořádkovým příkazem, ale je to pouze pro testovací účely.
Vytvoříme skript v Pythonu, který spustí všechny soubory v C++ a Javě v daném adresáři. Nejprve chceme získat seznam souborů v závislosti na příponě souboru a `glob` nám to umožňuje snadno!
from glob import glob # Získává soubory s každou příponou java_files = glob('*.java') cpp_files = glob('*.cpp')
Poté můžeme začít používat dílčí procesy pro spuštění jednotlivých typů souborů.
for file in cpp_files: process = subprocess.run(f'g++ {file} -o out; ./out', shell=True, capture_output=True, text=True) output = process.stdout.strip() + ' Mimochodem, toto spustil Python' print(output) for file in java_files: without_ext = file.strip('.java') process = subprocess.run(f'java {file}; java {without_ext}',shell=True, capture_output=True, text=True) output = process.stdout.strip() + ' Tento proces spustil dílčí proces Pythonu :)' print(output)
Jeden malý trik je použít funkci `strip()` řetězce k úpravě výstupu, abychom získali pouze to, co potřebujeme.
Poznámka: Buďte opatrní při spouštění rozsáhlých souborů Java nebo C++, protože jejich výstup ukládáme do paměti, což by mohlo způsobit únik paměti.
Otevírání externích programů
Jsme schopni spouštět další programy pouhým voláním umístění jejich binárních souborů prostřednictvím `subprocess`.
Zkusme to otevřením Brave, mého oblíbeného webového prohlížeče.
import subprocess subprocess.run('brave')
Tím se otevře nová instance prohlížeče nebo další karta, pokud už prohlížeč máte spuštěný.
Stejně jako u jakéhokoli jiného programu, který přijímá parametry, můžeme je využít k úpravě chování programu.
import subprocess subprocess.run(['brave', '--incognito'])
Shrnutí
Dílčí proces je výpočetní proces vytvořený jiným procesem. Procesy běžící na našem počítači můžeme zkontrolovat pomocí nástrojů jako `htop` a Správce úloh.
Python má vlastní knihovnu pro práci s dílčími procesy. V současné době nám funkce `run` poskytuje jednoduché rozhraní pro vytváření a řízení dílčích procesů.
Díky interakci s OS můžeme vytvářet nejrůznější typy aplikací.
A nakonec si pamatujte, že nejlepší způsob, jak se něco naučit, je vytvářet věci, které byste chtěli používat.