Archiv der Kategorie: Shell

STDERROR in eine Variable umleiten

In einem Script verkette ich mehrere Befehle, was aber den Nachteil hat, dass sich für die Fehlerbehandlung nur der Exit-Wert des äußeren Befehls auswerten lässt. Hier die Zeile, um die es geht:

sudo -u backup ssh root@${RECHNER} "~/sbin/do_SSHbackup" > $BACKUPDIR/${RECHNER%%.*}/${AKTDATE}.tgz || OK=NOK

Hier wird die Variable OK nur auf NOK gesetzt, wenn z.B. sudo fehlschlägt oder das Zielverzeichnis nicht existiert. Wirft jedoch das per SSH aufgerufene Script do_SSHbackup einen Fehler, so bekommt die Variable OK nichts davon mit. Auf dem Standardfehlerkanal landet jedoch eine Fehlermeldung.

Wenn ich nun die Ausgabe der Fehlermeldungen in einer Variablen sammeln und diese auswerten könnte, wäre ein Abfangen des Fehlers möglich. Aber wie, ohne dabei den Standardausgabekanal zu beeinflussen? Nach ein wenig „Googleei“ fand ich die Lösung bei stackoverflow.com.

OUTPUT=$( { sudo -u backup ssh root@${RECHNER} "~/sbin/do_SSHbackup" > $BACKUPDIR/${RECHNER%%.*}/${AKTDATE}.tgz; } 2>&1 ) || OK=NOK

Somit bekomme ich alle Fehlermeldungen in die Variable OUTPUT, welche ich im Nachgang auswerten kann:

if [ "$OK" != "OK" -o -n "$OUTPUT" ] ; then
  echo "$OUTPUT">&2
  ...
fi

Dateien mit Sym- oder Hardlinks finden

Symlinks finden

Das Verlinken von Dateien oder Verzeichnissen per Symlink ist eine prima Sache. Schwierig wird es nur, wenn man Dateien löscht oder verschiebt, auf die Symlinks zeigen. Diese laufen dann nämlich ins Leere. Um zu prüfen, ob es Symlinks auf eine Datei oder ein Verzeichnis gibt, nutz man das folgende Kommando:

find / -type l -print | xargs ls -ld | grep 'dateiname'

Das kann ich auch verwenden, wenn ich z.B. ein Verzeichnis verschieben will und nicht weiß, ob es Links auf Dateien oder Unterverzeichnisse im fraglichen Verzeichnis gibt. Ich setze dann als ‚dateiname‘ den Namen des Verzeichnisses ein, das ich verschieben will.

Siehe auch http://www.grymoire.com/Unix/Find.html#uh-6

Hardlinks finden

Mitunter benutzt man auch Hardlinks, um Dateien zu verlinken (z.B. Backup mit rsync, wie in der c’t 07/2009, S. 212 beschrieben). Hat man nun eine Datei, bei der man anhand des Linkcount erkennt, dass sie Hardlinks hat, will man evt. auch wissen, wo die anderen Links liegen. Mit

find . ! -type d -links +1 -ls|sort -n

findet man alle Dateien, deren Linkcount größer 1 ist. Durch die Sortierung anhand des Inodes (erste Spalte) kann man gut zusammengehörende Dateien erkennen.

Will man für eine Bestimmte Datei die zugehörigen Links finden, geht das auch so:

f=`ls -i dateiname |awk '{print $1}'`
find / -inum $f

Siehe auch: The UNIX and Linux Forums: search for hardlinks based on filename via find command.

set -x Ausgaben nur in eine Datei schreiben

Der Schalter set -x (auch xtrace genannt) ist ein hilfreiches Werkzeug zum Debuggen von Shellskripten. Aber manchmal sieht man auf Grund der Vielzahl an Meldungen den Wald von lauter Bäumen nicht. Und der normale Anwender sollte davon besser auch nichts zu sehen bekommen. Daher sollte man nach getaner Kammerjägerei den Schalter wieder aus dem Skript entfernen oder auskommentieren.

Was aber, wenn man die schönen Meldungen auch in der normalen Anwendung sammeln möchte, um z.B. einem von einem Anwender gemeldeten Fehler auf die Spur zu kommen. Mit ein paar zusätzlichen Zeilen kann man dies bewerkstelligen.

Hier erst mal das „normale“ Script:

#!/bin/sh

echo "Ich melde einen Fehler" >&2
echo "Eine ganz normale Meldung"

Es macht nichts wirklich sinnvolles, es gibt nur eine Meldung auf STDERR und eine auf STDOUT aus.

Nun die modifiziere Variante:

