Cvičení: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14.
Na tomto cvičení se podíváme na dva užitečné prográmky, xargs
a find
. A
také si rozšíříme znalosti o o SSH, protože si povíme o tzv. port
forwardingu.
Ale hlavní náplní bude vývoj projektů v jazyce Python ve virtuálním (izolovaném) prostředí, které lze snadno distribuovat mezi jednotlivé vývojáře ve velkém softwarovém týmu. A také se podíváme, jak se programy chystají na další distribuci.
Části o find
a xargs
jsou částečně propojeny, jinak jsou tři témata
(find
/xargs
, SSH port forwarding a práce v izolovaném prostředí) v
podstatě nezávislá a lze je číst v libovolném pořadí. Znalost o pokročilém
vývoji v Pythonu se bude hodit pro poslední domácí úlohu.
Předstartovní kontrola
- Víte, co je SSH.
- Víte, co jsou TCP porty.
- Víte, co je to obraz disku.
- Pamatujete si, k čemu se používají shellové wildcardy (zástupné znaky).
- Víte, že řetězce v C jsou zakončeny nulovým bajtem.
- Víte, jak se vytvářejí a jak jsou organizovány moduly v Pythonu.
Nástroje xargs
(a parallel
)
xargs
ve své nejjednodušší podobě čte standardní vstup a převádí jej na
argumenty pro uživatelem zadaný program.
Předpokládejme, že existují následující soubory:
2024-04-16.txt 2024-04-24.txt 2024-05-02.txt 2024-05-10.txt
2024-04-17.txt 2024-04-25.txt 2024-05-03.txt 2024-05-11.txt
2024-04-18.txt 2024-04-26.txt 2024-05-04.txt 2024-05-12.txt
2024-04-19.txt 2024-04-27.txt 2024-05-05.txt 2024-05-13.txt
2024-04-20.txt 2024-04-28.txt 2024-05-06.txt 2024-05-14.txt
2024-04-21.txt 2024-04-29.txt 2024-05-07.txt 2024-05-15.txt
2024-04-22.txt 2024-04-30.txt 2024-05-08.txt
2024-04-23.txt 2024-05-01.txt 2024-05-09.txt
Jako malé cvičení napište jednořádkový příkaz shellu, který tyto soubory vytvoří.
Řešení.Naším úkolem je odstranit soubory starší než 20 dní. V této verzi pouze provedeme echo příkazu, abychom nemuseli soubory při ladění našeho řešení znovu vytvářet.
cutoff_date="$( date -d "20 days ago" '+%Y%m%d' )"
for filename in 202[0-9]-[01][0-9]-[0-3][0-9].txt; do
date_num="$( basename "$filename" .txt | tr -d '-' )"
if [ "$date_num" -lt "$cutoff_date" ]; then
echo rm "$filename"
fi
done
To znamená, že program rm
by byl volán několikrát, přičemž by vždy
odstranil jen jeden soubor. Režie spojená se spouštěním nového procesu by
se mohla stát vážným úzkým hrdlem pro větší skripty (uvažujte například o
tisících souborů).
Bylo by mnohem lepší, kdybychom rm
zavolali jen jednou a zadali mu seznam
souborů, které má odstranit (tj. jako více argumentů).
Zde je řešením xargs
. Trochu program upravíme:
cutoff_date="$( date -d "20 days ago" '+%Y%m%d' )"
for filename in 202[0-9]-[01][0-9]-[0-3][0-9].txt; do
date_num="$( basename "$filename" .txt | tr -d '-' )"
if [ "$date_num" -lt "$cutoff_date" ]; then
echo "$filename"
fi
done | xargs echo rm
Místo toho, abychom soubor hned odstranili, vypíšeme pouze jeho název a
celou smyčku přesměrujeme rourou do xargs
, kde všechny jeho běžné
argumenty odkazují na program, který má být spuštěn.
Místo mnoha řádků s rm ...
uvidíme jen jeden dlouhý řádek s jediným
voláním rm
.
Další situací, kdy se xargs
může hodit, je sestavování složitého
příkazového řádku nebo situace, kdy by použití nahrazování příkazů ($( ... )
) způsobilo nečitelnost skriptu.
Záludné názvy souborů mohou samozřejmě stále způsobovat problémy, protože
xargs
předpokládá, že argumenty jsou odděleny bílými znaky. (Všimněte si,
že výše jsme byli v bezpečí, protože názvy souborů byly rozumné.) To lze
změnit pomocí --delimiter
.
Pokud do xargs
přesměrováváte výstup z vašeho programu, zvažte oddělení
položek nulovým bajtem (tj. terminátorem řetězce v jazyce C, \0
).
Vzpomeňte si, co jste měli v kurzu Arduina o řetězcích C – a jak se
ukončují. To je nejbezpečnější možnost, protože tento znak se nemůže objevit
nikde uvnitř žádného argumentu. A informujte o tom xargs
pomocí -0
nebo
--null
.
Poznamenejme, že xargs
je dostatečně chytrý na to, aby si uvědomil, kdy by
byl příkazový řádek příliš dlouhý, a automaticky jej rozdělí (podrobnosti
viz manuál).
Je také dobré mít na paměti, že xargs
může příkaz provádět paralelně
(tj. rozdělit stdin na více částí a zavolat program vícekrát s různými
částmi) prostřednictvím -P
. Pokud jsou vaše shellové skripty pomalé, ale
máte dostatek procesorového výkonu, může vám to práci docela urychlit.
parallel
Tento program lze použít k paralelnímu provádění více příkazů, čímž se urychlí jejich provádění.
parallel
se chová téměř stejně jako xargs
, ale má mnohem lepší podporu
pro souběžné provádění jednotlivých úloh (nemíchání jejich výstupů,
provádění na vzdáleném stroji atd. atd.).
Rozdíly jsou poměrně dobře popsány v dokumentaci příkazu
parallel
.
Další podrobnosti naleznete v parallel_tutorial(1)
(ano, je to manuálová
stránka) a v parallel(1)
.
find
I když jsou ls(1)
a wildcardy dosti mocné, někdy potřebujeme vybrat
soubory pomocí složitějších kritérií. Zde je užitečný program find(1)
.
Bez argumentů vypíše všechny soubory v aktuálním adresáři, včetně souborů ve vnořených adresářích.
/
), pokud nevíte, co děláte (a
rozhodně ne na sdíleném počítači linux.ms.mff.cuni.cz
).
Pomocí parametru -name
můžete omezit vyhledávání na soubory odpovídající
zadanému wildcardu.
Následující příkaz vyhledá všechny soubory alpha.txt
v aktuálním adresáři
a v libovolném podadresáři (bez ohledu na hloubku).
find -name alpha.txt
Proč následující příkaz pro vyhledání všech souborů *.txt
nebude fungovat?
find -name *.txt
find
má mnoho voleb – nebudeme zde opakovat jeho manuálovou stránku, ale
zmíníme se o těch, které stojí za zapamatování.
-delete
okamžitě odstraní nalezené soubory. Velmi užitečné a velmi
nebezpečné.
-exec
spustí daný program pro každý nalezený soubor. Musíte použít {}
pro zadání názvu nalezeného souboru a příkaz musíte ukončit pomocí ;
(protože ;
ukončuje příkazy i v shellu, budete ho muset escapovat).
find -name '*.md' -exec wc -l {} \;
Všimněte si, že pro každý nalezený soubor dojde k novému vyvolání příkazu
wc
. To lze změnit změnou terminátoru příkazu (\;
) na +
. Podívejte se
na rozdíl mezi voláním následujících dvou příkazů:
find -name '*.md' -exec echo {} \;
find -name '*.md' -exec echo {} +
Upozornění
Ve výchozím nastavení vypíše find
jeden název souboru na řádek. Jméno
souboru však může obsahovat i znak nového řádku (!), a proto následující
idiom není 100% bezpečný.
find -options-for-find | while read filename; do
do_some_complicated_things_with "$filename"
done
Pokud chcete být opravdu bezpeční, použijte -print0
a IFS= read -r -d $'\0' filename
, neboť tak se použije jediný bezpečný oddělovač – \0
.
Alternativně můžete výstup find -print0
přesměrovat do xargs --null
.
Pokud však pracujete s vlastními soubory nebo je vzor bezpečný, výše uvedená
smyčka je v pořádku (jen nezapomeňte, že adresáře jsou také soubory a mohou
ve svém názvu obsahovat \n
).
Shell také umožňuje exportovat funkci a volat ji zevnitř xargs
. Vypadá to
ošklivě, ale je to bezpečný přístup, pokud chcete provádět složité operace
nad nalezenými soubory.
my_callback_function() {
echo ""
echo "\$0 = $0"
echo "\$@ =" "$@"
}
export -f my_callback_function
find . -print0 | xargs -0 -n 1 bash -c 'my_callback_function "$@"' arg_zero arg_one
Připomeňme, že funkce lze definovat přímo v shellu a výše uvedené lze skutečně vytvořit interaktivně, aniž by se cokoliv muselo ukládat jako skript.
Přesměrování portů přes SSH (SSH port forwarding)
Obecně platí, že služby poskytované počítačem by neměly být vystaveny na síti, aby si s nimi mohli hrát náhodní “bezpečnostní výzkumníci”. Proto je brána firewall obvykle nakonfigurována tak, aby kontrolovala přístup k počítači ze sítě.
Pokud má být služba poskytována pouze lokálně, je ještě jednodušší nechat ji poslouchat pouze na zařízení zpětné smyčky (loopback). Tímto způsobem k ní mohou přistupovat pouze místní uživatelé (včetně uživatelů připojených k počítači přes SSH).
Pro příklad si můžete povšimnout, že na linux.ms.mff.cuni.cz
je na portu
8080 naslouchající webový server. Tento webový server není dostupný, pokud
se k němu pokusíte přistupovat přes linux.ms.mff.cuni.cz
, ale lokální
přístup k němu (při přihlášení k linux.ms.mff.cuni.cz
) funguje.
you@laptop$ curl http://linux.ms.mff.cuni.cz:8080 # Fails
you@laptop$ ssh linux.ms.mff.cuni.cz curl --silent http://localhost:8080 # Works
Přístup k tomuto webovému serveru pomocí cURL je sice možný, ale není to uživatelsky nejpřívětivější způsob prohlížení webových stránek.
Přesměrování portů přes SSH (SSH local port forwarding)
SSH lze použít k vytvoření bezpečného tunelu, přes který je lokální port přesměrován na port přístupný ze vzdáleného stroje. V podstatě se připojíte ke zpětné smyčce na svém počítači a SSH tuto komunikaci přesměruje na vzdálený server, čímž efektivně zpřístupní vzdálený port.
Následující příkaz způsobí, že se lokální port 8888 bude chovat jako port
8080 na vzdáleném počítači. Část 127.0.0.1
odkazuje na zpětnou smyčku na
vzdáleném serveru (můžete tam napsat i localhost
)
ssh -L 8888:127.0.0.1:8080 -N linux.ms.mff.cuni.cz
Vždy nejprve uvedete, který lokální port chcete přesměrovat (8888), a poté cíl, jako byste se připojovali ze vzdáleného počítače (172.0.0.1:8080).
Pomocí -N
je toto připojení použitelné pouze pro přesměrování – pro jeho
ukončení použijte Ctrl-C (bez něj se přihlásíte i ke vzdálenému počítači).
Otevřete http://localhost:8888 v prohlížeči a zkontrolujte, zda vidíte
stejný obsah jako při použití výše uvedeného příkazu ssh linux.ms.mff.cuni.cz curl http://localhost:8080
.
Často budete přesměrovávat (lokální) port N na stejný (vzdálený) port N,
proto je velmi snadné zapomenout na správné pořadí. Pořadí parametrů
-L
je ale důležité a záměna čísel (např. 8888:127.0.0.1:9090
místo
9090:127.0.0.1:8888
) způsobí přesměrování jiných portů (většinou se to
však rychle naučíte).
Nemějte však obavy, pokud si je nemůžete zapamatovat. Proto máte k dispozici manuálové stránky, které používají i každodenní uživatelé Linuxu. Není to nic, za co byste se měli stydět nebo čeho byste se měli bát :-).
Reverzní port forward (SSH remote/reverse port forwarding)
SSH umožňuje vytvořit také tzv. reverzní port forward.
V podstatě umožňuje otevřít spojení ze vzdáleného serveru k místnímu počítači (proti směru spojení SSH).
Prakticky můžete nastavit reverzní přesměrování portů například připojením se ze stolního počítače, který máte doma, na počítač v IMPAKTu/Rotundě a poté se pomocí tohoto reverzního přesměrování připojit z IMPAKTu/Rotundy zpět na svůj domácí počítač.
Tato vlastnost bude fungovat i v případě, že je váš počítač za NAT, což znemožňuje přímé připojení zvenčí.
Následující příkaz nastaví reverzní přesměrování portů tak, že připojení k
portu 2222
na vzdáleném počítači bude převedeno na připojení k portu 22
(ssh) na lokálním počítači:
ssh -N -R 2222:127.0.0.1:22 u-plN.ms.mff.cuni.cz
Nejprve uvedete vzdálený port k přesměrování (2222) a poté cíl, jako byste se připojovali z lokálního stroje (127.0.0.1:22).
Při zkoušení tohoto postupu se ujistěte, že je spuštěn démon sshd
(vzpomeňte si na lab 10 a příkaz systemctl
), a použijte jiný port než
2222, abyste zabránili kolizím.
Abyste se mohli připojit k vašemu počítači přes tento přesměrovaný port, musíte tak učinit z laboratoře IMPAKTu/Rotundy pomocí následujícího příkazu.
ssh -p 2222 your-desktop-login@localhost
Používáme localhost
, protože připojení je vázáno pouze na rozhraní
loopback, nikoli na skutečný síťový adaptér dostupný na laboratorních
počítačích. (Ve skutečnosti ssh
umožňuje navázat port forward na veřejnou
IP adresu, ale správce to často z bezpečnostních důvodů zakazuje.)
Vývoj softwaru ve virtuálním prostředí
Během předchozích cvičení jsme viděli, že doporučovaným způsobem instalace aplikací (a knihoven nebo datových souborů) na Linuxu je skrze správce balíčků. Ten nainstaluje aplikaci pro všechny uživatele, umožní upgrade celého systému a celkově udržuje systém v rozumném stavu.
Nicméně, ne vždy se systémová instalace hodí. Typický příklad jsou závislosti pro konkrétní projekt. Ty se obvykle neinstalují do systému a to hlavně z následujících důvodů:
- Potřebujete různé verze závislostí pro různé projekty.
- Nechcete si pamatovat, co všechno máte odinstalovat, když na projektu přestanete pracovat.
- Potřebujete určit, kdy dojde k jejich upgradu: upgrade OS by neměl ovlivnit váš projekt.
- Verze, které potřebujete, jsou jiné, než které zná správce balíčků.
- A nebo nejsou vůbec ve správci balíčků dostupné.
Z výše uvedených důvodů je mnohem lepší vytvořit instalaci určenou pro jeden
projekt, která je lépe oddělena (izolována) od zbytku systému.
V tomto ohledu instalace pro uživatele (tj. někam do $HOME
) nemusí
poskytnout takovou míru odstínění, jakou chceme.
Tento přístup podporuje většina rozumných programovacích jazyků a obvykle je najdeme pod názvy jako virtual environment, local repository, sandbox nebo něco podobného (koncepty nejsou 1:1 ve všech jazycích a nástrojích, ale základní myšlenka je stejná).
S virtuálním prostředím (virtual environment) jsou závislosti obvykle instalovány do určeného adresáře ve vašem projektu. Tento adresář potom neverzujeme (Gitem). Překladač/interpret je pak nasměrován na tento adresář.
Instalace do jednoho adresáře nezanese váš systém. Umožní vám pracovat na více projektech s nekompatibilními závislostmi, protože jsou kompletně oddělené.
Každý vývojář si pak může prostředí vytvořit, aniž by do repozitáře přibývaly soubory, které jsou specifické pro danou distribuci nebo operační systém. Ale konfigurační soubor zajistí, že všichni budou pracovat s identických prostředím (tj. stejné verze všech závislostí).
Také to znamená, že noví členové softwarového týmu si mohou prostředí jednoduše připravit právě s pomocí takového konfiguračního souboru.
Instalace závislostí
Uvnitř virtuálního prostředí projektu se obvykle nepoužívají správci balíčků (jako DNF). Místo toho jsou závislosti instalovány správci balíčků daného programovacího jazyka.
Ty jsou obvykle platformě nezávislé a používají vlastní repozitáře. Tyto repozitáře pak obsahují jen knihovny pro daný programovací jazyk. Opět může být těchto repozitářů více a je na vývojářích, jak si vše nastaví
V našem případě budeme vždy instalovat jen do adresářů virtuálního prostředí a do systému jako takového zasahovat nebudeme.
Instalační adresáře
Na typickém Linuxovém systému existuje několik míst, kam může být software nainstalován:
/usr
– systémové balíčky jak je instaluje správce balíčků dané distribuce/usr/local
– software instalovaný lokálně administrátorem; sem obvykle instalují svoje balíčky správci balíčků daného jazyka, pokud je instalují globálně (system-wide)/opt/$PACKAGE
– velké balíčky, které neinstaluje správce balíčků, mají často celý vlastní adresář uvnitř/opt
.$HOME
(obvykle/home/$USER/
) – jazykoví správci balíčků spuštění ne-rootem mohou balíčky nainstalovat do domovských adresářů (přesné umístění záleží na jazyce).$HOME/.local
je obvyklé místo pro lokální instalaci, které kopíruje/usr/local
, ale je jen pro jednoho uživatele (spustitelné soubory jsou pak v$HOME/.local/bin
)- virtuální prostředí pro každý projekt
Python Package Index (PyPI)
Zbytek textu se bude zabývat Pythoními nástroji. Podobné nástroje jsou ale dostupné i pro další jazyky: věříme, že ukázky nad Pythonem jsou dostačující pro demonstraci principů v praxi.
Python má repozitář zvaný Python Package Index (PyPI), kde kdokoliv může zveřejnit své Pythonovské knihovny či programy.
Repozitář je možné používat buď skrz webový prohlížeč, nebo přes klienta na
příkazové řádce, který se jmenuje pip
.
pip
se chová podobně jako DNF.
Můžete s ním instalovat, upgradovat nebo odinstalovat Pythoní moduly.
Problémy s důvěryhodností
Všechny balíčky z hlavního repozitáře vaší distribuce jsou obvykle kontrolovány někým z bezpečnostního týmu pro danou distribuci. To bohužel není pravda pro repozitáře, jako je PyPI. Takže jako vývojář musíte být opatrnější, když budete instalovat software z těchto zdrojů.
Ne všechny balíčky dělají to, co slibují. Některé jejich chyby jsou jen neškodné hlouposti, ale některé jsou záměrně škodlivé. Používání existujícího kódu je obvykle dobrý nápad, ale měli byste se zamyslet nad důvěryhodností autora. Konec konců, kód poběží na vašem účtu, až jej budete spouštět nebo instalovat.
Typickým příkladem je tzv. typosquatting, kdy lumpové zveřejní svůj zákeřný balíček a jeho jméno se liší jen překlepem od jiného známého balíčku. Můžeme doporučit tenhle článek, ale na webu jich lze najít mnohem víc.
Na druhou stranu, mnoho PyPI balíčků je také dostupných jako balíčky pro
vaši distribuci (klidně si zkuste dnf search python3-
na vašem Fedořím
stroji).
Takže byly pravděpodobně už některým ze správců distribuce zkontrolovány a
jsou v pořádku.
Pro balíčky, které takto dostupné nejsou, se vždy dívejte na znaky
normálního a zákeřného projektu. Oblíbenost repozitáře se
zdrojáky. Uživatelské aktivita. Jak reagují na bug reporty? Kvalita
dokumentace. Atd. atd.
Nezapomeňte, že dnešní software je málokdy stavěn na zelené louce. Nebojte se zkoumat, co už je hotové. Zkontrolujte si to. A pak to použijte :-).
Obvyklý průběh práce
Zatímco konkrétní nástroje se liší podle programovacího jazyka, základní kroky pro vývoj projektu uvnitř virtuálního projektu jsou v podstatě vždy stejné.
- Vývojář naklonuje projekt (třeba z Gitového repozitáře).
- Virtuální prostředí (sandbox) je inicializováno. To obvykle znamená vytvoření nového adresáře s čistým prostředím pro daný jazyk.
- Virtuální prostředí musí být aktivováno. Obvykle virtuální prostředí
musí změnit
$PATH
(nebo nějaký podobný ekvivalent, který je používán pro hledání knihoven a modulů), takže vývojář musísource
(nebo.
) nějaký aktivační skript, aby byly změněny příslušné proměnné. - Potom může vývojář nainstalovat závislosti. Obvykle jsou uloženy v souboru, který lze přímo předat správci balíčků (daného programovacího jazyka).
- Teprve teď může vývojář začít na projektu pracovat. Projekt je kompletně izolován, odstraněním adresáře s virtuálním prostředím odstraníme i veškeré stopy po instalovaných balíčcích.
Každodenní práce pak zahrnuje jen krok č. 3 (nějaký způsob aktivace) a krok 5 (vlastní vývoj).
Poznamenejme, že aktivace virtuálního prostředí obvykle odstraní přístup ke globálně (systémově) nainstalovaným knihovnám. Uvnitř virtuálního prostředí vývojář začíná s čistým štítem: jen s holým překladačem. To je ale velmi rozumné rozhodnutí: zajistí, že libovolná systémová instalace neovlivní nastavení projektu.
Jinými slovy to zlepší opakovatelnost (reproducibility) celého nastavení. Také to znamená, že vývojář musí uvést každou závislost do konfiguračního souboru. I pokud je to závislost, která je obvykle vždy nainstalovaná.
Virtuální prostředí pro Python (také známé jako virtualenv
nebo venv
)
Abychom si mohli bezpečně vyzkoušet instalaci balíčků Pythonu, nastavíme nejprve virtuální prostředí pro náš projekt. Python má naštěstí vestavěnou podporu pro vytvoření virtuálního prostředí.
Ukážeme si to na následujícím příkladu:
#!/usr/bin/env python3
import argparse
import shutil
import sys
import fs
class FsCatException(Exception):
pass
def fs_cat(filesystem, filename, target):
try:
with fs.open_fs(filesystem) as my_fs:
try:
with my_fs.open(filename, 'rb') as my_file:
shutil.copyfileobj(my_file, target)
except fs.errors.FileExpected as e:
raise FsCatException(f"{filename} on {filesystem} is not a regular file") from e
except fs.errors.ResourceNotFound as e:
raise FsCatException(f"{filename} does not exist on {filesystem}") from e
except Exception as e:
if isinstance(e, FsCatException):
raise e
raise FsCatException(f"unable to read {filesystem}, perhaps misspelled path or protocol ({e})?") from e
def main():
args = argparse.ArgumentParser(description='Filesystem cat')
args.add_argument(
'filesystem',
nargs=1,
metavar='FILESYSTEM',
help='Filesystem specification, e.g. tar://path/to/file.tar'
)
args.add_argument(
'filename',
nargs=1,
metavar='FILENAME',
help='File path on FILESYSTEM, e.g. /README.md'
)
config = args.parse_args()
try:
fs_cat(config.filesystem[0], config.filename[0], sys.stdout.buffer)
except FsCatException as e:
print(f"Fatal: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Uložte tento kód do souboru fscat.py
a nastavte mu spustitelný bit.
Všimněte si, že fs.open_fs
dokáže otevírat různé souborové systémy a
přistupovat k souborům v nich, jako byste použili vestavěný pythonovský
open
.
V našem programu zadáme cestu k souborovému systému a souboru (z tohoto
filesystému), který má být vypsán na obrazovku (odtud název fscat
, protože
simuluje cat
uvnitř jiného souborového systému).
Zkuste spustit program fscat.py
.
Pokud nemáte v systému nainstalovaný balíček python3-fs
, mělo by dojít k
chybě ModuleNotFoundError: No module named 'fs'
. Je pravděpodobné, že
tento modul nainstalovaný nemáte.
Pokud jste již nainstalovali python3-fs
, odinstalujte jej a zkuste to
znovu (jen pro tuto ukázku). Ale dvakrát zkontrolujte, zda byste
neodstranili nějaký jiný program, který jej může vyžadovat.
Nyní bychom mohli nainstalovat python3-fs
pomocí DNF, ale již jsme
vysvětlili, proč to není dobrý nápad. Mohli bychom jej také nainstalovat
globálně pomocí pip
, ale ani to není nejlepší postup.
Místo toho pro něj vytvoříme nové virtuální prostředí.
python3 -m venv my-venv
Výše uvedený příkaz vytvoří nový adresář my-venv
, který obsahuje čistou
instalaci Pythonu. Neváhejte a prozkoumejte obsah tohoto adresáře.
Nyní je třeba toto prostředí aktivovat.
source my-venv/bin/activate
Váš prompt by se měl změnit: nyní začíná (my-venv)
.
Spuštění souboru fscat.py
přesto skončí s chybou ModuleNotFoundError
.
Nyní nainstalujeme závislost:
pip install fs
To bude chvíli trvat, protože Python bude stahovat také tranzitivní
závislosti této knihovny (a jejich závislosti atd.). Po dokončení instalace
spusťte znovu soubor fscat.py
.
Tentokrát by to mělo fungovat.
./fscat.py
Dobře, vypsalo to chybovou zprávu o požadovaných argumentech. Stáhněte si tento archív a spusťte skript následujícím způsobem:
./fscat.py tar://test.tar.gz testdir/test.txt
Měl by vypsat Test string
, protože je schopen pracovat i s tar archívy
jako se souborovými systémy a vypsat na nich soubory (ověřte, zda je soubor
skutečně k dispozici, buď pomocí atool
, MC, nebo přímo pomocí tar
).
Jakmile jsme s vývojem hotovi, můžeme prostředí deaktivovat voláním
deactivate
(tentokrát bez zadávání zdrojů).
Spuštění souboru fscat.py
mimo prostředí opět skončí chybou
ModuleNotFoundError
.
Jak to funguje?
Virtuální prostředí Pythonu používá při své implementaci dva triky.
Nejprve skript activate
rozšíří $PATH
o adresář my-venv/bin
. To
znamená, že volání python3
bude preferovat aplikaci z adresáře virtuálního
prostředí (např. my-venv/bin/python3
).
Vyzkoušejte si to sami: vypište $PATH
před a po aktivaci virtuálního
prostředí.
To také vysvětluje, proč bychom měli v shebangu vždy zadávat /usr/bin/env python3
místo /usr/bin/python3
. env
se bude řídit proměnnou $PATH
,
která byla změněna aktivací virtuálního prostředí.
Můžete si také prohlédnout skript activate
a zjistit, jak je
implementován. Všimněte si, že deactivate
je ve skutečnosti funkce.
Proč není skript activate
spustitelný?
Nápověda.
Druhý trik spočívá v tom, že Python hledá moduly (tj. soubory implementující
importovaný modul) vzhledem k cestě k binárnímu souboru python3
. Pokud je
tedy python3
uvnitř my-venv/bin
, Python bude hledat moduly uvnitř
my-venv/lib
. To je adresář, kde budou umístěny lokálně nainstalované
soubory.
To si můžete ověřit spuštěním následujícího jednořádkového příkazu, který vypíše prohledávané adresáře Pythonu (opět před a po aktivaci):
python3 -c 'import sys; print(sys.path)'
Toto chování ve skutečnosti není v interpretu jazyka Python pevně
zabudováno. Při spuštění Pythonu se automaticky importuje modul
site
. Tento modul obsahuje nastavení specifické pro dané umístění: upraví
soubor sys.path
tak, aby obsahoval všechny adresáře, do kterých vaše
distribuce instaluje moduly Pythonu. Detekuje také virtuální prostředí tak,
že hledá soubor pyvenv.cfg
v prarodičovském adresáři binárního souboru
python3
. V našem případě tento konfigurační soubor obsahuje
include-system-site-packages=false
, což modulu site
říká, aby adresáře s
moduly distribuce vynechal. Vidíte, že princip je velmi jednoduchý a
samotný interpret nemusí o virtuálních prostředích nic vědět.
Instalace balíčků specifických pro Python pomocí pip
pip
VS. python3 -m pip
?
Obecně se doporučuje používat python3 -m pip
, nikoli surový pip
.
Důvody, které vedly k těmto dalším deseti stiskům kláves, jsou dobře popsány
v článku Why you should use python3 -m pip
. Aby však byl
následující text čitelnější, budeme používat kratší variantu pip
.
Jedno použití pip
u v praxi jsme si již ukázali, ale pip
toho umí mnohem
více. Pěkný přehled všech možností pip
u najdete v článku Using Python’s
pip to Manage Your Projects'
Dependencies.
Zde uvádíme stručný přehled nejdůležitějších pojmů a příkazů.
Ve výchozím nastavení pip install
prohledává registr balíčků
PyPI, aby nainstaloval balíček zadaný na příkazovém
řádku. Nebyli bychom daleko od pravdy, kdybychom řekli, že všechny balíčky
uvnitř tohoto registru jsou jen archivované adresáře, které obsahují
předepsaným způsobem uspořádaný zdrojový kód v jazyce Python.
Pokud chcete tento výchozí registr balíčků změnit, můžete použít argument
--index-url
.
V další části se naučíme, jak z adresáře s kódem vytvořit správný balíček
Pythonu. Za předpokladu, že jsme to již udělali, můžeme tento balíček
nainstalovat přímo (bez archivace/balení) příkazem pip install /path/to/python_package
.
Představte si například situaci, kdy máte zájem o open-source balíček třetí
strany. Tento balíček je k dispozici ve vzdáleném repozitáři git (typicky
na GitHubu nebo GitLabu), ale NENÍ zabalen a zveřejněn v PyPI. Můžete
jednoduše klonovat repozitář a spustit pip install .
. Díky pip VCS
Support se však můžete
fázi klonování vyhnout a balíček nainstalovat přímo pomocí:
pip install git+https://git.example.com/MyProject
Chcete-li aktualizovat konkrétní balíček, spusťte příkaz pip install --upgrade [packages]
.
Nakonec pro odstranění balíčku spustíte pip uninstall [packages]
.
Verzování závislostí
Možná jste už slyšeli o sémantickém verzování. Python používá víceméně kompatibilní verzování, které je popsáno v PEP 440 – Version Identification and Dependency Specification.
Při instalaci závislostí z registru balíčků můžete tuto verzi zadat.
pkgname # latest version
pkgname == 4.2 # specific version
pkgname >= 4.2 # minimal version
pkgname ~= 4.2 # equivalent to >= 4.2, == 4.*
Pravdou je, že specifikátor verze se skládá z řady klauzulí o verzi oddělených čárkami. Proto můžete zadat:
pkgname >= 1.0, != 1.3.4.*, < 2.0
Někdy je užitečné uložit seznam všech aktuálně nainstalovaných balíčků (včetně tranzitivních závislostí). Například jste si nedávno všimli nové chyby ve svém projektu a rádi byste si uchovali přesné verze aktuálně nainstalovaných závislostí, aby váš spolupracovník mohl chybu reprodukovat.
Za tímto účelem je možné použít pip freeze
a vytvořit seznam, který
nastaví konkrétní verze a zajistí tak stejné prostředí pro každého vývojáře.
Doporučujeme je uložit do souboru requirements.txt
.
# Generating requirements file
pip freeze > requirements.txt
# Installing package from it
pip install -r requirements.txt
Balení Pythoních projektů
Řekněme, že jste přišli na super skvělý algoritmus a chcete obohatit svět tím, že se o něj podělíte. Oficiální dokumentace Pythonu nabízí návod krok za krokem, jak toho dosáhnout.
Struktura adresáře balíčků Pythonu
Úplně prvním krokem, než jej můžete publikovat, je jeho transformace do
správného balíčku Pythonu. Musíme vytvořit soubory pyproject.toml
a
setup.cfg
. Tyto soubory obsahují informace o projektu, seznam závislostí a
také informace pro instalaci projektu.
setup.py
, nikoli setup.cfg
a
pyproject.toml
. Proto v mnoha repozitářích/návodech stále můžete najít
jeho použití. Obsah je víceméně 1:1, ale jsou určité případy, kdy jste
nuceni použít setup.py
. To naštěstí pro náš usecase neplatí, a proto jsme
se rozhodli popsat moderní variantu se statickými konfiguračními soubory.
setuptools
možnost experimentálního použití pouze
pyproject.toml
. Tento přístup používá také
Poetry, ale v
následujícím textu zůstaneme u stabilní kombinace setup.cfg
a
pyproject.toml
.
V repozitáři
fscat
najdete balíček Pythonu se stejnou funkčností, jakou má náš předchozí skript
fscat.py
.
setup.cfg
.
Můžeme si všimnout, že potřebné závislosti jsou v souboru setup.cfg
a v
souboru requirements.txt
duplicitní. Ve skutečnosti to není chyba. V
souboru setup.cfg
je třeba použít co nejslabší verzi závislosti, zatímco v
souboru requirements.txt
musíme všechny závislosti uvést s přesnou verzí.
Existují také tranzitivní závislosti, které by se v souboru setup.cfg
NEMĚLY vyskytovat.
Další podrobnosti naleznete v části install_requires vs requirements file.
Zkuste si tento balíček (s pomocí podpory VCS) následujícím příkazem nainstalovat:
pip install git+http://gitlab.mff.cuni.cz/teaching/nswi177/2024/common/fscat.git
Možná jste si všimli, že soubor setup.cfg
obsahuje sekci
[options.entry_points]
. Tato sekce určuje, jaké jsou skripty váš projekt
aktuálně obsahuje. Všimněte si, že po spuštění výše uvedeného příkazu
můžete spustit příkaz fscat
přímo. Pip pro vás vytvořil obalový skript a
přidal jej do $PATH
virtuálního prostředí.
fscat tar://tests/test.tar.gz testdir/test.txt
Nyní balíček odinstalujte pomocí:
pip uninstall matfyz-nswi177-fscat
Naklonujte repozitář do místního počítače a vstupte do jeho adresáře. Nyní spusťte:
pip install -e .
pip install -e
vytvoří editovatelnou instalaci pro snadné
ladění. Namísto kopírování vašeho kódu do virtuálního prostředí nainstaluje
pouze věc podobnou symlinku (ve skutečnosti soubor fscat.egg-link
, který
má podobný účinek na mechanismus Pythonu pro vyhledávání modulů) odkazující
na adresář s vašimi zdrojovými soubory.
Sestavení Pythoního balíčku
Když už máme správnou adresářovou strukturu, zbývají nám už jen dva kroky k jejímu zveřejnění v registru balíčků.
Nyní připravíme distribuční balíčky pro náš kód. Nejprve nainstalujeme
balíček build
příkazem pip install build
. Poté můžeme spustit příkaz
python3 -m build
V podadresáři dist
se vytvoří dva soubory:
-
matfyz-nswi177-fscat-0.0.1.tar.gz
– archiv zdrojového kódu -
matfyz_nswi177_fscat-0.0.1-py3-none-any.whl
– wheel soubor, což je sestavený balíček (py3
je požadovaná verze Pythonu,none
aany
říkají, že se jedná o balíček nezávislý na platformě).
Všimněte si, že wheel soubor není nic jiného než jednoduchý zip archiv.
$ file dist/matfyz_nswi177_fscat-0.0.1-py3-none-any.whl
dist/matfyz_nswi177_fscat-0.0.1-py3-none-any.whl: Zip archive data, at least v2.0 to extract, compression method=deflate
$ unzip -l dist/matfyz_nswi177_fscat-0.0.1-py3-none-any.whl
Archive: dist/matfyz_nswi177_fscat-0.0.1-py3-none-any.whl
Length Date Time Name
--------- ---------- ----- ----
51 2024-04-24 10:48 fscat/__init__.py
837 2024-04-24 10:48 fscat/fscat.py
777 2024-04-24 10:48 fscat/main.py
1075 2024-04-24 10:53 matfyz_nswi177_fscat-0.0.1.dist-info/LICENSE
1173 2024-04-24 10:53 matfyz_nswi177_fscat-0.0.1.dist-info/METADATA
92 2024-04-24 10:53 matfyz_nswi177_fscat-0.0.1.dist-info/WHEEL
42 2024-04-24 10:53 matfyz_nswi177_fscat-0.0.1.dist-info/entry_points.txt
6 2024-04-24 10:53 matfyz_nswi177_fscat-0.0.1.dist-info/top_level.txt
769 2024-04-24 10:53 matfyz_nswi177_fscat-0.0.1.dist-info/RECORD
--------- -------
4822 9 files
Možná vás zajímá, proč existují dva archivy s velmi podobným obsahem. Odpověď najdete v článku What Are Python Wheels and Why Should You Care?.
Nyní se můžete přepnout do jiného virtuálního prostředí a nainstalovat
balíček pomocí pip install
package.whl.
Publikování Pythoního balíčku
Pokud si myslíte, že by balíček mohl být užitečný i pro další lidi, můžete jej zveřejnit v Python Package Index. To se obvykle provádí pomocí nástroje twine. Přesný postup je popsán v části Uploading the distribution archives.
Vytváření distribučních balíčků (např. pro DNF)
I když se může zdát, že práce s vytvářením projektových souborů je velmi komplikovaná, ve skutečnosti z dlouhodobého hlediska šetří čas.
Prakticky každý vývojář Pythonu by nyní mohl nainstalovat váš program a mít jasný výchozí bod při zkoumání dalších detailů.
Všimněte si, že pokud jste instalovali nějaký program prostřednictvím DNF a
tento program byl napsán v jazyce Python, někde uvnitř byl soubor
setup.cfg
, který vypadal velmi podobně jako ten, který jste právě viděli.
Jen místo toho, aby se skript nainstaloval do vašeho virtuálního prostředí,
byl nainstalován globálně.
Žádné jiné kouzlo za tím opravdu není.
Všimněte si, že například Ranger je napsán v jazyce Python a tento
skript
popisuje jeho instalaci (jedná se o skript pro vytváření balíčků pro DNF).
Všimněte si, že %py3_install
je makro, které ve skutečnosti volá setup.py install
.
Nástroje vyšší úrovně
Na pip
a virtualenv
můžeme pohlížet jako na nízkoúrovňové nástroje.
Existují však i nástroje, které oba tyto nástroje kombinují a přinášejí
větší komfort při správě balíčků. V jazyce Python existují přinejmenším dvě
oblíbené možnosti, a to Poetry a
Pipenv.
Interně tyto nástroje používají pip
a venv
, takže stále můžete mít
nezávislé pracovní prostory a také možnost nainstalovat konkrétní balíček z
Python Package Index (PyPI).
Úplné představení těchto nástrojů je mimo rámec tohoto kurzu. Obecně se řídí stejnými principy, ale přidávají některé funkce navíc, které je hezké mít. Ve stručnosti lze uvést tyto hlavní rozdíly:
- Mohou zmrazit určité verze závislostí, takže se projekt sestaví na všech
počítačích stejně (pomocí souboru
poetry.lock
). - Balíčky lze odstranit spolu s jejich závislostmi.
- Inicializace nového projektu je jednodušší.
Ostatní jazyky
Ostatní jazyky mají vlastní nástroje s podobnými funkcemi:
Úlohy k ověření vašich znalostí
Očekáváme, že následující úlohy vyřešíte ještě před příchodem na cvičení, takže se budeme moci o vašich řešeních na cvičení pobavit.
Učební výstupy
Učební výstupy podávají zhuštěný souhrn základních konceptů a dovedností, které byste měli umět vysvětlit a/nebo použít po každém cvičení. Také obsahují absolutní minimum, které je potřebné pro pochopení navazujících cvičení (a dalších předmětů).
Znalosti konceptů
Znalost konceptů znamená, že rozumíte významu a kontextu daného tématu a jste schopni témata zasadit do většího rámce. Takže, jste schopni …
-
vysvětlit rozdíl mezi (normálním) přesměrování SSH portu a tzv. reverzním přesměrováním
-
vysvětlit, co jsou požadavky (závislosti na knihovnách)
-
vysvětlit základy sémantického verzování
-
vysvětlit, jaké jsou výhody a nevýhody instalace závislostí pro celý systém vs jejich instalaci ve virtuálním prostředí
-
poskytnout vysokoúrovňový přehled virtuálních prostředí
-
vysvětlit výhod a nevýhody uvedení tranzitivních závislostí ve srovnání s uvedením jen přímých
-
vysvětlit výhody a nevýhody uvedení přesné verze nebo jen minimální
Praktické dovednosti
Praktické dovednosti se obvykle týkají použití daných programů pro vyřešení různých úloh. Takže, dokážete …
-
používat
xargs
-
použít
find
se základními predikáty (-name
,-type
) a akcemi (-exec
,-delete
) -
použít přesměrování portu SSH pro přístup ke službě dostupné na zařízení loopback
-
použít reverzní předávání portů SSH pro připojení k počítači za NAT
-
vytvořit nové virtuální prostředí (pro Python) pomocí
python3 -m venv
-
aktivovat a deaktivovat virtuální prostředí
-
nainstalovat závislosti projektu do virtuálního prostředí pomocí
pip
-
vyvíjet program uvnitř virtuálního prostředí (s projektem používajícím
setup.cfg
apyproject.toml
) -
nainstalovat projekt, který používá
setup.cfg
-
volitelné: připravit projekt Pythonu pro instalaci