Schlagwort-Archive: Unix

Unix-Kommandozeile

Alle Scripte im aktuellen Verzeichnis greppen

Heute hatte ich den Fall, dass ich in einem Verzeichnis alle Perl- und Shell-Scripte nach dem Wort „gtar“ durchsuchen musste. Leider befanden sich in dem Verzeichnis nicht nur Scripte, sondern auch, zum Teil recht große, Binär-Dateien, die ich ausklammern wollte. Entstanden ist der folgende Einzeiler:

for FILE in `ls -1`; do if `file $FILE | grep -q Befehlstext >/dev/null`; then grep -l gtar $FILE; fi; done

Das funktioniert unter HP-UX, bei Linux ist file gesprächiger und muss daher auf den MIME-Type begrenzt werden:

for FILE in `ls -1`; do if `file --mime-type $FILE | egrep -e '(x-perl|x-shellscript)' >/dev/null`; then grep -l gtar $FILE; fi; done

 

Eine Datei umbenennen und dabei das Datum anfügen

Des öfteren kommt man die die Verlegenheit, dass man eine Datei so umbenennen möchte, dass das Modifikationsdatum der Datei an den Namen angehängt wird. Zum Beispiel soll aus „foo.bar“ die Datei „foo.bar_2013-02-12“ werden.

Jetzt kann man das natürlich so von Hand tippen, nach dem man sich mit ls -l über das entsprechende Datum informiert hat. Man kann das aber auch die Shell machen lassen:

mv foo.bar foo.bar_`stat -c '%y' foo.bar | cut -f 1 -d " "`

Dabei muss man aber viel tippen und vor allem den Dateinamen gleich drei mal. Das geht auch anders:

THEFILE=foo.bar && mv $THEFILE ${THEFILE}_`stat -c '%y' $THEFILE | cut -f 1 -d " "`

Weiterhin viel Tipparbeit, daher bauen wir daraus am besten einen Alias. Allerdings kann man einem Alias keine Parameter übergeben, daher machen wir eine Funktion daraus und tragen das folgende in unsere bash.rc ein:

mvdate(){ mv $1 ${1}_`stat -c '%y' $1 | cut -f 1 -d " "`; }

Nun macht mvdate foo.bar aus der Datei „foo.bar“ die „foo.bar_2013-02-12“.

 

diff aus 2x STDOUT

Man kann bei diff normalerweise nur einen der beiden Datei-Parameter durch ein „-“ ersetzen, um diese Daten vom Standard-Input zu lesen. Was aber, wenn man die Ausgaben von zwei Programmen vergleichen und man diese nicht in Dateien zwischenspeichern will?

Bei commandlinefu.com fand ich die Lösung:

diff <( cmd1 ) <( cmd2 )

Man kann also prima die Ausgabe von zwei Greps auf ps vergleichen, um die User zu finden, bei denen ein Programm nicht gestartet ist:

diff <( ps -ef | grep  'bin/anel' | sort | cut -c 1-8 ) \
  <( ps -ef | grep 'script/start_aws' | sort | cut -c 1-8 )

Skip der ersten Zeile wenn leer

Heute hatte ich das Problem, eine leere Ausgabe des Programms „zip“ zu unterdrücken, damit Cron mir keine Mail schickt, wenn es nichts zu zippen gibt.

Findet zip nichts, was es einpacken kann, bekommt man die folgende Meldung:

/usr/bin/zip -r /tmp/test.zip . -t `date -d "-1 days" +%Y-%m-%d` -i "*/bearbeitet/*"

zip error: Nothing to do! (/tmp/test.zip)

Die Zeile mit „Nothing to do!“ lässt sich ja noch einfach mit einem grep unterdrücken, aber wie werde ich die Leerzeile davor los? Ein

| tail +2

bewirkt zwar, dass die Ausgabe erst ab der zweiten Zeile erfolgt – leider auch dann, wenn zip etwas zu tun bekommt. Also muss perl ran:

more testdaten | perl -n -e 'print $_ unless ( $. == 1 && /^$/ )'

Dies unterdrückt die erste Zeile nur, wenn sie leer ist, wobei weitere Leerzeilen nicht ausgefiltert werden.

