# Roury na Linuxu, ve Windows a v PowerShellu: co opravdu funguje, co je mýtus a kde lidé chybují

## Začněme tím nejčastějším nedorozuměním

Tvrzení, že Windows mají na rozdíl od Unixu jen parodii na roury, která interně funguje přes dočasné soubory, se na internetu opakuje pravidelně. Bývá to formulováno samozřejmě, jako známá pravda, a kdo by si chtěl ověřit detaily. Jenže to není pravda už pětadvacet let, a stojí za to si projít, kde ten mýtus vznikl a co ho udržuje při životě, protože pochopení toho zároveň vysvětluje, jak vlastně roury na Windows opravdu fungují.

Odkud se ten mýtus vzal? Z DOS éry. Tehdejší `command.com` skutečně nic jiného nemohl, protože DOS byl jednouživatelský a single-tasking systém. Když jste napsali `dir | sort`, COMMAND.COM nejdřív spustil `dir`, jeho výstup uložil do dočasného souboru, a teprve potom spustil `sort`, který četl z toho souboru. Šlo to udělat jinak, když nemůžete mít dva procesy běžící současně? Ne, nešlo. A tahle realita se vryla do paměti jedné generace uživatelů tak hluboko, že se traduje jako fakt o Windows dodnes.

Přitom Windows NT 3.1 z roku 1993 už měl skutečné anonymní kernelové roury přes Win32 API `CreatePipe()`. Tahle vlastnost zmizela z spotřebitelských Windows s přechodem na NT-jádro v XP (2001) — od té doby spotřebitelský Windows má kernel, který je v této otázce strukturálně rovnocenný Unixu. Mýtus tedy popisuje systém, který Microsoft pohřbil před čtvrt stoletím.

Když dnes v cmd.exe napíšete třeba `tar cvf - adresar | gzip > archiv.tgz`, shell zavolá `CreatePipe()` a dostane pár handles — read end a write end roury — a kernel mezi nimi spravuje ring buffer, podobně jako Linux. Oba procesy běží paralelně, blokující čtení a zápis fungují, EOF se propaguje. Funkčně izomorfní s Unix pipes, jen s jiným API pod kapotou. Žádný dočasný soubor v dohledu.

## Mýtus má ale občas pravdivé jádro někde jinde

Nepravdy se nešíří úplně náhodně. Lidé něco pozorují a vyvozují z toho špatné závěry. Stojí za to se podívat, co skutečně může vést k pocitu "pipeline ve Windows se chová divně", protože některé pocitové symptomy se podobají tomu, jak by se taková implementace přes dočasné soubory chovala.

První věc je **materializace v některých cmdletech PowerShellu** (k tomu se dostaneme podrobněji, až rozebereme rozdíl mezi pipeline ve Windows a pipeline v PowerShellu). Některé cmdlety jako `Sort-Object`, `Group-Object` nebo `Measure-Object` si musí počkat na celý vstup, než vůbec můžou začít psát výstup. To není vlastnost PowerShell pipeline, ale **inherentní fyzika** té operace — Unix `sort` to dělá taky, prostě musí. Rozdíl je v tom, že GNU `sort` má léta external merge sort se spillem na disk (přes flag `-T`), kdežto PowerShell `Sort-Object` drží všechno v managed paměti a při překročení limitu spadne. Pro uživatele velkých datasetů to může vypadat jako "pipeline blokuje a něco si někam ukládá", což je nebezpečně blízko představě "jde to přes dočasné soubory".

Druhá věc je **cena spuštění procesu na Windows**. CreateProcess je řádově dražší než Unix `fork+exec` — desítky milisekund místo jednotek. Pro úlohy, kde rourujete deset malých příkazů a každý z nich startuje nový proces, je to znatelné. Pro velké soubory to nevadí, protože dominuje samotná práce. Bolí to hlavně krátké příkazy v dávkách, což je idiom typický pro Unix shell. K tomu se ještě podrobněji vrátíme, protože je za tím zajímavé designové rozhodnutí celé NT architektury.

A třetí věc je **kódování textu na hranicích procesů**. Cmd.exe a externí utility se často neshodují na code page, takže rourovaný text se občas překóduje na nesmysl. To není problém roury jako takové, je to problém textových konvencí ve Windows ekosystému — code pages, OEM vs. ANSI, UTF-8 BOM/no-BOM. Ale pro uživatele to vypadá, že "pipeline kazí data", což opět posiluje pocit, že je s ní něco fundamentálně špatně.

Když si tohle dáte dohromady, můžete uvidět, jak vznikla představa, že pipeline ve Windows je nějaká divná, možná dokonce přes soubory. Ale technicky vzato je to úplně mimo — pipeline samotná je kernel mechanismus stejně jako na Linuxu.

## Genealogie: odkud Windows roury skutečně mají

Tahle otázka je zajímavější, než vypadá, protože odpověď je hybridní a obě strany — Unix tábor i Windows tábor — mají tendenci ji zjednodušit ve svůj prospěch.

Stručně řečeno: **procesní model, kernel architektura, I/O subsystém, asynchronní IO, security model, scheduler** — to vše je primárně z VMS. Ale **stdin/stdout/stderr konvence, koncept pipe jako anonymního byte streamu, filozofie "shell skládá malé nástroje"** — to vše je z Unixu. Roura na Windows je tedy unixová idea implementovaná nad VMS-rodokmenem kernelu. Hybrid, ne čistá inspirace ani z jedné strany.

Pozadí stojí za to znát. Windows NT navrhoval Dave Cutler, který přišel v 1988 z DEC. Tam předtím vedl tým, který napsal VMS, a později projekt MICA (zrušený OS pro DEC PRISM RISC architekturu). Když odešel do Microsoftu se skupinou kolegů z DEC, dostal mandát postavit nový operační systém. A on logicky postavil systém, jaký uměl postavit — strukturálně velmi blízký VMS. Hierarchický Object Manager s namespace a handle table per proces, I/O Manager s IRP packets a driver stack, asynchronní IO přes overlapped operations a I/O completion ports, priority-based preemptive scheduler s 32 prioritami, memory-mapped files jako primární mechanismus sdílení, security model s ACL a SID a tokeny, registry jako evoluce VMS logical names a SYSGEN parameters. Existuje slavná historka, že písmena VMS posunutá o jedna v abecedě dají WNT — Cutler to popírá, ale technicky je NT skutečně víc VMS než Unix na úrovni kernel architektury.

