Farbausgabe und Terminal-Fähigkeiten erkennen: tput und ANSI-Codes portabel nutzen
AI generated
$_
#!/
Bash · Terminal · tput · Ausgabe
Farbausgabe und Terminal-Fähigkeiten erkennen
tput und ANSI-Codes portabel einsetzen, ohne kaputte Logdateien

Rohe ANSI-Escape-Codes wie \e[31m sehen im eigenen Terminal gut aus, brechen aber, sobald ein Skript auf einem anderen Terminaltyp läuft oder seine Ausgabe in eine Logdatei umgeleitet wird. tput fragt stattdessen die tatsächlichen Fähigkeiten des aktuellen Terminals ab und liefert die passende Escape-Sequenz, während ein einfacher isatty-Check dafür sorgt, dass Farbe automatisch verschwindet, sobald niemand mehr sie interaktiv liest.

15 Min. Lesezeit tput · setaf · isatty Bash 4.x · 5.x · POSIX-Terminals

1. Warum rohe ANSI-Escape-Codes in Skripten fragil sind

Ein hart kodierter Escape-Code wie \e[31m für Rot geht davon aus, dass das Zielterminal genau diese Sequenz versteht. Das trifft auf die meisten modernen Terminals wie xterm oder gnome-terminal zwar zu, aber nicht zwingend auf ältere Terminals, serielle Konsolen oder minimale Umgebungen, in denen der genaue Funktionsumfang vom Wert der Umgebungsvariable TERM abhängt und stark variieren kann.

Noch problematischer wird es, sobald die Ausgabe eines Skripts nicht mehr direkt im Terminal landet, sondern in eine Datei umgeleitet oder an ein anderes Programm weitergeleitet wird. Rohe Escape-Codes tauchen dann als kryptische Steuerzeichen mitten im Text auf, stören das Parsen mit anderen Tools und machen Logdateien unnötig schwer lesbar, weil sie blind gesendet werden, unabhängig davon, ob überhaupt ein Terminal zuschaut.

2. tput-Grundlagen: setaf, bold und sgr0

tput schlägt in der terminfo-Datenbank nach, welche Escape-Sequenz für den in $TERM hinterlegten Terminaltyp tatsächlich die gewünschte Wirkung erzielt, und gibt genau diese Sequenz aus. tput setaf 1 liefert die Sequenz für Rot als Vordergrundfarbe, tput bold aktiviert fetten Text, und tput sgr0 setzt alle Textattribute zurück auf den Ausgangszustand, zuverlässiger als ein manuell zusammengebauter Reset-Code.

Weil jeder tput-Aufruf einen eigenen Subprozess startet, lohnt es sich, die benötigten Sequenzen einmal am Skriptanfang in Variablen zu speichern und diese Variablen danach beliebig oft wiederzuverwenden, statt tput in einer Schleife bei jeder Ausgabezeile erneut aufzurufen. Das spart merklich Prozessstarts, besonders bei Skripten mit vielen farbigen Log-Zeilen.


#!/usr/bin/env bash
set -euo pipefail

RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
BOLD=$(tput bold)
RESET=$(tput sgr0)

echo "${BOLD}${GREEN}Deployment successful${RESET}"
echo "${RED}Error: connection refused${RESET}"

3. Terminal-Fähigkeiten vor der Ausgabe prüfen: tput colors

tput colors liefert die Anzahl der Farben, die das aktuelle Terminal laut terminfo-Eintrag unterstützt, üblicherweise 8, 16 oder 256. Ein Skript, das Farbausgabe anbietet, sollte diesen Wert vor dem ersten setaf-Aufruf prüfen, statt blind von Farbunterstützung auszugehen, weil ein Aufruf mit einem zu hohen Farbindex auf einem Terminal mit weniger unterstützten Farben zu unerwarteten Ergebnissen führen kann.

Ein robustes Prüfmuster lautet if [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then, das sowohl den Fall abdeckt, dass tput colors einen validen Wert unter 8 liefert, als auch den Fall, dass der Aufruf ganz fehlschlägt, etwa weil kein passender terminfo-Eintrag existiert.


#!/usr/bin/env bash
set -euo pipefail

color_count="$(tput colors 2>/dev/null || echo 0)"

if [[ "$color_count" -ge 8 ]]; then
  echo "Terminal supports at least 8 colors ($color_count total)"
else
  echo "Terminal has no usable color support"
fi

4. isatty-Check: [ -t 1 ] und wann Ausgabe kein Terminal ist

Der Ausdruck [ -t 1 ] prüft, ob der Datei-Deskriptor 1, also stdout, mit einem echten Terminal verbunden ist. Wird die Ausgabe eines Skripts mit > log.txt in eine Datei umgeleitet oder mit | an ein anderes Programm weitergereicht, liefert dieser Test false, weil stdout dann kein Terminal mehr ist, sondern eine reguläre Datei oder eine Pipe.

Für zuverlässige Farbausgabe gehören tput colors und [ -t 1 ] zusammen: Nur wenn beide Bedingungen erfüllt sind, das Terminal Farbe unterstützt und die Ausgabe tatsächlich interaktiv gelesen wird, sollten Escape-Sequenzen überhaupt gesendet werden. Eine der beiden Prüfungen allein reicht nicht aus, um Fehlausgaben in allen Situationen zuverlässig zu vermeiden.


#!/usr/bin/env bash
set -euo pipefail

supports_color() {
  [[ -t 1 ]] || return 1
  local n
  n="$(tput colors 2>/dev/null || echo 0)"
  [[ "$n" -ge 8 ]]
}

if supports_color; then
  echo "$(tput setaf 2)Color enabled$(tput sgr0)"
else
  echo "Color disabled (no TTY or no color support)"
fi

5. Farbausgabe automatisch deaktivieren bei Umleitung in eine Datei

Statt an jeder einzelnen Ausgabezeile eine Bedingung für Farbe einzubauen, ist es deutlich wartungsfreundlicher, die Farbvariablen einmal am Skriptanfang entweder mit den echten tput-Sequenzen oder mit leeren Strings zu belegen, je nachdem, ob [ -t 1 ] und tput colors erfolgreich sind. Der restliche Skriptcode verwendet dann überall dieselben Variablen wie $RED und $RESET, ohne selbst noch einmal zu prüfen, ob Farbe aktiv ist.

Zusätzlich zur automatischen Erkennung erwarten viele Nutzer explizite Override-Flags wie --no-color oder --color=always, um die automatische Erkennung bei Bedarf zu übersteuern, etwa wenn eine CI-Pipeline die Ausgabe trotz fehlendem echtem Terminal farbig einfärben soll, weil ein nachgeschalteter Log-Viewer die Codes selbst wieder interpretiert.


#!/usr/bin/env bash
set -euo pipefail

force_color=0
no_color=0

for arg in "$@"; do
  case "$arg" in
    --color=always) force_color=1 ;;
    --no-color) no_color=1 ;;
  esac
done

if [[ "$no_color" -eq 1 ]]; then
  use_color=0
elif [[ "$force_color" -eq 1 ]]; then
  use_color=1
elif [[ -t 1 ]] && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
  use_color=1
else
  use_color=0
fi

if [[ "$use_color" -eq 1 ]]; then
  RED=$(tput setaf 1); GREEN=$(tput setaf 2); RESET=$(tput sgr0)
else
  RED=""; GREEN=""; RESET=""
fi

echo "${GREEN}Ready${RESET}"

6. Eine wiederverwendbare Farbfunktion für das ganze Skript bauen

Statt Farbvariablen direkt in jeder Ausgabezeile zu kombinieren, bündeln kleine Wrapper-Funktionen wie log_info() und log_error() die komplette Formatierung an einer einzigen Stelle. Der restliche Code ruft nur noch log_info "Deployment gestartet" auf, ohne selbst mit Escape-Sequenzen in Berührung zu kommen, was Tippfehler bei Farbnamen und vergessene RESET-Aufrufe von vornherein ausschließt.

Bei mehreren Deployment-Skripten im selben Projekt lohnt es sich, diese Funktionen in eine einzige, gemeinsam eingebundene Datei auszulagern, die jedes Skript am Anfang mit source einbindet. So bleibt das Farbschema über alle Skripte hinweg konsistent, und eine spätere Anpassung, etwa ein anderer Grundton, muss nur an einer Stelle gepflegt werden.


#!/usr/bin/env bash
set -euo pipefail

log_info()  { echo "${GREEN}[INFO]${RESET} $*"; }
log_error() { echo "${RED}[ERROR]${RESET} $*" >&2; }

log_info "Starting deployment"
log_error "Configuration file not found"

7. Der NO_COLOR-Standard und weitere Konventionen

Die Community-Konvention NO_COLOR (siehe no-color.org) legt fest, dass jedes gesetzte, nicht-leere Wert dieser Umgebungsvariable jede Farbausgabe eines Programms deaktivieren soll, unabhängig davon, was das Terminal eigentlich unterstützen würde. Viele moderne CLI-Tools respektieren diese Variable inzwischen, weshalb es sich lohnt, sie auch in eigenen Bash-Skripten zu berücksichtigen.

Die sinnvolle Prüfreihenfolge ist: zuerst NO_COLOR respektieren und Farbe bei gesetzter Variable sofort abschalten, danach explizite Skript-Flags wie --no-color auswerten, und erst danach die automatische Erkennung über [ -t 1 ] und tput colors greifen lassen. So gewinnt immer die explizitere Nutzerabsicht gegenüber der automatischen Erkennung.


#!/usr/bin/env bash
set -euo pipefail

if [[ -n "${NO_COLOR:-}" ]]; then
  use_color=0
elif [[ -t 1 ]] && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
  use_color=1
else
  use_color=0
fi

echo "use_color=$use_color"

8. Fallstricke: tput in Subshells, fehlendes terminfo und CI-Umgebungen

Minimale Docker-Basis-Images enthalten oft keine vollständige terminfo-Datenbank, wodurch tput-Aufrufe fehlschlagen oder eine Fehlermeldung auf stderr ausgeben können, statt einfach eine leere Sequenz zu liefern. Jeder tput-Aufruf in einem portablen Skript sollte deshalb mit 2>/dev/null abgesichert und mit einem sinnvollen Fallback-Wert wie || echo 0 kombiniert werden.

CI-Umgebungen setzen TERM häufig auf dumb oder lassen die Variable ganz leer, wodurch tput colors einen niedrigen oder fehlerhaften Wert zurückgibt. Ein Skript, das diesen Fall nicht abfängt, könnte sonst mit einem harten Fehler die ganze Pipeline stoppen, nur weil eine kosmetische Funktion wie Farbausgabe nicht verfügbar ist, weshalb der Fallback-Pfad immer getestet werden sollte.

9. tput im Vergleich zu rohen ANSI-Codes

Die Wahl zwischen rohen ANSI-Codes und tput ist im Grunde eine Abwägung zwischen minimalem Aufwand und echter Portabilität. Für ein Wegwerf-Skript, das ausschließlich im eigenen Terminal läuft, mögen hart kodierte Codes ausreichen, für jedes Skript, das weitergegeben, in CI-Pipelines eingesetzt oder in eine Logdatei geschrieben werden könnte, ist die Kombination aus tput, isatty-Check und NO_COLOR-Unterstützung die robustere Wahl.

Ansatz Portabilität Terminal-Erkennung Empfehlung
Rohe ANSI-Codes fest kodiert Gering Keine Nur für kurze, rein interaktive Wegwerf-Skripte
tput ohne Prüfung Mittel Keine automatische Deaktivierung Interaktive Skripte ohne Umleitungsrisiko
tput + isatty + NO_COLOR Hoch Vollständig, inklusive Nutzer-Override Produktionsreife CLI-Tools und Deployment-Skripte
Drittanbieter-Farbbibliotheken Hoch Abhängig von der Bibliothek Große CLI-Projekte mit vielen Ausgabeformaten

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

tput und Farbausgabe in Bash: Das Wichtigste auf einen Blick

tput-Grundlagen

tput setaf, tput bold und tput sgr0 fragen die tatsächliche terminfo-Fähigkeit ab, statt Escape-Codes hart zu kodieren.

Fähigkeiten prüfen

tput colors liefert die Anzahl unterstützter Farben, mit 2>/dev/null || echo 0 gegen fehlende terminfo-Einträge abgesichert.

isatty-Check

[ -t 1 ] erkennt, ob stdout an ein echtes Terminal geht, und deaktiviert Farbe automatisch bei Umleitung in eine Datei.

NO_COLOR beachten

Die NO_COLOR-Umgebungsvariable und explizite --no-color/--color=always-Flags haben Vorrang vor der automatischen Erkennung.

11. FAQ: tput und Farbausgabe in Bash: Das Wichtigste auf einen Blick

1Warum sollte ich tput statt rohen ANSI-Codes verwenden?
tput fragt die terminfo-Datenbank für den tatsächlichen Terminaltyp in $TERM ab und liefert nur die Sequenzen, die dieses Terminal wirklich versteht, statt eine feste Sequenz blind zu senden.
2Was liefert tput colors zurück?
Die Anzahl der vom aktuellen Terminal laut terminfo unterstützten Farben, üblicherweise 8, 16 oder 256. Der Wert sollte vor dem ersten setaf-Aufruf geprüft werden.
3Was macht [ -t 1 ]?
Der Test prüft, ob Datei-Deskriptor 1, also stdout, mit einem echten Terminal verbunden ist. Bei Umleitung in eine Datei oder eine Pipe liefert er false.
4Wie deaktiviere ich Farbe automatisch, wenn die Ausgabe in eine Datei umgeleitet wird?
Farbvariablen einmal am Skriptanfang abhängig von [ -t 1 ] und tput colors entweder mit echten Sequenzen oder mit leeren Strings befüllen, danach überall dieselben Variablen verwenden.
5Was ist der NO_COLOR-Standard?
Eine Community-Konvention, nach der jedes gesetzte, nicht-leere NO_COLOR jede Farbausgabe eines Programms deaktivieren soll, unabhängig von der tatsächlichen Terminal-Fähigkeit. Viele CLI-Tools respektieren diese Variable.
6Warum sollte ich tput-Aufrufe mit 2>/dev/null absichern?
Minimale Umgebungen ohne vollständige terminfo-Datenbank lassen tput mit einer Fehlermeldung fehlschlagen. 2>/dev/null zusammen mit einem Fallback wie || echo 0 verhindert, dass diese Meldung das Skript stört.
7Warum liefert tput colors in einer CI-Pipeline oft 0 oder einen Fehler?
CI-Umgebungen setzen TERM häufig auf dumb oder lassen die Variable leer, wodurch kein passender terminfo-Eintrag mit Farbunterstützung gefunden wird.
8Wie baue ich eine wiederverwendbare Farbfunktion?
Kleine Wrapper wie log_info() und log_error() bündeln die Formatierung an einer Stelle. Der restliche Code ruft nur noch diese Funktionen auf, ohne selbst mit Escape-Sequenzen zu arbeiten.
9Sollte ich ein --no-color-Flag zusätzlich zur automatischen Erkennung anbieten?
Ja. Viele Nutzer wollen die automatische Erkennung gezielt übersteuern, etwa in Log-Viewern, die Escape-Codes selbst interpretieren, oder in Umgebungen ohne verlässliches TERM.
10In welcher Reihenfolge sollte ich NO_COLOR, eigene Flags und isatty prüfen?
Zuerst NO_COLOR respektieren, danach explizite Skript-Flags wie --no-color oder --color=always auswerten, und erst zuletzt die automatische Erkennung über isatty und tput colors greifen lassen.