#!/bin/sh
{
  echo "+++++++++ `date "+%F %T"`: ${0##*/} $1 $2 $3 +++++++++"
  set -x
  echo "Fehler" >&2
  echo "normal"
  set +x
} 2>&1 | tee -a /tmp/${0##*/}.log | grep -v '^+'

Dieses Skript schreibt die komplette Ausgabe (mit den Debug-Zeilen von xtrace) in eine Logdatei mit gleichen Namen wie das Script selbst (mit angehängtem .log) in das Verzeichnis /tmp. Die xtrace-Zeilen werden für die normale Ausgabe über grep ausgefiltert.

Durch den Schalter -a bei tee wird die Ausgabe immer an die bestehende Logdatei angehängt. Dem trägt auch die echo-Zeile Rechnung, durch die zum Einen der Beginn eines neuen Laufs markiert wird und zum Anderen die zusätzlichen Parameter protokolliert werden.

Leerzeichen bei For-In-Schleifen in der bash

Im Artikel Dateinamen mit Leerzeichen auf der Kommandozeile verarbeiten habe ich beschrieben, wie man Ärger mit Leerzeichen in Dateinamen umgeht. Heute geht es um Daten, die innerhalb einer For-In-Schleife aus einer Datei gelesen werden und die Leerzeichen enthalten.

Wenn wir eine Datei mit folgendem Inhalt haben:

klaus rechner1
martin rechner3
dieter rechner7

Und wir lassen diese von folgendem Script einlesen und wieder ausgeben (was natürlich recht sinnfrei ist, hier aber nur zur Veranschaulichung dienen soll):

#!/bin/bash
for ZEILE in `cat daten`
do
  echo $ZEILE
done

Dann kommt sowas raus:

klaus
rechner1
martin
rechner3
dieter
rechner7

Durch die Leerzeichen werden die Daten-Zeilen zerteilt. Damit das nicht passiert, setzt man den „Input Field Separator“ $IFS auf ‚
‚ (Standard ist ein “
„):

#!/bin/bash
Newline=$'
'
IFS=$Newline
for ZEILE in `cat daten`
do
echo $ZEILE
done
IFS=

Dann sieht das Ergebnis wie erwartet und gewünscht aus:

klaus rechner1
martin rechner3
dieter rechner7

Alle Fehlermeldungen in einem Script umleiten

Man kann in einem Shell-Script ja Ausgaben, die an den Standard-Fehler-Kanal STDERR gehen, mit 2>>dateiname.log in eine Datei umleiten. Dies muss aber für jeden Befehl einzeln gemacht werden. Es gibt aber bei der Bash eine Methode, die Umleitung für das gesamte Script zu machen.

Dazu nutzt man eine Spezialform des Befehls exec: Werden als Parameter nur Umleitungen angegeben, so leitet die Shell die gewünschten Kanäle permanent um.

#!/bin/bash
exec 2>> dateiname.log

echo "Normale Ausgabe"
echo "An stderr" >&2
echo "das folgende macht einen Fehler"
TEST=`gibtsnicht  `
echo "Das geht auch in Pipes:"
LINES=`lsx -1 $TREE | egrepx -v '^.+$' | wc -l`

Startet man das Script, so erhält man auf der Konsole diese Ausgabe:

Normale Ausgabe
das folgende macht einen Fehler
Das geht auch in Pipes:

Das sind die Ausgaben auf der Standard-Ausgabe.

Die Log-Datei enthält die Ausgabe der Fehlermeldungen:

An stderr
./logtest: line 7: gibtsnicht: command not found
./logtest: line 9: lsx: command not found
./logtest: line 9: egrepx: command not found

Sollen alle Ausgaben in die Logdatei umgeleitet werden, so verwendet man

exec >> dateiname.log 2>&1

Das Quartal mit date berechnen

Das Unix-Kommando date kennt viele Format-Parameter aber leider keinen, der uns das Quartal ausgibt. Zum Glück lässt sich das aber mit Hilfe von expr berechnen.

Wir können mit dem date-Befehl einen Ausdruck erstellen, den expr dann für uns ausrechnet. Wir bekommen das das aktuelle Quartal oder, wenn wir mit -d ein anders angeben, das des gefragten Datums:

expr `date "+( %m - 1 ) / 3 + 1"`

expr `date -d "2007-05-01" "+( %m - 1 ) / 3 + 1"`

String in der Shell in Groß- oder Kleinbuchstaben wandeln

Mitunter will man in einem Shell-Script einen String ist Groß- oder Kleinbuchstaben wandeln. Das lässt sich recht einfach mit dem Kommando tr bewerkstelligen, welches auf vielen Systemen verfügbar ist.

So werden Kleinbuchstaben in Großbuchstaben gewandelt:

VAR=öäüß
echo $VAR
VAR=`echo $VAR|tr "[:lower:]" "[:upper:]"`
echo $VAR

Durch die Verwendung von [:lower:] und [:upper:] werden auch Umlaute berücksichtigt, was bei [a-z] bzw. [A-Z] nicht der Fall wäre.

Die umgekehrte Richtung funktioniert logischer Weise so:

VAR=GROSSÄÜÖ
echo $VAR
VAR=`echo $VAR|tr "[:upper:]" "[:lower:]"`
echo $VAR

Dateinamen mit Leerzeichen auf der Kommandozeile verarbeiten

Dateinamen, die Leerzeichen enthalten, machen auf der Kommandozeile oder in Scripten häufig Ärger, da das Leerzeichen meist als Trennzeichen zwischen den Parametern dient. hier nun ein paar Tricks, wie man die Probleme umschiffen kann.

Will man beispielsweise untersuchen, wieviel Platz die Dateien und Unterverzeichnisse des aktuellen Verzeichnisses jeweils einnehmen, kann man das mit

ls -A -1 | du -sh

machen. Aber das funktioniert nur für Dateinamen und Verzeichnisse, die keine Leerzeichen enthalten. Um auch solche zu verarbeiten muss man die folgende Kommandokette verwenden:

find . -maxdepth 1 -print0 | xargs -0 du -sh

Wir benutzen hier find, weil dieses die gefunden Namen durch das Argument -print0 als sog. nullterminierten String ausggeben kann. Dies teilen wir xargs mit dem Schalter -0 mit, wodurch xargs automatisch alle Leerzeichen etc. escaped an das eigentliche Kommando „du -sh“ weitergibt. Damit find nicht auf die Idee kommt, auch die Unterverzeichnisse zu durchsuchen, begrenzen wir die Suche mit „-maxdepth 1“ auf das angegebene Verzeichnis.

Unter Linux (bzw. mit GNU-ls) kann man auch die Option -Q des ls Kommandos nutzen, welches die Namen in Anführungszeichen setzt. Xargs ist aber trotzdem erforderlich:

/bin/ls -A1Q | xargs du -sh

[Wird fortgesetzt…]