A teď ten zajímavý kontrast: **VMS prakticky neměl roury**. Měl `SYS$INPUT`, `SYS$OUTPUT`, `SYS$ERROR` jako logická jména s přesměrováním, ale shell pipes ve smyslu `cmd1 | cmd2` v DCL prostě nebyly first-class. Šlo to obejít přes mailboxy (VMS IPC primitivum) nebo dočasné soubory, ale nebyla to běžná, idiomatická věc. VMS kultura byla jiná — programy byly typicky větší, monolitické, s vlastními rich command interfaces. Filozofie "malé nástroje skládané přes textové roury" je čistě Unix, McIlroy a Ritchie 1972-73.

Takže když Microsoft přidával na NT podporu pro POSIX subsystem (jeden z původních NT subsystémů vedle Win32 a OS/2, požadovaný pro některé americké vládní kontrakty), museli implementovat klasické Unix volání včetně `pipe()`, fork-like semantiky, signal handling. Win32 subsystem získal vlastní API pro totéž — `CreatePipe()`, `CreateProcess()` — dělající totéž s Microsoft-style verbose handle-based API a explicit security descriptors. Implementačně to leží nad **Named Pipe File System driver** (`npfs.sys`) v NT kernelu. Anonymní roury v NT jsou ve skutečnosti pojmenované roury s automaticky generovaným unikátním jménem, které není v namespace exposed. Pipe driver implementuje ring buffer, blokující čtení/zápis, EOF propagaci — všechno, co Unix pipe dělá, jen organizované jinak.

Pro úplnost ještě ke shellům: cmd.exe je přímý potomek OS/2 cmd.exe (Microsoft s IBM společně vyvíjeli OS/2 v 80. letech), který byl náhradou DOS command.com s plnohodnotnými pipes, protože OS/2 měl multitasking. Když se cesty MS a IBM rozešly v 1990, Microsoft vzal OS/2 cmd.exe a portoval ho na NT, kde dodnes přežívá skoro nezměněn. Syntaxe `|` v cmd.exe je tedy zděděná z OS/2, který tu myšlenku převzal z Unixu. VMS DCL ovlivnil cmd.exe minimálně — některé drobnosti jako `/option` formát voleb možná, ale to jde spíš z CP/M a starších DEC OS jako RT-11. Pipe syntaxe `|` rozhodně z DCL nepochází, DCL ji neměl.

## Roury na Linuxu a ve Windows: jak fungují a co garantují

Když srovnáváme roury v Linuxu a ve Windows na úrovni operačního systému, srovnáváme dva mechanismy, které jsou strukturálně velmi podobné, jen s jinými API. Pojďme si projít, co každý z nich poskytuje a kde se liší.

**Unix pipe** vytváří `pipe(2)` syscall, který alokuje pár file descriptorů — read end a write end. Kernel mezi nimi spravuje ring buffer (typicky 64 KiB na Linuxu, konfigurovatelný). Když chcete pipeline mezi dvěma procesy, shell zavolá `pipe(2)`, pak `fork(2)` pro každý ze dvou stupňů, a v každém forknutém child procesu zavolá `dup2(2)` pro přemapování read nebo write endu na file descriptor 0 nebo 1, načež `execve(2)` nahradí proces obsahem cílového programu. Cílový program dostane stdin a stdout napojené na rouru a o ničem dalším nemusí vědět.

**Windows pipe** vytváří `CreatePipe()` Win32 API, které alokuje pár handles — read end a write end anonymní roury. Pro pipeline mezi dvěma procesy shell zavolá `CreatePipe()`, pak `CreateProcess()` pro každý stupeň, a v `STARTUPINFO` struktuře předá příslušné handles jako `hStdInput` nebo `hStdOutput`. Child proces zdědí ty handles jako svůj stdin nebo stdout. Cílový program dostane stdin a stdout napojené na rouru a o ničem dalším nemusí vědět.

Tahle dvě API jsou izomorfní v tom, co dělají — výsledek je z pohledu programu identický. Hlavní rozdíly jsou:

Nejprve **default-safe inheritance handles na Windows**. Když na Windows zavoláte CreateProcess s `bInheritHandles=TRUE`, dědí se jen ty handles, které jste explicitně označili jako dědičné při jejich vzniku (`HANDLE_FLAG_INHERIT`). Default je nesdílet. Unix má naopak default "vše se dědí" a nastavovat `FD_CLOEXEC` musíte explicitně na každém file descriptoru, který nechcete sdílet. Unix přístup je default-unsafe a vede k chronickým file descriptor leaks (typický scénář: webový server, který forkne child workera, který si někde otevřel TCP socket, child workera nahradí jiný proces, který socket nepoužívá, ale stále drží a brání ho zavřít z původního procesu). Tohle je opravdový bezpečnostní rozdíl, který se manifestuje v reálných CVE.

Pak **cena spuštění procesu**. Unix `fork+exec` je řádově rychlejší než Windows CreateProcess — jednotky milisekund versus desítky milisekund. To má zásadní vliv na shell scripting idiom: v Unixu je naprosto běžné rourovat deset malých nástrojů, protože každý spawn je v podstatě zdarma. Na Windows ten samý idiom bolí, jednotlivá spawn cena se sčítá a viditelně zpomaluje skripty s mnoha krátkými příkazy. Pro velké soubory to nevadí (dominuje práce, ne startup), ale pro klasický Unix shell idiom s mnoha krátkými utilitami je to měřitelný handicap NT.

Tahle pomalost CreateProcess není ovšem náhodná chyba designu — je to **vědomý kompromis**, který obětoval levné spawning ve prospěch jiných vlastností. Cutler vědomě zvolil `CreateProcess` s plnou `STARTUPINFO` strukturou popisující přesně jaké prostředí má child mít. Žádné "zděď všechno a pak to v childovi opraskej", které Unix `fork()` model vyžaduje. To má důsledky: Unix `fork()` je sice levný pro malé procesy, ale má **velmi problematické vlastnosti**, které Unix tradičně toleruje. Copy-on-write iluze přestává být zadarmo pro velké procesy, protože sice fyzická paměť se nekopíruje, ale page table entries ano — pro velký proces (databáze, runtime s velkým heapem) `fork()` trvá stovky milisekund a způsobuje measurable latency spikes. Notoricky známý problém u Redis, PostgreSQL, Java aplikací. Po `fork()` má child kompletní kopii všeho včetně potenciálně držených mutexů ve stavu, který v childovi nikdy nikdo neuvolní, protože druhá vlákna v childovi neexistují. POSIX má dlouhý seznam "async-signal-safe functions" pro úzký koridor mezi `fork()` a `execve()` a programátoři ho rutinně porušují.

