Springe zum Inhalt

Eine sehr nützliche Funktion der Bash ist es, Kommandos in einer Historie zu speichern, um sie wiederholt ausführen zu können. So z.B. können die zuletzt ausgeführte Kommando durch Drücken der Cursor-Hoch-Taste wieder in die Eingebezeile geholt, evtl. editiert und erneut ausgeführt werden.

Wenn nun durch wiederholtes Ausführen immer des gleichen Kommandos ausgeführt wird, z.B. um sich ändernden Plattenplatz bei einer Sicherung zu beobachten oder den Verzeichnisinhalt zu überwachen, der durch ein laufendes Programm geändert wird, schreibt die Bash auch immer wieder das gleiche Kommando in die Historie. Das ist sehr unpraktisch, wenn man evtl. ein weiter zurückliegendes Kommando wieder aus der Historie hervorholen will.

Genau für diese Funktion hat die Bash eine Einstellung, welche aus der Umgebungsvariable HISTCONTROL gelesen wird. Wird dort ignoredups eingestellt, werden keine direkt aufanderfolgenden gleichen Kommandos in die Historie geschrieben. Eine zweite Einstellung ignorespaces legt fest, dass keine Kommandos in die Historie geschrieben werden, die mit einem Leerzeichen beginnen, was z.B. für Kommandos nützlich ist, bei denen man ein Kennwort direkt in der Kommandozeile angegeben hat. Da beide Einstellungen sehr nützlich sind, können sie mit ignoreboth gleichzeitig eingeschaltet werden.

In der ~/.bashrc fügt man dazu folgende Zeile ein:

HISTCONTROL=ignoreboth

Nach einem erneuten Login oder nach dem "sourcen" der Datei mit . ~/.bashrc ist die Option sofort aktiv.

Kürzlich ergab sich das Problem, Syslog-Nachrichten an einen Remote-Host verschicken zu müssen, der sonst dazu verwendete Befehl logger auf dem System aber noch kein Verschicken an Remote-Systeme unterstützte. Leider gab es für das betreffende Gerät auch keine neuere Software, es handelte sich um eine externe Netzwerk-Festplatte von WD, ein My Book Live.

Wenn sonst keine entsprechenden Werkzeuge zur Verfügung stehen, ist es oft ein unterschätztes Feature, dass bash direkt an Netzwerkadressen senden oder von diesen empfangen kann. Glücklicherweise unterstützt die auf den Geräten installierte bash-Version das Lesen und Schreiben an Netzwerkadressen per TCP oder UDP. Das ursprüngliche Syslog-Nachrichtenformat, beschrieben in RFC3164, ist reiner Text. Gesagt getan, ein Skript muss her:

#!/bin/bash
 
SEVERITY=${1:-7}
MESSAGE=$2
LOGHOST=Hostname oder IP-Adresse des Remote-Syslog-Hosts
 
declare -A SEVERTIES=([emergency]=0 [alert]=1 [critical]=2 [error]=3 [warning]=4 [notice]=5 [informational]=6 [debug]=7)
re='^[0-9]+$'
if ! [[ $SEVERITY =~ $re ]]; then
        SEVERITY=${SEVERTIES[$SEVERITY]}
        if [ -z "$SEVERITY" ]; then
                echo Severity not found >&2
                exit 1
        fi
fi
read -d $'\0' PROCNAME ARGS </proc/$PPID/cmdline
echo "<13$SEVERITY>$PROCNAME[$PPID]: $MESSAGE" >/dev/udp/$LOGHOST/514

Das Skript hat zwei Parameter, der erste gibt den Schweregrad an, welcher als numerischer Wert oder in seiner Textdarstellung übergeben werden kann. Der zweite Parameter ist die Nachricht selbst. Vor dem ersten Aufruf muss noch der Inhalt der Variablen LOGHOST angepasst werden.

Wenn das Skript nun z.B. unter dem Namen rlog.sh abgespeichert wird, kann es aus anderen Skripten wie folgt aufgerufen werden (vorher nicht vergessen, das Skript auch ausführbar zu machen):

rlog.sh debug "Ich bin eine Testnachricht"

Natürlich muss auf der Empfängerseite auch ein syslog-Daemon auf dem Port 514 lauschen. In der Debian-Standardinstallation tut der dortige rsyslogd das nicht. Dazu muss die Datei /etc/rsyslogd.conf angepasst bzw. die Kommentarzeichen vor folgenden Zeilen entfernt werden:

module(load="imudp")
input(type="imudp" port="514")

Möchte man nun die von Remote eintreffenden Nachrichten auf dem Syslog-Host in eigene Logdateien schreiben, können unter /etc/rsyslog.d/ entsprechende Konfigurationdateien erstellt werden.

if $fromhost-ip == [ '192.168.0.191', '192.168.0.192', '192.168.0.193' ] then /var/log/nas.log
& stop

