Shell-Befehle

Einen KPI aus einem Shell-Befehl auf deinem Mac füttern: was der Befehl ausgeben muss, in welcher Umgebung er läuft, das 10-Sekunden-Limit und die Freigabe.

Ein Shell-KPI führt einen Befehl auf deinem Mac aus und speichert die Zahl, die dieser Befehl ausgibt. Das ist die Auffanglösung für alles, was kein Connector abdeckt: eine lokale Datenbank, ein Git-Repository, eine CSV im Download-Ordner, eine API, für die du ohnehin schon ein Tool installiert hast.

Es ist zugleich die mächtigste Quelle, ganz wörtlich. Der Befehl läuft mit den Rechten deines Benutzerkontos, und Pulsaria sperrt ihn in keine Sandbox. Lies den Abschnitt zur Freigabe, bevor du etwas einfügst, das du nicht selbst geschrieben hast.

Shell-KPIs funktionieren nur unter macOS. Auf jeder anderen Plattform scheitert der Abruf mit „Shell-Befehle sind nur unter macOS verfügbar“, und die Buttons, die einen Lauf auslösen, werden gar nicht erst angezeigt.

Einen Shell-KPI anlegen

  1. Dashboard → + KPI im Kopf einer Gruppe, oder KPI anlegen auf einem leeren Dashboard.
  2. In der Quellenauswahl die Karte Bash wählen („Jeder Shell-Befehl, der eine Zahl ausgibt. Läuft auf deinem Mac, braucht deine Freigabe.“).
  3. Den Befehl in das Feld Bash-Befehl tippen. Es ist ein zweizeiliges Code-Feld mit dem Platzhalter curl -s https://api.example.com/metric | jq '.value'.
  4. Optional Cron-Zeitplan (optional) setzen, damit der KPI sich selbst abruft.
  5. Test ausführen drücken. Das Ergebnis-Panel darunter zeigt den exakt ausgeführten Befehl, die Ausgabe, die Shell, den Exit-Code und die Zahl, die Pulsaria speichern würde.
  6. Freigeben drücken und im Dialog bestätigen.
  7. Im Abschnitt Ziel noch Name und Zielwert ausfüllen, dann KPI anlegen.

Du kannst auch ohne Freigabe speichern. Pulsaria zeigt dann den Hinweis „Gespeichert — noch nicht freigegeben / Der Scheduler überspringt diese KPI, bis du sie freigibst“, und der KPI liegt im Dashboard, ohne etwas zu tun, bis du zurückgehst und ihn freigibst.

Die Karte heißt Bash, ausgeführt wird der Befehl aber von /bin/sh -c. Schreib also POSIX-Shell, keine zsh- oder bash-spezifische Syntax.

Was der Befehl ausgeben muss

Pulsaria nimmt die komplette stdout-Ausgabe, schneidet Leerzeichen und Zeilenumbrüche vorne und hinten ab und liest den Rest als Zahl. Alles andere lässt den Abruf scheitern.

stdoutErgebnis
42Wird als 42 gespeichert
42\n (mit Zeilenumbruch)Wird als 42 gespeichert — Whitespace wird abgeschnitten
1234Wird als 1234 gespeichert — die Einrückung von wc -l stört nicht
3.5Wird als 3.5 gespeichert — Punkt als Dezimaltrennzeichen
3,5Scheitert mit „Ausgabe ist keine Zahl“
42% oder € 42Scheitert — keine Einheit, kein Währungszeichen, kein Tausendertrennzeichen
12\n15Scheitert — eine Zahl, keine Liste
(leer)Scheitert — leere Ausgabe gilt als „keine Zahl“

Zwei weitere Regeln entscheiden, ob der Wert überhaupt angeschaut wird:

  • Der Exit-Code muss 0 sein. Ein Exit-Code ungleich null lässt den Abruf mit „Exit-Code N“ scheitern, egal was ausgegeben wurde, und es wird nichts gespeichert. Das trifft besonders grep -c: ohne Treffer endet es mit 1. Hänge || true an, wenn null ein legitimes Ergebnis ist.
  • stderr wird für den Wert ignoriert. Es wird protokolliert und angezeigt, aber ein Befehl, der Warnungen nach stderr und eine saubere Zahl nach stdout schreibt, ist in Ordnung.

Die Umgebung, in der dein Befehl läuft

Shell/bin/sh -c "<dein Befehl>"
UmgebungWird geleert, dann werden genau drei Variablen gesetzt: PATH, HOME, LANG
PATHVom Pulsaria-Prozess übernommen; falls nicht gesetzt: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
HOMEÜbernommen; ein leerer String, wenn der App-Prozess keines hat
LANGÜbernommen; en_US.UTF-8, falls nicht gesetzt
ArbeitsverzeichnisWird von Pulsaria nicht gesetzt — verlass dich nicht auf relative Pfade
Timeout10 Sekunden, danach wird der Prozess beendet
EingabeKeine. Der Befehl kann nichts nachfragen und nichts von stdin lesen

