tree

24. Dezember 2012 at 07:00

TreeFrohe Weihnachten!

Und weil sich heute alles um den Weihnachtsbaum versammelt, will ich das „tree“ Utility mal vorstellen. Mit „apt-get install tree“ läßt sich das Tool unter Debian installieren. Es zeigt Verzeichnisbäume auf der Konsole in einer Linien-Hierarchie an. Nichts besonderes, aber um sich einen Überblick zu verschaffen, kann es selbst hilfreich sein.

Die wichtigsten Parameter für den Einstieg:

  • -a zeigt auch versteckte Dateien an
  • -d zeugt nur Verzeichnisse, keine Dateien (sonst werden auch alle Dateien mit angezeigt)
  • -L <num> zeigt nur <num> Ebenen an.

Möchte man zum Beispiel mal seine Struktur im /usr/local Verzeichnis kennen lernen, geht das so:

# tree -d /usr/local/
/usr/local/
├── bin
├── etc
├── games
├── include
├── lib
├── man -> share/man
├── sbin
├── share
│   ├── ca-certificates
│   ├── fonts
│   ├── man
│   ├── sgml
│   │   ├── declaration
│   │   ├── dtd
│   │   ├── entities
│   │   ├── misc
│   │   └── stylesheet
│   └── xml
│   ├── declaration
│   ├── entities
│   ├── misc
│   └── schema
└── src

Ach ja, noch ein zweiter Tipp, weil ja Weihnachten ist:

pstree ist zwar ein ganz anderes Tool, aber macht das gleiche mit laufenden Prozessen (also ps mit Tree-Ansicht).

Viel Spaß beim Ausprobieren.

Scriptverzeichnis finden

19. Dezember 2012 at 21:16

Verzeichnis-IconIch bastel oft kleine Scripte, die mir irgendwelche Aufgaben abnehmen und meist auch nicht lange leben. Oft arbeite ich bei solchen Helper-Scripts nicht so 100% sauber. Zum Beispiel erstelle ich häufig keine saubere temporäre Datei mit tmpfile, sondern benutze einfach ein File im aktuellen Verzeichnis. Oder ich benötige eine Eingabedatei für das Script und benutze einfach „input.txt“ statt das File per Commandline zu übergeben.

Dabei fiel ich schon einige Male darauf herein, dass solche Scripte oft nicht richtig funktionieren, wenn man sich beim Starten nicht im Verzeichnis des Scripts befindet. Wenn man das Script z.B. unter /usr/local/bin ablegt und es später vom Home-Verzeichnis (~) aus starten (mit /usr/local/bin/script.sh), wird er die Datei „input.txt“ natürlich im Home-Verzeichnis suchen und nicht dort, wo ich sie abgelegt habe.

Ein einfacher Trick, dies global zu lösen: Gleich am Anfang des Scripts mit cd in das Verzeichnis wechseln, in dem das Script liegt. Aber wie findet das Script heraus, wo es liegt?

Die Variable $0 enthält den Scriptnamen, wie er auf der Kommandozeile aufgerufen wurde. Starten wir das Script mit /usr/local/bin/script.sh enthält $0 den String „/usr/local/bin/script.sh“. Sind wir schon im richtigen Verzeichnis, würden wir es mit ./script.sh starten. So enthält $0 auch nur „./script.sh“.

Das Kommando dirname hilft uns weiter. Während basename den Dateinamen aus einem vollständigen Pfad löst, holt dirname nur den Pfad heraus:

# basename /usr/local/bin/script.sh
script.sh
# dirname /usr/local/bin/script.sh
/usr/local/bin
# basename ./script.sh
script.sh
# dirname ./script.sh
.

Nun hilft uns „.“ natürlich nicht sehr weiter, um das tatsächliche Verzeichnis herauszufinden. Aber das benötigen wir ja auch gar nicht, denn es genügt ja, den relativen Pfad zu erhalten, um mit cd dorthin zu wechseln. Und das funktionier auch mit dem aktuellen Verzeichnis „.“ ohne Probleme.

Nun starten wir unser Script wie folgt:

#!/usr/bin/env bash
cd $(dirname $0)

Das „dirname $0“ bringt das Verzeichnis zum Vorschein, so wie es auf der Kommandozeile eingegeben wurde (also absolut oder relativ zum aktuellen Verzeichnis) und mit cd wechseln wir dorthin. Somit wechselt das Script immer in das Verzeichnis, in dem es selbst liegt.