Der ganze Aufruf sieht dann so aus:

/usr/bin/zip -r /tmp/test.zip . -t `date -d "-1 days" +%Y-%m-%d` -i "*/bearbeitet/*" 2>&1 | \
  perl -n -e 'print $_ unless /zip error: Nothing to do/ || ( $. == 1 && /^$/ )'

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

Adobe-Reader Installationsdateien

Die Links zur Installation des (Acrobat) Adobe Reader, die man so im Netz findet, führen alle zur Adobe-Seite und dort zu einem Installer, der die eigentliche Installation aus dem Netz nach lädt. Das nützt einem aber nichts, wenn man den Reader auf einem Rechner installieren will, der gar keinen Zugang zum Internet hat.

Aber bei chip.de findet sich in den Kommentaren der Hinweis auf ftp://ftp.adobe.com/pub/adobe/reader/win/. Hier gibt es aktuelle und ältere Versionen des Windows-Installers zum Download. Installer für andere Betriebssysteme (z.B. Linux, Mac) findet man unter ftp://ftp.adobe.com/pub/adobe/reader/.

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.

Eine Reihe von IP-Adressen per Ping prüfen

Mitunter hat man das Problem, eine nicht kleine Anzahl an Rechnern auf Erreichbarkeit zu überprüfen. Die Adressen und Hostnamen liegen als Excel-Liste vor, aber diese alle von Hand zu pingen … dazu bin ich echt zu faul. Also habe ich mir ein kleines Script gebaut, welches diese Aufgabe elegant erledigt:

#!/bin/sh
#
# Sendet einen Ping an alle in der übergebenen Datei aufgeführten Adressen.
# In der Liste muss pro Zeile zuerst die IP-Adresse und danach durch Leerzeichen
# getrennt der Hostname stehen (optional).
# Die Ausgabe von Ping kann unterdrückt werden, indem STDERR ins Null-Dev
# geschickt wird:
# ping_list liste.txt 2>/dev/null
#

#set -x

Newline='
'

IFS=$Newline
for LINE in `cat $1`
do
  #echo "'$LINE'"
  ADR=${LINE%% *}
  HOST=${LINE##* }
  #echo "Adr: $ADR"
  #echo "Host: $HOST"
  if [ "$OSTYPE" = "linux" ]
  then
    ping -q -W 3 -c 1 $ADR 1>&2
  elif [ "$OSTYPE" = "hpux11.00" ]
  then
    ping $ADR -n 1 -m 3 1>&2
  else
    echo "Unknown OS"
    exit 99
  fi
  if [ $? -eq 0 ]
  then
    echo "$ADR ($HOST) is up"
  else
    echo "$ADR ($HOST) FAILED"
  fi
done
IFS=

Es arbeitet zzt. unter Linux (SuSE 11.1) und HP-UX 11i und kann auch hier heruntergeladen werden:
ping_list

Dateien eines Jahres archivieren

Heute stand ich (mal wieder) vor der Aufgabe, in einem Verzeichnis alle Dateien vom Vorjahr zu archivieren.

Im Artikel „Dateien an Hand des Datums und der Uhrzeit finden“ habe ich gezeigt, wie man mit find Dateien eines bestimmten Zeitraums finden kann. Da ich aber für die Archivierung keine Rekursion benötige, kann ich das Ganze etwas einfacher mit einem Einzeiler machen:

stat -c '%n Date:%y' * | perl -n -e 'print "$1\n" if /^(.+)\sDate:2008/' \
 | zip archiv_2008.zip -@

Ich verwende hier stat, um das Ausgabeformat bestimmen zu können. Da die Dateinamen Leerzeichen enthalten können, kann ich keine Leerzeichen als Trenner zwischen Name und Datum verwenden, daher die Kennzeichnung mittels „Date:“. Perl filtert mir erstens alle Dateien von 2008 heraus und gibt auch nur den Dateinamen aus. Zip liest diese von der Standardeingabe, wenn die Dateiliste als -@ angegeben ist.

Soll Zip die Dateien auch gleich entsorgen, so kann man das über die Option -m erreichen.