A NT dostal několik dalších věcí, které dnes Linux postupně přebírá. **Job Objects** jsou primitivum pro přiřazení procesů do skupin s kvótami a politikami (CPU limit, memory limit, počet procesů, network limity, UI restrictions, kill-on-close). Linux cgroups z 2007 jsou pozdější analogie. Windows containers jsou postavené přímo nad Job Objects. **I/O Completion Ports** poskytují completion model pro async IO (kernel říká "už jsem za tebe přečetl") oproti readiness modelu Linux epoll ("můžeš číst"). Completion model lépe škáluje, protože každá operace způsobí jen jedno přepnutí kontextu, ne dvě. Linux io_uring z 2019 konečně přinesl completion model na Linux, do té doby NT držel náskok 25 let. **Threads jsou levné a first-class od první verze NT.** Když je CreateProcess drahý, NT řeší concurrency tak, že **udělá levný thread** — `CreateThread` je řádově rychlejší než `CreateProcess`. Unix kultura má "fork lots of processes" jako idiom; NT kultura má "spawn lots of threads on thread pool". **Symetrický multiprocessing** od NT 3.1 v 1993 na 32 CPU. Linux dostal SMP support postupně a finální Big Kernel Lock byl odstraněn až okolo Linuxu 2.6 (2003).

A pak je tu ten fascinující pattern, kdy moderní Linux postupně přebírá NT-style přístupy. cgroups připomínají Job Objects. io_uring připomíná IOCP. namespaces a kontejnery jsou postavené nad cgroups a namespaces tak, jako Windows containers stojí nad Job Objects. seccomp a capabilities konvergují k granular security tokens v NT. PREEMPT_RT real-time preemption mainline v 2024 dohání NT preemptive kernel z 1993. systemd v 2010+ napodobuje SCM z NT 3.1. Wayland nahrazuje X11 architekturou, kde compositor a display server jsou jeden proces — což Windows DWM dělá od Visty 2006, a Windows fundamentálně nikdy neměl X11-style problém. Tohle není argument "Linux jen kopíruje Windows" — je to pozorování, že **architektura NT byla v mnoha ohledech přepokvalifikovaná svou dobou**, protože Cutler stavěl OS pro server scenarios v době, kdy Unix byl pořád primárně workstation/timesharing OS. Když Linux v 2000s a 2010s začal vážně cílit na server a později hyperscale data centra, narazil na stejné problémy, které NT řešil v 90. letech, a postupně přicházel se strukturálně podobnými řešeními. Není to konspirace ani imitace, je to **konvergence** — pro server workloady jsou tyto přístupy objektivně lepší a nezávislé týmy k nim přicházejí nezávisle.

Pokud vás zajímá, proč tahle konvergence není v tech komunitě více vidět, je to kombinace faktorů. Open source vs. proprietary visibility — o každém Linux feature se diskutuje na LKML, blozích, konferencích, vznik io_uring má detailní paper trail. Vznik IOCP v 1994 má interní memo někde v Redmondu. Generational shift — lidé píšící o operačních systémech dnes vyrostli v éře, kdy Linux byl ten zajímavý OS a Windows byl ten nudný corporate OS, takže NT je pro ně black box, kterou není kulturně přitažlivé studovat. Microsoft 90. let toxic legacy — embrace-extend-extinguish, monopolismus — vytvořil hluboké asociace, které dnes matou, i když situace je radikálně jiná. A pragmatický fakt, že většina hyperscale cloud workloads běží na Linuxu, takže developer mental model je Linux-centric.

## Jak je možné, že libovolný program v rouře funguje bez úprav

Předtím, než se podíváme na specifický případ PowerShellu, stojí za to si projít, jak je vůbec možné, že tisíce CLI nástrojů napříč jazyky a érami jsou automaticky kompozovatelné. V tomhle je hluboká elegance, kterou je dobré ocenit.

Když napíšete v C jednoduchý program, který čte stdin a píše stdout — `while ((c = getchar()) != EOF) putchar(c)` — a zkompilujete ho, **funguje v jakékoli rouře na jakékoli platformě**. Bez detekce "běžím v rouře?", bez volání speciálního pipe API, bez čehokoli specifického. Stejně by to fungovalo v jakémkoli jiném jazyce, který umí číst stdin a psát stdout. Proč to tak je?

Klíč je v tom, že když program startuje, dostane od OS tři standardní handles — stdin, stdout, stderr. Z perspektivy programu jsou to **obyčejné file handles**, stejné jako kdyby si otevřel soubor přes `fopen()`. Program neví a nemůže vědět, co se za nimi skrývá. Mohou ukazovat na konzoli (terminál), soubor (`> output.txt`), konec roury (`| dalsi-program`), na `NUL`/`/dev/null`, na pojmenovanou rouru, nebo třeba na síťový socket. Program prostě volá `ReadFile()` na handle 0 a `WriteFile()` na handle 1 — všechno se to nakonec přeloží na ten samý syscall, ať už píšete v C, Pythonu, C#, Go, čemkoli. A co se za těmi handles děje, řeší kernel a shell, ne program.

Shell — tedy cmd.exe, PowerShell, bash, zsh — je ten, kdo před spuštěním programu rozhodne, na co budou handles ukazovat. Pro `tar | gzip > archiv.tgz` v cmd.exe to znamená, že shell vytvoří rouru přes `CreatePipe()`, otevře soubor archivu přes `CreateFile()`, a v `STARTUPINFO` strukturách předá ty handles při `CreateProcess()` jako stdin/stdout dětských procesů. Tar nemusí vědět, že jeho stdout je roura. Gzip nemusí vědět, že jeho stdin je roura a stdout je soubor. **Pipeline je plně transparentní pro program.**

Cmd a PowerShell (a samozřejmě bash a další Unix shelly) garantují čtyři věci. Za prvé, stdin/stdout/stderr jsou nastaveny správně podle pipeline a redirekcí — programy dostanou validní handles. Za druhé, EOF se propaguje — když upstream skončí a zavře write end, downstream při čtení dostane nulu bajtů a klasický `while (getchar() != EOF)` cyklus skončí. Za třetí, blokující IO funguje — když downstream čte rychleji než upstream píše, čtení čeká; když upstream píše rychleji než downstream čte, zápis čeká po naplnění kernel bufferu. Backpressure tedy funguje automaticky, programy se o ni nestarají. A za čtvrté, procesy běží paralelně — shell je všechny spustí najednou, ne sekvenčně.