Klar, das ist nur, um die eigene Faulheit zu unterstützen, eine saubere Programmierung wäre natürlich eher zu empfehlen, aber mir half diese Zeile schon recht oft, so dass ich sie meist schon überall einsetze.

Cron: Letzter Freitag im Monat

16. Dezember 2012 at 10:00

Viele regelmäßig Aufgaben erledigen Admins mit Script, die per Cron gestartet werden. Solange sich das Zeitmuster im Format von Cron eingeben läßt, ist das alles kein Problem.

  • 0 9 * * * = Jeden Tag um 9 Uhr
  • 30 14 * * 1-5 = Wochentags um 14:30 Uhr

Aber was, wenn ein Script z.B. am letzten Freitag im Monat gestartet werden soll? Das läßt sich in diesem Muster nicht darstellen. Es gibt aber ein paar einfache Tricks, die in Kombination zum Ziel dieser Aufgabe führen.

Trick 1: Kommandos mit && Verknüpfen

Mehrere Kommandos lassen sich mit && verknüpfen. Die Shell führt dabei das zweite Kommando nur aus, wenn das erste Kommando erfolgreich war (Exitcode 0):

/usr/local/daten_einsammeln.sh && /usr/local/daten_verarbeiten.sh

Somit kann man die Ausführung des zweiten Scripts (Verarbeiten der Daten) vom Erfolg des ersten Scripts (Sammeln der Daten) abhängig machen.

Trick 2: Abfragen ohne if mit [[ und ]]

Man kann mit [[ und ]] Bedingungen erstellen, ohne ‚if‘ einzusetzen. Ist die Bedingung erfüllt, wird Exitcode 0 zurückgemeldet. In Kombination mit Trick 1 läßt sich so eine bedingte Ausführung realisieren, ohne mit if-then-fi arbeiten zu müssen:

[[ bedingung ]] && kommando

Trick 3: Letzten Tag finden

Sucht man den letzten Freitag im Monat, so kann man das ganz einfach in eine Bedingung packen:

  • date -d ‚1 week‘ ‚+%m‘ ergibt den Monat des heutigen Datums.
  • date -d ‚1 week‘ ‚+%m‘ ergibt den Monat des Datums in genau einer Woche.

Jetzt einfache mathematische Logik: Wenn der Monat in genau einer Woche anders ist als heute, dann ist der heutige Wochentag der letzte in diesem Monat vorkommende.

Anders gesagt: Wenn wir in 7 Tagen in einem anderen Monat sind UND heute Freitag ist, dann ist heute der letzte Freitag im Monat.

Kombination der Tricks

Wir starten einen Cronjob jeden Freitag und nutzen den Trick 2, um das Kommando nur auszuführen, wenn eine Bedingung erfüllt ist. Die Bedingung ist aus Trick 3, wir prüfen nämlich, ob nächsten Freitag schon ein neuer Monat ist. Das sieht so aus:

[[ $(/bin/date -d '1 week' '+%m') -ne $(/bin/date '+%m') ]] && kommando

Somit haben wir einen Cronjob, der jeden Freitag gestartet wird, aber das gewünschte Kommando jeweils nur am letzten Freitag des Monats ausführt.

Hier eine Beispieldatei (/etc/cron.d/abrechnung), die Cron so akzeptieren wird:

MAILTO="cronjobs@butschek.de"
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

#  +--------------------------------------------- Minute      (0-59)
#  |      +-------------------------------------- Stunde      (0-24)
#  |      |      +------------------------------- Kalendertag (1-31)
#  |      |      |      +------------------------ Monat       (1-12)
#  |      |      |      |      +----------------- Wochentag   (1-7 = Mo-So)
#  |      |      |      |      |      +---------- Benutzer    (z.B. root)
#  |      |      |      |      |      |      +--- Kommando    (Pfad + Filename)
#  |      |      |      |      |      |      |

# Abrechnung am Letzten Freitag im Monat starten
  30     10      *      *      5      root   [[ $(/bin/date -d '1 week' '+%m') -ne $(/bin/date '+%m') ]] && /usr/local/abrechnung

#  |      |      |      |      |      |      |
#  |      |      |      |      |      |      +--- Kommando    (Pfad + Filename)
#  |      |      |      |      |      +---------- Benutzer    (z.B. root)
#  |      |      |      |      +----------------- Wochentag   (1-7 = Mo-So
#  |      |      |      +------------------------ Monat       (1-12)
#  |      |      +------------------------------- Kalendertag (1-31)
#  |      +-------------------------------------- Stunde      (0-24)
#  +--------------------------------------------- Minute      (0-59)