Zwei Konsequenzen, die du dir merken solltest:

Dein Shell-Profil wird nie gelesen. /bin/sh -c läuft nicht-interaktiv, also werden .zshrc, .zprofile und .bash_profile nicht ausgeführt, und keines deiner Aliase, keine Funktion und keine exportierte Variable existiert. Braucht dein Befehl ein API-Token, muss er es sich selbst holen — zum Beispiel aus dem Schlüsselbund mit security find-generic-password.

PATH ist das, was der App-Prozess hat, nicht das, was dein Terminal hat. Ein per Homebrew nach /opt/homebrew/bin installiertes Tool wird eventuell nicht gefunden, und der Befehl scheitert dann mit „Exit-Code 127“. Nimm für alles außerhalb von /usr/bin und /bin absolute Pfade: /opt/homebrew/bin/jq statt jq. Test ausführen zeigt dir das sofort — der Test benutzt exakt dieselbe Umgebung wie ein geplanter Lauf.

Datums-Tokens

Die Chip-Leiste unter dem Befehlsfeld fügt Platzhalter ein, die erst beim Abruf aufgelöst werden, nicht bei der Freigabe. Genau deshalb bleibt eine rollierende Abfrage dauerhaft freigegeben: Der gespeicherte Text ist das, was per Fingerabdruck festgehalten wird — dass {{today}} morgen einen anderen Wert liefert, macht nichts kaputt.

Alle Datums-Tokens liefern YYYY-MM-DD nach deinem lokalen Kalendertag.

TokenLöst auf zu
{{today}}Heutiges Datum
{{yesterday}}Gestriges Datum
{{last7DaysAgo}}Vor 7 Tagen
{{last28DaysAgo}}Vor 28 Tagen
{{last30DaysAgo}}Vor 30 Tagen
{{firstDayOfMonth}}Erster Tag des aktuellen Monats
{{lastDayOfMonth}}Letzter Tag des aktuellen Monats
{{firstDayOfLastMonth}}Erster Tag des Vormonats
{{lastDayOfLastMonth}}Letzter Tag des Vormonats
{{firstDayOfQuarter}}Erster Tag des aktuellen Quartals
{{lastDayOfQuarter}}Letzter Tag des aktuellen Quartals
{{firstDayOfYear}}Erster Tag des aktuellen Jahres
{{lastDayOfYear}}Letzter Tag des aktuellen Jahres
{{now}}Aktueller Zeitstempel, ISO 8601
{{nowUnix}}Aktueller Unix-Zeitstempel in Sekunden

Ein Klick auf einen Chip fügt den Token an der Cursorposition ein; beim Draufzeigen siehst du, welchen Wert er gerade ergibt. Leerzeichen in den Klammern sind erlaubt ({{ today }} funktioniert). Ein unbekannter Name wie {{foo}} bleibt unverändert im Befehl stehen — die Shell sieht dann die geschweiften Klammern wörtlich.

Das Befehlsfeld repariert außerdem bei jedem Test, jeder Freigabe und jedem Speichern die macOS-Textersetzung: typografische Anführungszeichen werden zu geraden, Gedankenstriche zu Bindestrichen, zu .... Einfügen aus Notizen oder Mail zerlegt dir also nicht unbemerkt den Befehl.

Freigabe und erneute Freigabe

Ein Shell-Befehl ist beliebiger Code. Deshalb führt Pulsaria keinen aus, den du nicht ausdrücklich gesehen und bestätigt hast.

Solange ein Bash-KPI nicht freigegeben ist, steht über dem Befehlsfeld ein bernsteinfarbener Hinweis:

Freigabe erforderlich — Auto-KPIs laufen erst, wenn du sie freigibst. Teste den Befehl unten und klicke dann auf „Freigeben“. Ohne Freigabe überspringt der Scheduler diesen KPI.

Freigeben öffnet einen Bestätigungsdialog mit dem Titel „Bash-Befehl bestätigen“. Er zeigt den exakten Befehl in einem Monospace-Block, den Satz „Dieser Befehl wird bei jedem Fetch lokal auf deinem Mac ausgeführt — mit deinen Benutzerrechten. Pulsaria sperrt den Befehl nach deiner Freigabe gegen unbemerkte Änderungen (SHA-256-Fingerabdruck).“ und die Erinnerung „Gib nur Quellen frei, denen du vertraust. Bei späteren Edits ist eine erneute Bestätigung erforderlich.“ Nach dem Bestätigen wird der Hinweis grün: „Freigegeben — Der Scheduler ruft diesen KPI gemäß Cron-Zeitplan automatisch ab.“

Was die Freigabe speichert