Tohle je celý kontrakt mezi shellem a programem. Cokoli za rámec tohohle je už věc programu, ne shellu.

Existují tři případy, kdy program přece jen potřebuje něco dělat speciálně, a stojí za to o nich vědět, protože matou. První je **binární mód stdin/stdout na Windows v C**. C runtime na Windows má defaultně textový mód, který při zápisu překládá `\n` na `\r\n` a obráceně při čtení. Pro binární data je to fatální — `\n` v archivu se rozbije. Řešení je dvouřádkové: `_setmode(_fileno(stdin), _O_BINARY)` a totéž pro stdout, někde na začátku programu, pod `#ifdef _WIN32`. Tar a gzip z BSD/GNU světa to mají dávno správně. Vyšší jazyky to řeší různě — Python má `sys.stdout.buffer.write(bytes)`, který obchází text mode úplně, C# má `Console.OpenStandardOutput()` pro raw byte stream, Go a Rust jsou binární defaultně.

Druhá věc je **buffering stdout**. C runtime přepíná mezi line-buffered (flush po každém `\n`) když je stdout terminál, a block-buffered (typicky 4 KB) když je stdout cokoli jiného včetně roury. Toto se programu děje automaticky, autor o tom typicky neví. Ale má to viditelný důsledek: program, který interaktivně píše po řádcích, vypadá v rouře jako kdyby visel — výstup je v 4 KB bufferu a downstream nic nevidí, dokud se buffer nenaplní nebo program neskončí. Toto je nejčastější skutečný problém s rourami a řeší se buď úpravou programu (`setvbuf(stdout, NULL, _IOLBF, 0)` v C, `python -u` pro Python), nebo wrapper utility (`stdbuf -oL program` na Linuxu, na Windows out of the box ekvivalent není). Pro batch programy jako tar, grep, sort to nevadí — píší velké objemy a buffer naplní rychle. Pro interaktivní nástroje a log-tailing je to relevantní.

Třetí věc je **detekce terminálu vs. roury přes `isatty()`**. To **není** o funkčnosti, je to o UX. `ls` na Linuxu defaultně koloruje výstup do terminálu, ale do roury kolorování vypne — aby nepošpinilo data pro další stupeň. `git diff` defaultně použije pager když výstup jde do terminálu, ale ne když do roury. `grep --color=auto` podobně. Programy, které `isatty()` nevolají, prostě dělají to samé v obou případech. Roura "funguje" i bez podpory `isatty()`, jen někdy nevypadá tak hezky.

## A teď k PowerShellu, který je úplně jiný příběh

Tady přichází bod, který je důležité jasně vyznačit: **PowerShell není operační systém, ani součást operačního systému**. PowerShell je **programovací jazyk a runtime**, který běží jako uživatelský proces nad Windows (nebo Linux, nebo macOS — od PowerShell 7 je cross-platform). Jeho "pipeline" tedy není mechanismem operačního systému ve stejném smyslu, jako jsou Unix pipe nebo cmd.exe pipe. Je to **konstrukt v programovacím jazyce**, který runtime překládá na sekvenci volání metod uvnitř jediného procesu.

Tenhle rozdíl je zásadní a stojí za to si ho podržet, protože diskuse o PowerShell pipeline často svádí na scestí lidi, kteří si pod slovem "pipeline" představují to, co znají z Linuxu — kernelový mechanismus pro tok bajtů mezi procesy. PowerShell pipeline je něco kategoriálně jiného.

Z pohledu Windows kernelu PowerShell nedělá nic zvláštního. Je to obyčejný uživatelský proces hostující CLR (Common Language Runtime, .NET runtime). Žádný syscall pro "vytvoř pipeline", žádný kernel object pro "pipeline stage", žádný IPC mechanismus pro tok objektů mezi cmdlety. Celá pipeline infrastruktura — parser, AST, `PipelineProcessor` orchestrující volání `BeginProcessing`/`ProcessRecord`/`EndProcessing`, parameter binding machinery se všemi pravidly, šest output streamů jako oddělené kolekce, `ErrorRecord` propagace, `$_` binding ve script blocích — to všechno je managed C# kód v `System.Management.Automation.dll`. Open source, kdokoli si může přečíst.

Když Microsoft zítra odstraní .NET, PowerShell pipeline nepřestane existovat — jen by se runtime musel přeložit jinam. PowerShell 7 na Linuxu a macOS funguje úplně stejně jako na Windows, právě proto, že pipeline nezávisí na OS supportu. Naproti tomu, kdyby Linus z Linux kernelu zítra odstranil `pipe(2)` syscall, Unix shell pipelines přestanou existovat — jsou na něm fundamentálně závislé.

Snover (architekt PowerShellu) si tohle architektonické rozhodnutí vědomě udělal kolem 2002, jak popisuje v Monad Manifesto. Vzal McIlroyův koncept staged transformation a implementoval ho jako jazykovou konstrukci nad typed objekty. Důvod nebyl, že by Windows neměl kernel pipe — měl ji od 1993 (mýtus jsme vyvrátili). Důvod byl, že **kernel pipe nemůže předávat objekty referenčně bez serializace na bajty**. Pro objektovou pipeline, kterou chtěl, by kernel pipe nestačila. Bohaté runtime prostředí (CLR) dovolilo implementovat v user space věc, která by jinak musela být v kernelu nebo by neexistovala vůbec. Tenhle pattern není unikátní pro PowerShell — Erlang/OTP message passing je další příklad, Go goroutines a channels podobně.

Takže shrnujme: PowerShell pipeline existuje na úplně jiné vrstvě abstrakce než Unix pipe nebo cmd.exe pipe. Že obě věci používají symbol `|` a slovo "pipeline" je víc historická náhoda (a vědomá pocta McIlroyovi ze strany Snovera) než hluboká strukturální podobnost. Srovnávat je přímo je trochu jako srovnávat TCP sockety s function composition v Haskellu — oba "spojují kus A s kusem B a předávají data", ale na tak rozdílných vrstvách, že přímé srovnání zkresluje.

## Co PowerShell pipeline poskytuje a v čem se liší od kernel rour

