Zx — Bessere Shell-Skripte mit JavaScript schreiben
Übersicht
zx ist ein Tool von Google zum Schreiben von Shell-Skripten in JavaScript/TypeScript. Es kapselt child_process, escaped Argumente automatisch und bietet sinnvolle Standardwerte – und gibt Ihnen die Leistungsfähigkeit des JavaScript-Ökosystems in Ihren Skripten.
#!/usr/bin/env zx
await $`cat package.json | grep name`
const branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`
const name = 'foo & bar'
await $`mkdir /tmp/${name}` // Keine Anführungszeichen nötig – automatisch escaped
Bash ist großartig für einfache Aufgaben, aber wenn Skripte komplex werden, hilft eine vollwertige Programmiersprache. zx fügt hilfreiche Wrapper um child_process hinzu, escaped Argumente und bietet sinnvolle Standardwerte. Denken Sie: bash + JavaScript in einem Skript.
Auslöser
Wird auch ausgelöst, wenn Benutzer nach dem Ausführen von Shell-Befehlen in JavaScript, dem Konvertieren von Bash-Skripten in zx, dem Ausführen von entfernten Skripten, Markdown-Skripten oder TypeScript-Shell-Skripten fragen.
Schnellstart
npm install zx
Skripte als .mjs-Dateien schreiben (unterstützt top-level await). Fügen Sie den Shebang #!/usr/bin/env zx hinzu oder führen Sie es über die CLI aus:
zx ./script.mjs # Direkte Ausführung
npx zx ./script.mjs # Über npx
node --import zx/globals # Als Node.js-Loader
Alle Funktionen ($, cd, fetch, etc.) sind in zx-Skripten ohne Importe global verfügbar. Für explizite Importe (bessere VS Code-Autovervollständigung):
import 'zx/globals'
Kernkonzepte
$`command` — Shell-Befehle ausführen
Das getaggte Template-Literal ist das Herzstück von zx. Alles in ${...} wird automatisch escaped und in Anführungszeichen gesetzt.
// Asynchron (Standard) — gibt ProcessPromise zurück
const output = await $`ls -la`
// Synchrone Variante — gibt ProcessOutput direkt zurück
const dir = $.sync`pwd`
// Arrays werden abgeflacht
const flags = ['--oneline', '--decorate', '--color']
await $`git log ${flags}`
// Exit-Codes ungleich Null werfen ProcessOutput
try {
await $`exit 1`
} catch (p) {
console.log(`Exit: ${p.exitCode}, Fehler: ${p.stderr}`)
}
Vorkonfiguration mit $({...})
Erstellen Sie benutzerdefinierte $-Instanzen mit voreingestellten Optionen – verknüpfbar und kombinierbar:
const $$ = $({ verbose: false, env: { NODE_ENV: 'production' } })
const pwd = $$.sync`pwd`
// Voreinstellungen sind verknüpfbar
const $1 = $({ nothrow: true })
const $2 = $1({ sync: true }) // Sowohl nothrow als auch sync angewendet
ProcessPromise & ProcessOutput
$`cmd` ProcessPromise (erweitert Promise)
├── .pipe() Stream-Piping
├── .kill() Prozess beenden
├── .text() Ausgabe als String
├── .json() Ausgabe als geparstes JSON
├── .lines() Ausgabe zeilenweise aufgeteilt
├── .nothrow() Fehler für diesen Befehl unterdrücken
├── .quiet() Ausgabe für diesen Befehl unterdrücken
├── .timeout() Automatisches Beenden nach Zeitüberschreitung
├── .stdio() E/A konfigurieren
├── .exitCode Promise<Exit-Code>
├── .stdout Lesbarer Stream
├── .stderr Lesbarer Stream
├── .stdin Beschreibbarer Stream
├── .pid / .cmd Prozessmetadaten
└── await → ProcessOutput
├── .stdout string
├── .stderr string
├── .exitCode number
├── .signal string|null
├── .text() / .json() / .lines() / .buffer() / .blob()
└── .ok boolean (wenn nothrow)
Entscheidungsbaum
Verwenden Sie beim Schreiben von zx-Skripten diesen Entscheidungsbaum:
| Ziel | Ansatz |
|---|---|
| Einen Befehl ausführen | await $`cmd` |
| Synchron ausführen | $.sync`cmd` |
| Ausgabe pipen | .pipe($`next`) / .pipe('datei.txt') |
| Fehler elegant behandeln | $({nothrow: true}) / .nothrow() |
| Zeitüberschreitung festlegen | $({timeout: '30s'}) / .timeout('30s') |
| JSON-Ausgabe parsen | (await $`cmd`).json() |
| Echtzeit-Streaming | for await (const line of $`cmd`) |
| Bei Fehler wiederholen | retry(5, () => $`cmd`) |
| Benutzereingabe | question('Name: ') |
| Fortschrittsanzeige | await spinner('Arbeite...', () => $`cmd`) |
| Verzeichnis wechseln | cd('/pfad') oder within(() => { $.cwd = '/tmp' }) |
| Temporäre Dateien/Verzeichnisse | tmpfile() / tmpdir() |
| CLI-Argumente parsen | argv.flag oder minimist(process.argv.slice(2)) |
| .env-Datei laden | dotenv.config('.env') |
Effektive zx-Skripte schreiben
Parallele Ausführung
const results = await Promise.all([
$`sleep 1; echo 1`,
$`sleep 2; echo 2`,
$`sleep 3; echo 3`,
])
Fehlerbehandlung mit nothrow
$.nothrow = true
const repos = ['zx', 'webpod']
const clones = repos.map(n => $`git clone https://github.com/google/${n}`)
const results = await Promise.all(clones)
const errors = results.filter(o => !o.ok).map(o => o.stderr.trim())
console.log('Fehler:', errors.join('\n'))
Stream-Piping
// Befehle wie Bash-Pipes verketten
const greeting = await $`printf "hello"`
.pipe($`awk '{printf $1", world!"}'`)
.pipe($`tr '[a-z]' '[A-Z]'`)
// In Datei pipen
await $`echo "Hallo!"`.pipe('/tmp/ausgabe.txt')
// Echtzeit-Ausgabe ans Terminal
await $`echo 1; sleep 1; echo 2; sleep 1; echo 3`.pipe(process.stdout)
Stream-Aufteilung & Zusammenführung
// Eine Quelle an mehrere Verbraucher aufteilen
const p = $`some-command`
const [o1, o2] = await Promise.all([
p.pipe`log`,
p.pipe`extract`,
])
// Mehrere Quellen zusammenführen
const $h = $({ halt: true })
const p1 = $`echo foo`
const p2 = $h`echo a && sleep 0.1 && echo b`
const p3 = $h`echo c && sleep 0.1 && echo d`
const cat = $h`cat`
p1.pipe(cat); p2.pipe(cat); p3.pipe(cat)
await cat.run()
Ausgabeformatierer
const p = $`echo '{"foo":"bar"}\nline2'`
await p.json() // { foo: 'bar' }
await p.lines() // ['{"foo":"bar"}', 'line2']
await p.text() // '{"foo":"bar"}\nline2\n'
Asynchrone Iteration
for await (const line of $`git log --oneline --max-count=5`) {
console.log(line)
}
Shell-Konfiguration
zx standardmäßig auf bash. Wechseln Sie nach Bedarf die Shell:
import { useBash, usePowerShell, usePwsh } from 'zx'
usePowerShell() // PowerShell.exe
usePwsh() // PowerShell v7+
useBash() // Zurück zu bash
// Manuelle Überschreibung
$.shell = '/bin/zsh'
Unter Windows sollten Sie WSL oder Git Bash für Bash-Unterstützung in Betracht ziehen, oder über usePowerShell() zu PowerShell wechseln.
Zusammenfassung der integrierten Helfer
| Helfer | Zweck | Beispiel |
|---|---|---|
cd() | Verzeichnis wechseln | cd('/tmp') |
fetch() | HTTP-Anfragen, unterstützt .pipe() | fetch('https://api.example.com') |
question() | Interaktive Benutzereingabe | question('Name: ') |
sleep() | Ausführung verzögern | await sleep(1000) |
echo() | Auf stdout ausgeben | echo`Status: ${p}` |
stdin() | Stdin lesen | JSON.parse(await stdin()) |
within() | Isolierter asynchroner Konfigurationskontext | within(() => { $.cwd = '/tmp' }) |
retry() | Wiederholung mit Verzögerung/Backoff | retry(5, () => $curl url) |
spinner() | CLI-Fortschrittsanzeige | await spinner(() => $long-cmd) |
glob() | Datei-Glob-Matching (globby) | glob('**/*.js') |
which() | Ausführbaren Pfad finden | await which('node') |
ps | Plattformübergreifende Prozessliste | ps.lookup({ command: 'node' }) |
tmpdir() | Temporäres Verzeichnis | tmpdir('sub') |
tmpfile() | Temporäre Datei | tmpfile('f.txt', 'content') |
argv | Geparste CLI-Argumente | argv.verbose |
dotenv | .env-Datei laden | dotenv.config('.env') |
Bereitgestellte npm-Pakete: chalk (Farben), fs (fs-extra), os, path, YAML (yaml), minimist.
Ressourcen
- references/api.md — Vollständige API-Referenz:
$-Optionen,cd(),fetch(),question(),sleep(),echo(),stdin(),within(),retry(),spinner(),glob(),which(),ps,kill(),tmpdir(),tmpfile(),minimist,argv,chalk,fs,os,path,YAML,dotenv,quote(),useBash(),usePowerShell(),usePwsh() - references/configuration.md — Alle
$.options:shell,prefix,postfix,quote,verbose,quiet,env,cwd,timeout,nothrow,detached,preferLocal,spawn,kill,log,input,signal,stdio,halt,delimiter,defaults - references/cli.md — CLI-Nutzung: Flags, Umgebungsvariablen, Markdown-Skripte, entfernte Skripte, Stdin-Ausführung, REPL-Modus
- references/process.md — ProcessPromise/ProcessOutput-Lebenszyklus, Piping, Beenden, Abbrechen, Ausgabeformatierer, Stream-Handhabung