Pulsaria bildet einen SHA-256-Hash über den gespeicherten Befehlstext und legt ihn bei der KPI-Definition ab. Vor jedem Lauf wird der gespeicherte Befehl erneut gehasht und verglichen.

  • Hash stimmt überein → der Befehl läuft.
  • Gar kein Hash vorhanden → der Lauf wird übersprungen und mit „Befehl nicht freigegeben — in den KPI-Einstellungen testen und freigeben“ protokolliert. Eine Sammel-Aktualisierung zählt ihn unter „übersprungen“ und bietet eine Abkürzung Freigeben an.
  • Hash vorhanden, aber abweichend → der Lauf scheitert mit „Befehl wurde geändert — bitte erneut freigeben“.

Der zweite Fall ist der Manipulationsschutz. Er greift, egal ob der Befehl im KPI-Dialog geändert wurde, durch einen Sync-Konflikt oder weil jemand kpi-definitions.json direkt bearbeitet hat.

Die Freigabe verfällt automatisch, wenn:

  • du den Befehl im KPI-Dialog änderst (ein einziges Zeichen reicht);
  • du den KPI duplizierst — die Kopie ist nicht freigegeben, mit dem Hinweis „Die Kopie benötigt eine erneute Freigabe, bevor sie automatisch abgerufen wird“;
  • du einen KPI aus einem JSON-Export importierst. Importierte Freigaben werden grundsätzlich verworfen, jeder importierte Bash-KPI muss auf dem neuen Mac erneut freigegeben werden. Siehe Backup, Export & Umzug.

Die Musterprüfung

Der Bestätigungsdialog durchsucht den Befehl nach zehn offensichtlich gefährlichen Mustern und listet die Treffer unter „Verdächtige Muster erkannt:“ auf.

MusterAngezeigte Warnung
rm -r / rm -rfLöscht Dateien rekursiv (rm -rf)
sudoVerwendet sudo
dd if=Verwendet dd (kann Daten überschreiben)
mkfsFormatiert ein Dateisystem (mkfs)
:(){ :|:& };:Fork-Bomb-Muster
curl … | shPipt curl-Output direkt in eine Shell
wget … | shPipt wget-Output direkt in eine Shell
evalVerwendet eval
chmod 777Setzt 777-Berechtigungen
> /dev/sd…Schreibt direkt auf eine Disk

Das ist ein Hinweis, keine Sicherheitsgrenze. Die Liste ist kurz, leicht zu umgehen, und ein Befehl, der kein einziges Muster trifft, kann deine Dateien trotzdem löschen. Die Freigabe schränkt nicht ein, was der Befehl darf — sie garantiert nur, dass genau das läuft, was du gelesen hast.

Zwei weitere ehrliche Details: Der Button Freigeben verlangt keinen erfolgreichen Test. Du kannst einen ungetesteten oder fehlschlagenden Befehl freigeben; nach einem grünen Test wird der Button lediglich smaragdfarben und ein Hinweis erscheint („✓ Test erfolgreich — du kannst jetzt freigeben“). Und die Freigabe gilt pro KPI, nicht pro Befehl — derselbe Befehl in zwei KPIs braucht zwei Freigaben.

Ausführen: Zeitplan, Test und manuelle Läufe

Test ausführen im KPI-Dialog ist ein Trockenlauf. Der Befehl wird ausgeführt, das Ergebnis gerendert, geschrieben wird nichts: kein KPI-Wert, kein Eintrag im Fetch-Log, keine Warnung. Der Test ignoriert außerdem die Freigabe, du kannst also in Ruhe herumprobieren, bevor du dich festlegst.

Alles andere läuft über denselben Pfad und schreibt sehr wohl:

  • Cron-Zeitplan (optional) im KPI-Dialog. Pulsaria prüft alle 60 Sekunden, solange die App offen ist, und ruft die fälligen KPIs ab — auch die, die während der App-Pause ausgefallen sind. Details unter Zeitpläne & Aktualisierung.
  • Jetzt ausführen auf der KPI-Detailseite, oder der kleine Play-Button auf der Dashboard-Karte neben dem BASH-Badge.
  • Aktualisieren im Dashboard-Kopf, das alle automatischen KPIs auf einmal abruft.

Egal wodurch der Lauf ausgelöst wird: Der Wert wird unter dem heutigen lokalen Datum gespeichert, ein Eintrag ins Fetch-Log geschrieben, und ein Statuswechsel auf „gefährdet“ oder „aus dem Plan“ löst eine Warnung aus. Ist die Testphase abgelaufen oder die Lizenz widerrufen, pausieren die geplanten Läufe, und manuelle Läufe werden abgewiesen.

Was protokolliert wird