Když máme tenhle rámec, můžeme se podívat, co PowerShell pipeline jako mechanismus konkrétně poskytuje a kde se liší od kernel rour, které jsme rozebírali.

Unix pipe posílá **bajty**. PowerShell pipeline posílá **.NET objekty**. To je fundamentální rozdíl, který určuje skoro všechno ostatní.

V Unixu se každý stupeň pipeline musí dohodnout na textové konvenci — kde jsou hranice záznamů (typicky newline), kde jsou pole (mezera, tabulátor, čárka), jak se reprezentují speciální znaky. Když chcete prošlapat seznam souborů a pracovat s jejich velikostmi, musíte velikost vytahnout z textového výstupu (typicky přes `awk '{print $5}'` nebo podobné) a pak ji parsovat zpět na číslo. Když chcete pracovat s časy souborů, řešíte formátování data. Když chcete pracovat se soubory, jejichž jména obsahují mezery nebo newline, máte problém. Celý awk/sed/cut ekosystém existuje, aby tyhle problémy řešil.

V PowerShellu objekt projde celou pipeline jako celek. `Get-ChildItem` vrací `FileInfo` objekty, které mají `Length` jako `Int64`, `LastWriteTime` jako `DateTime`, `Name` jako `String`. Když napíšete `Get-ChildItem | Where-Object Length -gt 1MB | Sort-Object LastWriteTime`, žádné parsování textu, žádný regex, žádný problém s mezerami v názvech. Typová bezpečnost.

K tomu pipeline v PowerShellu zachovává objekty **bez kopírování** (zero-copy). Reference se předává mezi stupni, ne data. `Get-ChildItem | Where-Object {...} | Sort-Object Length | ForEach-Object {...}` — ten samý `FileInfo` objekt projde všemi čtyřmi stupni, žádná serializace, žádná deserializace, žádná duplikace v paměti. Pipeline na milionu `FileInfo` objektů spotřebuje paměť na milion instancí, ne na milion krát počet stupňů. Výrazný rozdíl proti Unix pipeline, kde každý stupeň serializuje na text a parsuje zpět.

Ovšem tahle elegance má své hranice. **V momentě, kdy PowerShell pipeline překračuje hranici k externímu programu (.exe)**, objekty se musí degradovat na strings přes `ToString()` — externí program objekty nezná, mluví bajty. Opačný směr je tentýž problém — výstup externího programu přichází jako text, který se v PowerShellu reprezentuje jako string. PowerShell uvnitř svého .NET ekosystému je elegantní; na hranici k ne-.NET světu se objeví impedanční nesoulad, který nelze obejít — kernel pipe a textová sériová reprezentace jsou jediné, co externí program rozumí.

K tomu má PowerShell několik dalších vlastností, které stojí za zmínku.

Šest output streamů běží paralelně — success, error, warning, verbose, debug, information. Chyby jsou first-class — můžete je filtrovat, přesměrovat, zachytit bez toho, aby narušily success channel. V Unixu chyba jde na stderr, který se s pipeline neúčastní, exit status pipeline je defaultně poslední stupeň (proto `set -o pipefail`).

Synchronní model pipeline — runtime volá `process` blok jednou na každý objekt přicházející z předchozího stupně. To je pull-based stream, generátorová koroutina. Kooperativní souběžnost mezi stupni bez locků, threads, context switches. Streamování a short-circuit zadarmo — `1..1000000 | Where {...} | Select -First 10` po nalezení desátého výsledku přestane volat upstream, protože downstream prostě přestane táhnout. Deterministické pořadí, žádné race conditions.

Za tu eleganci platí PowerShell jednu cenu, kterou Unix pipeline neplatí: **CPU paralelismus napříč stupni není zdarma**. Pokud `Where-Object` a `Sort-Object` oba dělají těžkou výpočetní práci, neběží paralelně na různých jádrech — běží na jednom vlákně střídavě. V Unixu jsou stupně samostatné procesy, kernel je rozdělí mezi jádra zdarma. Podobně IO paralelismus — Unix `find | xargs` profituje z toho, že find pokračuje ve walku, zatímco xargs zpracovává. V PowerShellu `Get-ChildItem -Recurse | ForEach-Object {...}` je sériové.

Tohle se ale řeší přes explicit `-Parallel` na konkrétních cmdletech. `ForEach-Object -Parallel { ... } -ThrottleLimit 16` přidá fork/join paralelismus tam, kde má smysl — pro typický scénář "pomalá IO operace na každý objekt" (REST API volání, network ops) to je přesné řešení. Kombinace synchronní pipeline + paralelní stupně dává nejlepší z obou světů: pull-based streaming dole, paralelní execution nahoře.

Pozoruhodné je, že tahle volba "sériový default, paralelismus explicit" **není odchylka, ale aplikace dlouhodobého konsensu** napříč moderním programováním. Erlang/BEAM VM s lehkými procesy a message passing — etalon spolehlivosti pro telecom switche od 80. let, dnes WhatsApp servery, RabbitMQ, Discord. Go goroutines z 2009 — `go func() { ... }()` jako explicit opt-in, celý cloud infrastructure stack (Kubernetes, Docker, Prometheus) postavený nad tím. Kotlin coroutines, Python async/await, JavaScript async/await, Rust async, C# async/await, Swift structured concurrency — všechno opt-in async modely. Implicitní paralelismus v Unix shell pipeline (kde každý `|` automaticky znamená paralelní proces) je historická anomálie z doby, kdy fork+exec byl jediný způsob isolace, ne designové vítězství. Vědomé "tady chci paralelismus, tady ne" je správný design pro 90 % případů.

Pozor ovšem na nadinterpretaci téhle analogie: PowerShell runspaces **nejsou tak lehké** jako BEAM procesy nebo Go goroutines. BEAM proces je mikrosekundová záležitost, kilobytes paměti, miliony souběžných procesů na jednom VM. PowerShell runspace je desítky až stovky ms inicializace, izolovaný .NET execution context. PowerShell může mít efektivně tisíce souběžných runspaces, ne miliony. Patří do správné rodiny designu, ale není jejím nejlepším členem co do váhy primitiv. Pro praktické použití v admin workflow to nevadí — typický scénář je desítky až stovky souběžných operací —, ale je dobré rozumět, kde ta analogie končí.

## Konkrétní příklad: tar a gzip ve třech různých shellech

