Es muss mal wieder Shell sein...

oder ein sehr praktisches Beispiel für iterative Problemlösung mit Unix Kommandos.

Großen Dank an Harald König fürs online stellen dieses Scripts, für die Ideen und das Aufzeigen des Wegs!
Auf der Open Rhein Ruhr hat er in einem Vortrag eine schrittweise Lösung auf Basis der BASH aus einem binären Disk Image mit vFAT Dateisystem gelöschte Cannon RAW Fotographien wieder hergestellt.
Ich habe das Script vom Vortrag von hier und es gefüllt mit meinen Kommentaren…


#!/bin/bash

l=0
# l ist der Vorwert. Wird mit Null initialisiert. Später lesen wir die Positionen und berechnen mittels
# l den Abstand zwischen zwei Fundorten.
# Der Bereich muss das Bild enthalten - möglicherweise aber auch mehr.

grep -a -b -o 'II\*…..CR' sdb-1.img |
# Die Imagedateu heißt sdb-1.img und muss im gleichen Verzeichnis liegen wie das Shell script.
#
# grep -a (treat as ASCII - auch Binärdateien..)
# -b (Zeige den byte offset)
# -o (only matching - zeigt nicht die ganze Zeile - nötig, damit der byte offset korrekt ist..)

( cut -d: -f1 ; echo 999999999999 ) |
# Ausgabe des grep→12345:II*…..CR - wir wollen nur 12345
# cut schneidet aus jeder Zeile alles vor dem Doppelpunkt
# (-d: -Delimeter/Trenner) (-f1 -Feld1 also rechts vor dem ersten :) heraus

while read n x ; do
# eine while-schleife zum Lesen der Positionswerte.
# read.. hat er auf dem Vortrag kurz erwähnt.
# read liest aus einer Liste genau EIN element in eine Shellvarible (hier n).
# Das x ist wichtig für den Fall das die Eingableliste keine ganzzahligen Vielfache
# der Variablen sind. (s.u.)

len=$[ ( n-l )/512 ]
# Berechnung der Länge des Datenblocks(des vermutlichen Bildes) in 512Byte

if [ $len -gt 100000 ] ; then len=100000 fi
# Sollte die Länge des Blocks größer 100000 512Byte Blöcke werden, nimm nur die ersten 100000 * 512Byte.
# Verhindert das bei "Löchern" zwischen den Bildern gigantische Dateien entstehen.

if [ $l -gt 0 ] ; then
# Das IF ist nötig, damit wir erst bei der zweiten Fundstelle beginnen zu extrahieren.
# Erst dann können wir eine Länge berechnen.

dd if=sdb-1.img of=CR bs=512 skip=$[ l/512 ] count=$len
# dd extrahiert den betreffenden Teil des Images und schreibt ihn in die Datei CR
# if= input file
# of= output file
# bs= block size - 512Bytes
# skip= gibt den Startpunkt an, ab dem gelesen werden soll - in 512 Byte blöcken, deshalb l/512
# count= wieviele Blöcke gelsen werden sollen

len=$( echo $( exiftool CR | grep ^Strip | cut -d: -f2 ) + p |dc )
# Ist etwas verwirrend. Len verwendet er hier in einem neuen Kontext als Länge der wirklichen Datei.
# Die "tatsächliche" Länge wird aus den Ausgaben des exiftools gezogen.
# Es gibt da einen Strip Offset und einen Strip Length.
# Beide Werte addiert ergeben die Gesamtlänge des Bildes (mit allen Metadaten)
# Inhalt von $() erkläre ich unten..

dd if=/dev/null of=CR bs=$len seek=1
# Die dd Schere, schneidet das Bild bei $len ab.
# Das ist eine interessante Spielart von dd. Wir lesen von /dev/null ein Dateiende (null byte)
# und schreiben es an die Position $len+1
# (seek sorgt für die +1)
# Alles danach wird implizit weggeworfen.

mv CR $( exiftool CR | grep 'Date/Time Original' | head -1 | cut -d: -f2- | cut -c2- | tr ' :' -. ).cr2
# schließlich wird noch die Datei namens CR mit dem Aufnahmedatum (vFAT kompatibel) versehen.
# mv benennt CR um in das was die PIPE innerhalb von $( ) auswirft. Das ist der Dateiname.
# Alles in $() erkläre ich unten.

fi
# IF ende

l=$n
# und die neue Position in l speichern. Wird damit zur alten Position für den nächsten Durchlauf.
# done schließt die While-Schleife ab.
done

# ENDE… cool…
exit