Jeder Lauf über den echten Pfad — geplant oder manuell, erfolgreich, fehlgeschlagen oder übersprungen — hängt einen Eintrag ans Fetch-Log an. Bei einem Bash-KPI hält der Eintrag stdout, stderr, den Exit-Code, den tatsächlich ausgeführten Befehl (mit aufgelösten Datums-Tokens), den Shell-Aufruf /bin/sh -c und die Dauer fest.

Im Bildschirm Fetch-Log (Seitenleiste → Fetch-Log) zeigt eine Zeile den KPI-Namen, entweder Wert: 42 oder die Fehlermeldung, ein bash-Badge, die Dauer in Millisekunden und einen relativen Zeitstempel. Aufgeklappt siehst du den vollständigen Zeitstempel sowie die Blöcke stdout und stderr. Der Exit-Code steht dort nicht als eigenes Feld — wenn er die Ursache ist, ist er die Fehlermeldung („Exit-Code 127“). Die vollständige Meta-Zeile mit Shell, Exit-Code und Dauer zeigt das Test-Panel im KPI-Dialog.

Das Log ist ein Ringpuffer mit maximal 500 Einträgen, neueste zuerst. Mehr zum Bildschirm selbst unter Fetch-Log.

stdout und stderr werden wortwörtlich in fetch-log.json gespeichert. Ein Befehl, der im Fehlerfall ein Token oder einen Kundennamen ausgibt, schreibt diese Zeichenkette in eine Datei, die auch in einem JSON-Export landet. Halte Geheimnisse aus der Ausgabe heraus.

Beispielbefehle

Zeilen in einer lokalen SQLite-Datenbank:

/usr/bin/sqlite3 "$HOME/data/app.db" "select count(*) from users"

Commits in einem Repository seit Monatsbeginn:

/usr/bin/git -C "$HOME/Projects/app" rev-list --count --since={{firstDayOfMonth}} HEAD

Datenzeilen in einer exportierten CSV, ohne Kopfzeile:

echo $(( $(wc -l < "$HOME/Exports/leads.csv") - 1 ))

Eine Zahl aus einer JSON-API, mit absolutem Pfad zu jq:

curl -s "https://api.example.com/orders?from={{firstDayOfMonth}}&to={{today}}" | /opt/homebrew/bin/jq '.total'

Treffer in einer Logdatei — beachte das || true, ohne das ein leeres Ergebnis als „Exit-Code 1“ scheitern würde:

grep -c "signup" "$HOME/Library/Logs/app.log" || true

Freier Speicherplatz in ganzen Gigabyte:

df -k / | awk 'NR==2 {print int($4/1048576)}'

Für einen simplen JSON-Endpunkt brauchst du die Shell gar nicht. Eine HTTP-Quelle macht dasselbe ohne curl, ohne jq und ohne PATH-Problem.

Wenn etwas schiefgeht

MeldungUrsacheLösung
Befehl nicht freigegeben — in den KPI-Einstellungen testen und freigebenDer KPI wurde nie freigegeben; der Lauf wurde übersprungen, nicht versuchtKPI öffnen → Freigeben
Befehl wurde geändert — bitte erneut freigebenDer gespeicherte Befehl passt nicht mehr zu seinem FingerabdruckBefehl prüfen, dann erneut Freigeben
Ausgabe ist keine Zahlstdout war leer, hatte eine Einheit dabei oder enthielt mehr als eine ZeileAusgabe auf eine nackte Zahl reduzieren
Exit-Code 127Die Shell hat das Programm nicht gefundenAbsoluten Pfad verwenden, z. B. /opt/homebrew/bin/jq
Exit-Code 1Der Befehl selbst ist gescheitert oder endet bei „kein Treffer“ ungleich nullIm Terminal nachstellen, || true ergänzen, wo null gültig ist
Command timed out after 10sDer Befehl brauchte länger als 10 SekundenSchneller machen, oder die Zahl per launchd-Job in eine Datei schreiben und Pulsaria diese Datei lesen lassen
Shell-Befehle sind nur unter macOS verfügbarDer KPI läuft auf einer Nicht-macOS-PlattformNichts zu reparieren — diese Quelle gibt es nur für macOS
Kein Befehl konfiguriertDie Quelle ist Bash, aber das Befehlsfeld ist leerKPI öffnen und einen Befehl eintragen

Die Meldungen aus der nativen Schicht — Command timed out after 10s, Failed to spawn /bin/sh: …, Empty command — sind immer englisch, auch in der deutschen Oberfläche. Alles andere folgt deiner Sprachauswahl.

Ein Exit-Code von -1 im Test-Panel bedeutet, dass der Prozess ohne regulären Exit-Status endete, typischerweise weil ihn ein Signal beendet hat.

Wie es weitergeht

Nicht gefunden?

Schreib uns — jede Mail liest ein Mensch, meistens der, der das Feature gebaut hat.

admin@one-pixel-ahead.com