Pojďme si na konkrétním příkazu ukázat, jak různé to může vypadat. Vezměme `tar cvf - adresar | gzip > archiv.tgz`. (Mimochodem, ten konkrétní příkaz je trochu redundantní — `tar -z` už interně volá gzip, takže `tar czvf - | gzip` komprimuje dvakrát a výsledek je vlastně `.tgz.gz`. Pravděpodobně chcete buď `tar cvf - adresar | gzip > archiv.tgz` (tar bez komprese, gzip zvlášť), nebo `tar czvf archiv.tgz adresar` (tar s vestavěnou kompresí). Předpokládejme tu první variantu.)

Nejdřív k samotnému tar a gzip na Windows. Tar je od Windows 10 build 17063 (2017) **součástí systému** — najdete `tar.exe` v `C:\Windows\System32\`. Je to BSD libarchive, umí `-z` (gzip), `-j` (bzip2), `-J` (xz), `--zstd`. Gzip v systému není, ale dostanete ho z Git for Windows, MSYS2, Scoop nebo Chocolatey. Předpokládejme, že máte oba.

**V bashi na Linuxu** to funguje, jak byste čekali. Bash zavolá `pipe(2)`, dostane pár fd. Zavolá `fork(2)` pro tar, ve forknutém child procesu udělá `dup2(write_end_fd, 1)`, načež `execve("tar", ...)`. Stejně pro gzip s read endem na stdin a souborem otevřeným přes `open()` na stdout. Oba procesy běží paralelně, kernel mezi nimi spravuje ring buffer (64 KiB), backpressure funguje, EOF se propaguje při ukončení taru. Žádné překvapení.

**V cmd.exe na Windows** to funguje doslova stejně, jen s jinými API. Cmd.exe parsuje řádek, rozpozná pipe operator, zavolá `CreatePipe()` a dostane pár handles. Pak zavolá `CreateProcess()` pro `tar.exe` a v `STARTUPINFO.hStdOutput` mu předá write end roury. Tar tu rouru zdědí jako svůj stdout, ale **vůbec o tom neví** — vidí jen "můj stdout, na který píšu přes WriteFile()". Pak cmd.exe analogicky spustí `gzip.exe` s read endem roury jako stdin a s file handlem otevřeným přes `CreateFile("archiv.tgz", ...)` jako stdout. Oba procesy běží paralelně, kernel jim mezi nimi spravuje ring buffer, blokující čtení/zápis funguje, EOF se propaguje. Mechanismus je izomorfní s bashem.

**V PowerShellu** je to o trochu komplikovanější a stojí za to vědět proč. PowerShell vidí dva externí programy — ne cmdlety. Pro externí programy PowerShell **nepoužívá** svou objektovou pipeline (která je in-process). Místo toho vytvoří proces-úrovňové roury přes vrstvu .NET — `System.Diagnostics.Process` s `RedirectStandardOutput` a `RedirectStandardInput`. PowerShell spustí tar s přesměrovaným stdout, spustí gzip s přesměrovaným stdin a stdout, a potom **kopíruje bajty přes managed kód** ze stdout taru do stdin gzipu. Funkčně to vypadá stejně (paralelní běh, žádné dočasné soubory, streamování), ale je to mírně dražší — každý bajt projde managed kódem dvakrát.

A tady přichází ta past, do které lidé pravidelně padají: **kódování**. Tar produkuje binární data na stdout. Starší PowerShell, konkrétně 5.1 a starší, **interpretuje stdout externího programu jako text**. Bajty se převedou na .NET stringy podle `[Console]::OutputEncoding`, předají se gzipu přes textový stream, který je zase enkóduje zpět na bajty — a tahle round-trip přes string **rozbije binární data**, protože ne každá sekvence bajtů je validní v dané code page. Výsledný `.tgz` je poškozený a nedá se rozbalit.

Naštěstí to dnes nemusí být problém. PowerShell 7.5+ má feature `PSNativeCommandPreserveBytePipe` jako default, který detekuje "tady tečou bajty mezi externími programy" a obejde string konverzi — propojí stdout a stdin přímo na úrovni file handles. PowerShell 7.0 až 7.4 to umí, ale musíte si zapnout `$PSNativeCommandPreserveBytePipe = $true`. PowerShell 5.1 to nemá vůbec a binární pipe v něm prostě nepoužívejte; pokud na to narazíte, obejděte to přes `cmd /c "tar cvf - adresar | gzip > archiv.tgz"`, který tu degradaci nedělá.

To je důležitá nuance: **encoding bugs v PowerShellu se týkají primárně PowerShellu 5.1 a hranice k externím programům**. Uvnitř čistě cmdletové pipeline mezi PowerShell cmdlety encoding nehraje roli — objekty se předávají v paměti, žádná textová serializace. Když někdo říká "PowerShell pipeline má encoding bugs", pravděpodobně myslí 5.1 a scénář s externími programy. V PowerShell 7+ s in-process cmdlety na to nenarazíte.

## Co je vlastně PowerShell, abychom byli důkladní

Když to všechno dáme dohromady, pojďme rovnou odpovědět na otázku, co PowerShell je. Není to jen shell, ani jen jazyk, ani jen runtime — je to všechno tři najednou v jednom balení.

První vrstva je **interaktivní shell**. PowerShell má REPL (`pwsh` spuštěný bez argumentů), historii, tab completion, aliasy (`ls` → `Get-ChildItem`, `cd` → `Set-Location`, `cat` → `Get-Content`), prompt customization, podporu pro psaní krátkých ad-hoc příkazů. V tomhle režimu konkuruje bashi, zshi, fishi.

Druhá vrstva je **skriptovací jazyk**. Soubory `.ps1` s plnohodnotnou syntaxí — proměnné s typovou anotací, control flow, funkce s parametr bloky, try/catch/finally, classes (od PowerShell 5), moduly (`.psm1`), advanced functions s `[CmdletBinding()]`, které dostanou zdarma `-Verbose`, `-Debug`, `-ErrorAction`, `-WhatIf`, `-Confirm`. Tohle už není shell scripting, tohle je plnohodnotný jazyk srovnatelný s Pythonem nebo Ruby co do expresivity.

Třetí vrstva je **hostovací prostředí pro .NET**. PowerShell je v podstatě interaktivní REPL nad celým .NET runtime. Můžete přímo psát `[System.Net.WebClient]::new().DownloadString('...')`, `Add-Type -AssemblyName System.Windows.Forms`, cokoli. Veškerá .NET base class library je dostupná, lze instancovat libovolné typy, volat statické metody, načítat externí assembly, kompilovat C# kód za běhu přes `Add-Type`.

Když Snover navrhoval Monad (původní kódové jméno PowerShellu) kolem 2002, vědomě si stanovil cíl, který tehdy nikdo jiný neměl: **jeden nástroj plynule přecházející od jednořádkovky po REPLu k produkčnímu skriptu k frameworku pro správu enterprise infrastruktury**. Bash je dobrý jako shell, ubohý jako skriptovací jazyk a žádný jako aplikační framework. Python je dobrý jako skriptovací jazyk, slušný v REPL, špatný jako interaktivní shell. PowerShell to spojuje.

Stojí ještě za zmínku rozdíl mezi verzemi: **PowerShell 5.1** je výchozí verze na Windows 10 i 11 (`powershell.exe`), Microsoft ji udržuje (security updates), ale aktivně nevyvíjí — poslední feature release byl asi 2016. **PowerShell 7.x** je separátní instalace (`pwsh.exe`), cross-platform (Windows, Linux, macOS), aktivně vyvíjená, open source. Není to upgrade 5.1, je to paralelní produkt. Pro nový development je správnou volbou 7+. Pro někoho, kdo dědí 5.1 codebase, je 5.1 realita, se kterou žije — a většina encoding bugs, o kterých jsme mluvili, je problém 5.1.

PowerShell objektový pipeline mechanismus je tedy **rozšíření**, které sedí nad operačním systémem, ne uvnitř něj. Když používáte PowerShell na Linuxu, váš operační systém má pořád Unix kernel pipe — PowerShell s ním ale moc nepracuje, používá svou vlastní in-process pipeline mezi cmdlety a sahá na kernel pipe jen na hranici k externím programům. Když používáte PowerShell na Windows, váš OS má NT kernel pipe, ale PowerShell ji opět používá jen na hranici k externím programům. Pipeline mezi cmdlety je v obou případech ten samý managed .NET kód.

## Pasti, do kterých lidé padají

Diskuse o rourách napříč platformami trpí systematickými blind spots, kde znalost z jedné platformy se projektuje na druhou bez zohlednění rozdílů. Pojďme si projít ty nejčastější, protože když je rozeznáte, můžete na ně poukázat, když je uvidíte v komentáři na Stack Overflow nebo Reddit.

### Past první: "Pipeline znamená totéž všude"

To je možná ta nejhlubší past, protože vzniká z toho, že stejný symbol `|` a stejné slovo "pipeline" se používají pro tři kategoriálně odlišné věci. Unix pipe je IPC primitivum na úrovni kernelu pro tok bajtů mezi procesy. Cmd.exe pipe je téhož druhu — kernel mechanismus, jen s jinými API. PowerShell pipeline je language construct uvnitř jediného procesu pro tok objektů, který s kernelem nemá nic společného. Tyhle tři věci řeší různé problémy, mají různé vlastnosti, různé limity. Když někdo říká "PowerShell pipeline je pomalá", obvykle myslí "pomalejší než Unix pipe pro stejný úkol", a to obvykle není fér srovnání, protože úkol není stejný — PowerShell pipeline řeší předávání typed objektů zero-copy, Unix pipe řeší tok bajtů mezi procesy. Dělá to každý dobře pro svůj domain.

### Past druhá: "Roury ve Windows jsou imitace, parodii nebo přes dočasné soubory"

Tohle je ten konkrétní mýtus, kterým jsme začínali. Cmd.exe roury jsou skutečné kernelové roury od 1993 přes `CreatePipe()`. Funkčně izomorfní s Unix pipes — stejné semantiky, stejný princip, jen jiné API. Mýtus o dočasných souborech pochází z DOS éry, který skončil před 25 lety. Když to někdo opakuje, opakuje informaci, která neplatí čtvrt století. Buď to ten člověk neaktualizoval od 1995, nebo to chytil od někoho, kdo ji neaktualizoval. Stojí za to ho v klidu upozornit, ne jako přehmat, ale jako historickou kuriozitu.

### Past třetí: "PowerShell pipeline je jen pomalá imitace Unix pipe"

PowerShell pipeline řeší úplně jiný problém. Předávání typed objektů zero-copy uvnitř jednoho procesu, s typovou bezpečností, bez serializace na text. Pro práci uvnitř .NET ekosystému je elegantnější než Unix textová pipe — žádný awk, žádný sed, žádné parsování, jen `Where`, `Select`, `Sort`, `Group` operující přímo nad strukturovanými daty. Pro komunikaci s ne-.NET světem ovšem ztrácí, protože musí degradovat na strings. Není to "imitace" Unix pipe, je to úplně jiný design pro jiný use case. Když to někdo srovnává tabulkově proti Unix pipe, jako by to byla konkurence ke stejné věci, dělá kategorickou chybu.

### Past čtvrtá: "Implicitní paralelismus Unix shell je výhoda"

Tady je třeba se zamyslet, co se vlastně tím "implicitním paralelismem" myslí. V Unix shell pipeline je každý stupeň samostatný proces a kernel je rozdělí mezi jádra zdarma — to je výhoda pro CPU paralelismus napříč stupni. Ale stojí to za pozornost: implicitní paralelismus má tendenci být zradou v převleku za výhodu. Buffering surprises (stage A produkuje rychleji než B, kernel buffer naplní, A se zablokuje, vy hledáte proč skript visí), order non-determinism při merge (`find | parallel | sort` — sort dostane řádky v náhodném pořadí), resource exhaustion (`find -type f | xargs -P 0` spustí jeden proces na soubor, a když je souborů 100 000, máte 100 000 souběžných procesů a OOM killer). Moderní konsensus napříč jazyky a runtime (Erlang, Go, async/await rodina) preferuje **explicit opt-in concurrency**, ne implicit. PowerShell explicit `-Parallel` je v souladu s tímhle konsensem.

### Past pátá: "fork/exec na Linuxu je rychlý, tedy lepší než CreateProcess na Windows"

Rychlý pro malé procesy v single-threaded shell-style use case, ano. Pro velký proces s velkým heapem? Stovky milisekund kvůli copy-on-write přes všechny page table entries. Notoricky známé u Redis, PostgreSQL, Java aplikací. Plus bezpečnostní past při interakci s thready — `fork()` v multi-threaded procesu je z velké části nedefinovaný terén. CreateProcess je dražší, ale poskytuje atomický explicit setup, Job Objects (resource governance), granulární inheritance — to vše jsou skutečné benefity pro server workloady. Jde o trade-off pro různé use cases, ne univerzální vítězství jednoho z nich.

### Past šestá: "PowerShell pipeline materializuje data, je to slabost"

Sort, group, distinct musí buffrovat v každém pipeline modelu — Unix `sort` to dělá taky. Je to inherentní fyzika operace, nelze ji nestreamovat (jak byste věděli, který element je první v setřízeném pořadí, kdybyste neviděli všechny?). Rozdíl je v implementaci konkrétních cmdletů. GNU `sort` má external merge sort se spillem na disk přes `-T` flag a funguje na souborech větších než RAM. PowerShell `Sort-Object` drží všechno v managed paměti a při překročení padá. To je oprava na úrovni cmdletu, ne na úrovni pipeline modelu — někdo by mohl `Sort-ObjectExternal` napsat za odpoledne. Že to není v default cmdletech je věc priorit, ne architektonického omezení.

### Past sedmá: "PowerShell synchronní model brání paralelismu"

Naopak, synchronní pull-based model dává zadarmo věci, které čistě paralelní model neumí: kooperativní souběžnost (generátorové koroutiny), streamování, short-circuit (`| Select -First N` přestane táhnout upstream), deterministické pořadí. Paralelismus je opt-in přes `-Parallel` přesně tam, kde má smysl. Kombinace synchronní pipeline + paralelní stupně dává nejlepší z obou světů — pull-based streaming dole, paralelní execution nahoře tam, kde IO bound práce dominuje. Tvrdit, že synchronní model "brání paralelismu", je nepochopení — model umožňuje paralelismus tam, kde ho chcete, a nepoužívá ho tam, kde ho nechcete.

### Past osmá: "PowerShell má encoding bugs, vyhněte se mu"

To je primárně problém PowerShell 5.1 host implementace, ne fundamentální pipeline mechanismus. PowerShell 7.5+ to řeší. A týká se to specificky hranice externích programů (volání `.exe`), ne internal cmdlet pipeline. Když někdo říká "PowerShell má encoding bugs", pravděpodobně mluví o 5.1, a pravděpodobně o specifickém scénáři rourování binárních dat. V čistě PowerShell pipeline mezi cmdlety encoding nehraje žádnou roli, protože objekty se předávají v paměti bez textové serializace.

### Past devátá: "fork() je elegantní primitivum"

Pro single-threaded shell-style use case ano, elegantní je. Pro multi-threaded program s velkým heapem je to past plná hran — copy-on-write přes page tables se nákladově neutralizuje, mutex handling po forku je dramatic (mutexy zůstávají uzamčené v childovi, kde vlákno, které je drželo, neexistuje), FD inheritance je default-unsafe a vede k chronickým leaks. NT design "explicit CreateProcess" je v některých ohledech čistější. Žádný z těch dvou designů není univerzálně lepší — každý optimalizuje pro jiné scénáře. Unix design favorizuje rychlý shell scripting; NT design favorizuje long-running multi-threaded servery.

### Past desátá: "Moderní Linux features jsou nové vynálezy"

io_uring (2019), cgroups (2007), namespaces, preemptive kernel (PREEMPT_RT mainline 2024), systemd (2010), Wayland (2008) — všechno má NT precedent o 15 až 25 let dříve. Není to argument "Linux jen kopíruje Windows", protože vznik těchto features na Linuxu je nezávislý — tyhle problémy je třeba řešit pro hyperscale a server workloady a Linux na ně narazil, jakmile začal vážně cílit na tuhle doménu. Je to **konvergence**, ne imitace. Ale tech komunita to systematicky nereflektuje, protože vznik features na Linuxu je veřejný a oslavovaný (LKML, Phoronix, blogy), zatímco NT historie sedí v Redmondu a nikdo o ní nemluví. Když narazíte na článek "konečně máme moderní X v Linuxu" pro X, které NT mělo dlouho, stojí za to si to v hlavě označit a chápat širší kontext.

## Souhrn pro toho, kdo to studuje

Klíčové k zapamatování: každá platforma má **vědomě zvolený design pro určité use cases**, ne nedostatky, které je třeba "opravit" znalostmi z jiné platformy.

Unix pipe je kernelový mechanismus pro tok bajtů mezi procesy. Funguje výtečně pro klasický idiom "malé nástroje skládané přes textové roury" — desítky tisíc nástrojů z různých ér a jazyků jsou automaticky kompozovatelné, protože kontrakt je jednoduchý: čti stdin, piš stdout, OS se postará o zbytek. Cena je textová konvence — všechno musí jít přes plain text, a strukturovaná data musíte serializovat a parsovat zpět.

Cmd.exe pipe je technicky kompetentní kernelový mechanismus stejného druhu jako Unix pipe — `CreatePipe()` od 1993, žádné dočasné soubory. Ekosystémově ovšem zasazený do prostředí, kde nikdy nevznikla bohatá kolekce malých CLI nástrojů — Windows nikdy neměl `grep`, `awk`, `sed`, `sort` jako standardní součást systému (až nedávno s WSL a moderními verzemi Windows). Mechanismus je v pořádku; kultura kolem něj se nikdy nerozvinula jako v Unixu.

PowerShell pipeline je úplně něco jiného — language construct uvnitř .NET runtime, který předává typed objekty zero-copy mezi cmdlety. Není to OS feature, je to feature programovacího jazyka. Pro práci v .NET ekosystému poskytuje typovou bezpečnost, discoverability, integraci s celou .NET BCL, strukturovaný error handling, automatický paralelismus na opt-in basis. Cena je impedanční nesoulad na hranici k ne-.NET světu — externí programy musí komunikovat přes text, a PowerShell platí tu daň jen tehdy, kdy překračuje hranici. Uvnitř svého ekosystému je elegantní; na hranici ztrácí.

Žádný z těchto modelů není univerzálně lepší. Každý je optimalizací pro svůj kontext. Když studujete tyhle systémy, dělejte to s vědomím, že vstupujete do tří odlišných kultur s odlišnými historiemi a odlišnými prioritami, ne tří implementací jednoho ideálu. Tehdy začnete vidět, proč zástupci každé kultury obhajují to, co obhajují, a kde jsou jejich blind spots. A to je nejlepší pozice pro vlastní úsudek, kdy který nástroj použít — výběr podle vědomého kritéria, ne podle kulturního reflexu.