bash read - liest 3 Variblen, bekommt aber 4 Werte angeboten. Was wird $c enthalten? probierts aus…

  echo 1 2 3 4 | ( read a b c ; echo $a )
  echo 1 2 3 4 | ( read a b c ; echo $ )
  echo 1 2 3 4 | ( read a b c ; echo $b )
  echo 1 2 3 4 | ( read a b c ; echo $c )

Deshalb das x in der Zeile „read n x“. Der „Schrott“ landet damit im $x nicht im $n

Wenn man in der Bash passwd=$(cat /etc/passwd) ausführt speichert man den ganzen Inhalt der Datei /etc/passwd in die Variable passwd. Wie geht das? Bash führt die Kommandos in den Klammern aus und alles was sie zurückgeben, wird in die Variable gespiechert. Das kann man genial mit PIPEs verbinden. Wie oben geschehen.

  len=$( echo $( exiftool CR | grep ^Strip | cut -d: -f2 ) + p |dc )

speichert also in die variable len alles was diese Kette ausgibt:

  echo $( exiftool CR | grep ^Strip | cut -d: -f2 ) + p |dc

Was passiert da?

  echo $(WASauchIMMER) + p | dc

Ein echo gibt wieder die Rückgabes einer anderen Kommondokette und die Zeichenkette „ + p“ an ein Programm namens „dc“

dc ist der Rechner in polnischer Notation. Der rechnet mit Zahlen wie oben… (jemand, der einen HP Taschenrechner hat, kennt das) Wenn also $(WASauchIMMER) z.B. das ausgibt:

  20
  30

Dann macht das Echo daraus:

  20
  30 + p

+ addiert 20 und 30 und „p“ ist das PRINT Kommando von dc. Damit gibt dc dann 50 aus. Das wäre dann das imaginäre Ergebnis das in len oben gespeichert wird.
Aber WASauchIMMER ist ja eigentlich das hier:

  exiftool CR | grep ^Strip | cut -d: -f2

Hier wird exiftool auf den extrahierten Datenblock (der wahrscheinlich das RAW Bild ist) aufgerufen. Es liefert alle Metadaten. Die werden alle in ein „grep“ gepiped. Das lässt nur Zeilen durch, die mit „Strip“ beginnen (^ ist das Symbol für Zeilenanfang). Es werden zwei Zeilen ausgeworfen. Jede Zeile wird mittels cut am Doppelpunkt (-d:) beschnitten und nur das zweite Feld (-f2) ausgelesen. Da steht die Zahl drin.
Wie wir oben wissen werden diese beiden Zahlen aufaddiert und so kommt man auf die wirkliche Bildgröße…
So, jetzt sind wir reif für den hier:

  exiftool CR | grep 'Date/Time Original' | head -1 | cut -d: -f2- | cut -c2- | tr ' :' -.

Was exiftool macht wissen wir.
Was grep hier macht auch.
head ist neu..
grep findet mehrere Zeilen mit 'Date/Time Original', head -1 nimmt nur die erste Zeile in die Ausgabe - Gegenteil von tail.

Dann

  cut -d: -f2-

Kennen wir von Oben schon. Schneidet alles vor dem ersten Doppelpunkt (inklusive) ab. Achtung! das Minus ist wichtig. Es sagt Feld 2 bis zum Zeilenende. Das ist wichtig, da das Datum selbst auch Doppelpunkt (also Feldtrenner) enthält.
Und damit erklärt sich auch dieser hier:

  cut -c2-

-c Zeichen.. alles ab dem zweiten Zeichen (bis zum Zeilenende) - schneidet die Leerzeichen vor dem Datum ab.
So, nun noch das Kommando „tr“ - heißt „translate“ oder Übersetze.
Macht mal folgendes:

  ls -l | tr " " "!"

Sieht anders aus das ls? Fällt Euch was auf? Ja, alle Leerzeichen sind durch Ausrufungszeichen ersetzt. Genau das wird hier genutzt um aus dem Datum also sowas:

  2016:11:20 16:26:54

Einen für vFAT gültigen Dateinamen zu machen.

  tr ' :' -.

Ersetzt Leerzeichen mit Minus und Doppelpunkte mit einfachen Punkten. Probierts aus:

  echo 2016:11:20 16:26:54 | tr ' :' -.

Ausgabe:

  2016.11.20-16.26.54

Toll, oder? Garnicht so schwer. Danke Harald, das ich mir das anschauen durfte, hat großen Spaß gemacht!

  • klaren/klarensshellkochbuch/orrscript1.txt
  • Zuletzt geändert: 2020/03/08 08:58
  • von klaren