In diesem Beispiel werden Syslog-Meldungen, welche von den angegebenen IP-Adressen empfangen werden, in das Logfile /var/log/nas.log geschrieben.

Von jetzt an können auch die hier vorhandenen WD My Book Live Syslog-Nachrichten schreiben:

rlog.sh debug "Ich bin eine Nachricht von $HOSTNAME"

2

Oft darf von einem Bash Script nur eine Instanz laufen. Beispiele sind ein Backup oder ein Datei-Transfers, welche bei gleichzeitigem Lauf der selben Scripte evtl. nicht korrekt durchgeführt würden. Mit einfachen bash-Mitteln lässt sich dies jedoch verhindern.

Ganz einfach könnte man auf das Vorhandensein einer Datei testen und sie anlegen, falls sie nicht existiert. Allerdings hätte man sich damit auch eine sogenannte RaceCondition geschaffen, der Vorgang zwischen Test und Anlegen ist nicht atomar. Ein nicht funktionierendes Beispiel:

LOCKFILE=/var/lock/test.lock
[[ -f $LOCKFILE ]] && exit 1
> $LOCKFILE

Nehmen wir an, zwei Instanzen eines Skripts mit diesem Schnipsel kommen gleichzeitig an die Stelle des Tests auf das Vorhandensein des Lockfiles. Beide finden das Lockfile nicht und werden weiter ausgeführt!

Atomar hingegen ist das Anlegen von Verzeichnissen. mkdir legt ein Verzeichnis an und testet gleichzeitig, ob es bereits vorhanden ist. Mit folgendem Schnipsel am Scriptanfang kann ein gleichzeitiges Ausführen des selben Scripts verhindert werden:

LOCK=/var/lock/mylock
if ! mkdir $LOCK 2>/dev/null; then
	echo Already running or stale lock exists. >&2
	exit 1
fi
trap -- "rmdir $LOCK" EXIT
 
# do work

trap sorgt dafür, dass bei Beendigung des Scripts das Lock-Verzeichnis wieder entfernt wird.

Eins sei noch erwähnt: wenn der Prozess in dem das Script läuft, also die bash, mit "kill -9" abgewürgt wird, wird der Befehl hinter trap nicht mehr ausgeführt. Das Lock-Verzeichnis muss dann manuell entfernt werden.

Update vom 16.02.2016: Hinweis auf RaceCondition und Lösung mit mkdir hinzugefügt.

10

Die von Let's Encrypt kostenlos erhältlichen Zertifikate sind aktuell 90 Tage gültig. Bei vielen Zertifikaten, so z.B. bei vielen Subdomains mit eigenen Zertifikaten, fällt der Überblick schwer, wann welches Zertifikat in Kürze abläuft und erneuert werden muss. Eine automatische Erinnerung muss her.

Eine Methode, um automatisiert alle Zertifikate auf ihren bevorstehenden Ablauf zu testen, kann mit dem folgenden Bash-Script realisiert werden. ...weiterlesen "Let’s Encrypt: Erinnerung vor Zertifikatablauf"

In einem Xterm oder Gnome-Terminal ist es nützlich, dass der aktuelle Host oder das Verzeichnis in der Caption des Teminalfensters angezeigt wird. Die Caption wird evtl. auch in der Taskleiste des jeweiligen Displaymanagers angezeigt, so dass man schnell weiß, welches Terminalfenster wo steht. Allerdings gibt es einige Programme, z.B. den Midnight-Commander, welche die Caption umstellen und beim Verlassen des Programms nicht wieder zurückstellen. In der Shell kann man mitlerweile ganz woanders sein, doch das Terminalfenster zeigt immer noch eine veraltetete Caption an.

Abhilfe bringt das Umsetzen der Bash-Variablen PS1, welche die Anzeige des Kommandoprompts definiert. In diese Variable kann man Steuerzeichen einfügen, die die Caption entsprechend setzen. In der Datei ~/.bashrc fügt man dazu am Ende folgende Kommandos ein:

PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] '

case "$TERM" in
        xterm)
                PS1="$PS1\033]0;${USER}@${HOSTNAME}: ${PWD}\007"
                ;;
        *)
                ;;
esac

Eventuell muss die erste Zeile mit der aktuellen Definition von PS1 ersetzt werden (dieser hier stammt aus einer Linux-Mint Installation). Die von der Distribution gesetzte Sequenz für den Kommandoprompt kann vor der Modifikation mit

echo $PS1

angezeigt werden. Der nachfolgende Block dient zur Auswahl des jeweiligen Terminals. Auf der Konsole kann keine Caption gesetzt werden, also wird die Steuersequenz auch nur bei Terminals mit Typ xterm hinzugefügt.

Bei Bedarf kann der Codeblock leicht um andere Terminaltypen erweitert werden.