Bash: Dateinamen vergleichen und kopieren

Post Reply
Message
Author
rob090225

Bash: Dateinamen vergleichen und kopieren

#1 Post by rob090225 »

Hi,

bin gerade dabei mich in Linux und Bash bzw. Shell Scripting einzuarbeiten. Mit anderen Worten: ein Shell Script-Anfänger.

Ich soll ein Script schreiben, welches mit Hilfe von Übergabeparametern ein Verzeichnis auf Dateien durchsucht. Sofern im selben Verzeichnis eine Datei mit dem selben Dateinamen, aber mit anderer Dateiendung vorhanden ist, sollen die zutreffenden Dateien kopiert werden.

Konkret sieht der Scriptaufruf so aus:
./script.sh <quellverzeichnis> <zielverzeichnis> <dateiendung1> <dateiendung2>
also z.B. ./script.sh /home/test/ /home/test/archiv/ md5 tgz

Beispieldateien im Verzeichnis /home/test/:
20090220_132010_blabla.tgz
20090220_132010_blabla.md5
20090221_151050_dumdidum.tgz
20000221_151050_dumdidum.md5
(Sofern zu einer .tgz-Datei eine zugehörige .md5-Datei existiert, sollen sowohl tgz und md5 kopiert werden)

Mein bisheriger Ansatz wie folgt:

Code: Select all

#!/bin/bash

inputDir=$1
outputDir=$2
fileExt1=$3
fileExt2=$4

var1=&#40; `find $inputDir -name "*.$fileExt1" | sed "s/.$fileExt1//g;s/$inputDir//g"` &#41;    # sed benutzen, um bei der Datei-Trefferliste jeweils die Dateiendung und den Pfad zu entfernen, um den Dateinamen in var1 mit var2 vergleichen zu können.
var1Num=$&#123;#var1&#91;*&#93;&#125;

var2=&#40; `find $inputDir -name "*.$fileExt1" | sed "s/.$fileExt1//g;s/$inputDir//g"` &#41;
var2Num=$&#123;#var2&#91;*&#93;&#125;

# Zählvariablen deklarieren
var1Counter=0
var2Counter=0
copyCounter=0

# Sammeln der Dateiennamen, zu denen sowohl eine .tgz- als auch .md5-Datei vorhanden ist 
for &#40;&#40;var1Counter=0;$var1Counter<$var1Num;var1Counter++&#41;&#41;
do
  for &#40;&#40;var2Counter=0;$var2Counter<$var2Num;var2Counter++&#41;&#41;
  do
    var1=$&#123;var1&#91;var1Counter&#93;&#125;
    var2=$&#123;var2&#91;var2Counter&#93;&#125;
     if &#91; "$var1" = "$var2" &#93;
     then
       copyList&#91;copyCounter&#93;=$&#123;var1&#91;var1Counter&#93;&#125;
      &#40;&#40;copyCounter++&#41;&#41;
     fi
  done
done

let copyCounter=$copyCounter-1
for &#40;&#40;i=0;i<$copyCounter;i++&#41;&#41;
do
  echo copyList&#58; $&#123;copyList&#91;$i&#93;&#125;
  cp -p $&#123;copyList&#91;$i&#93;&#125;.$fileExt1 $inputDir $outputDir
  cp -p $&#123;copyList&#91;$i&#93;&#125;.$fileExt2 $inputDir $outputDir
done
So, nun wird einigen sicherlich schon ins Auge stoßen, dass ich an der Stelle

Code: Select all

var1=&#40; `find $inputDir -name "*.$fileExt1" | sed "s/.$fileExt1//g;s/$inputDir//g"` &#41;
Probleme bekomme: man müsste innerhalb der Variable $inputDir bzw. Parameter $1 die Slashes maskieren.
Hier bin ich irgendwie in einer Sackgasse gelandet...

Wie könnte ich dies bewerkstelligen? Oder anders gefragt: gibt es noch eine besser/einfachere Möglichkeit den Dateinamenvergleich usw. zu bewerkstelligen?

Sry für den langen Text, aber ich wollte die Situation möglichst genau schildern ;)

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#2 Post by Janka »

Ich würde das viel einfacher machen:
Alle Dateien finden, auf die die Maske passt, in eine Schleife abkippen. Dort gucken, ob die Partnerdatei existiert. Wenn ja, beide Dateien kopieren.

Code: Select all

#!/bin/bash 
 
inputDir=$1 
outputDir=$2 
fileExt1=$3 
fileExt2=$4

find $inputDir -name "*.$fileExt1" | while read thisFile
do
  otherFile=$&#40;dirname $thisFile&#41;/$&#40;basename $thisFile .$fileExt1&#41;.$fileExt2
  &#91; -f $otherFile &#93; && cp $thisFile $otherFile $outputDir
done
Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

rob090225

#3 Post by rob090225 »

Ohje, wenn ich mir diese paar Zeilen so anschaue wird mir klar, dass ich es nicht hätte umständlicher machen können.

Allerdings erschließt sich mir nicht so recht die Logik in der while-Schleife.

Nun ergibt sich auch noch die Frage:
Wie kann ich innerhalb der Dateisuche und des Kopiervorgangs (bzw. sollen die Dateien verschoben werden, nicht kopiert) Returncodes abfangen? Ich möchte folgende Returncodes nutzen:
0 -> es wurden Dateien verarbeitet
4 -> es lagen keine Dateien zuer Verarbeitung vor
8 -> Fehler: Schreibschutz auf Datei, die verschoben werden soll
12 -> Input oder Outputverzeichnis nicht gefunden, kein Speicherplatz im Outputdirectory

Bin irgendwie nicht in der Lage das in deine Schleife zu packen :/

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#4 Post by Janka »

rob090225 wrote:Allerdings erschließt sich mir nicht so recht die Logik in der while-Schleife.
find ... gibt eine Liste aus Dateinamen aus, auf die das Muster "*.$fileExt1" passt. Diese Liste wird von der while-Schleife zeilenweise mittels "read" gelesen. Die gelesene Zeile landet in $thisFile (z.B "20090220_132010_blabla.md5"). Daraus konstruiert otherFile=... jeweils den Namen der Partnerdatei (hier: "20090220_132010_blabla.tgz")
Im nächsten Schritt wir geguckt, ob diese Partnerdatei existiert und eine normale Datei ist [ -f $otherFile ], wenn das der Fall ist (&&) werden beide kopiert (cp ...).
Wie kann ich innerhalb der Dateisuche und des Kopiervorgangs (bzw. sollen die Dateien verschoben werden, nicht kopiert) Returncodes abfangen? Ich möchte folgende Returncodes nutzen:
0 -> es wurden Dateien verarbeitet
4 -> es lagen keine Dateien zuer Verarbeitung vor
8 -> Fehler: Schreibschutz auf Datei, die verschoben werden soll
12 -> Input oder Outputverzeichnis nicht gefunden, kein Speicherplatz im Outputdirectory
Irgendein Grund, genau diese Zahlen für die Returncodes zu nehmen?

Fehlerbehandlung in der Shell ist immer etwas komplizierter, weil man ja fast alles mit externen Programm macht und es daher notwendig ist, alle möglichen Fehler dieser Programme zu verarbeiten, wenn man alles abfangen will. Umgekehrt musst du dich aber auch auf die Fehlerbilder beschränken, die du eindeutig ermitteln kannst.

Deine Fehler 8 und 12 kannst du ermitteln, indem du das Ergebnis von "cp" überwachst. Deinen Fehler 4 bekommst du raus, indem du in der Schleife irgendwo eine Variable setzt, und später außerhalb prüfst, ob diese gesetzt wurde.

Im übrigen kann man auch Dateien verschieben oder löschen, die schreibgeschützt sind. Verschieben oder Löschen erfordert nämlich nur das Recht, das Directory zu ändern in dem sich der jeweilige Datei*name* befindet.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

rob090225

#5 Post by rob090225 »

Noch einmal ein dickes danke an dich, auch für die ausführliche Erklärung des Codes!

Und ja, die Returncodes müssen so verwendet werden, da diese in einem späteren Schritt von anderen, bereits vorhandenen, Scripten abgefragt und verwendet werden.

Ich kam nun auf die glorreiche Idee in die Schleife einen Counter einzubauen, um auch die Anzahl der kopierten Dateien zu erfassen:

Code: Select all

counter=0

# Dateien einlesen, vergleichen und kopieren
find $inputDir -name "*.$fileExt1" | while read thisFile
do
  otherFile=$&#40;dirname $thisFile&#41;/$&#40;basename $thisFile .$fileExt1&#41;.$fileExt2
  echo "DIRNAME&#58; $&#40;dirname $thisFile&#41;"
  echo "BASENAME&#58; $&#40;basename $tisFile .$fileExt1&#41;"
  echo "BASENAME&#58; $&#40;basename $tisFile .$fileExt1&#41;.$fileExt2"
  &#91; -f $otherFile &#93; && cp $thisFile $otherFile $outputDir
  &#40;&#40;counter++&#41;&#41;
  echo "INNERHALB SCHLEIFE&#58; $counter"
done

echo "AUSSERHALB SCHLEIFE&#58; $counter"
Problem:
echo "INNERHALB SCHLEIFE: $counter" zeigt mir immer korrekt die Anzahl der kopierten Dateien an, echo "AUSSERHALB SCHLEIFE: $counter" jedoch nicht. Die Variable $counter hat außerhalb der Schleife immer den Wert, den ich ihr ganz zu Beginn des Programms mit counter=0 zuweise... warum? :(

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#6 Post by Janka »

rob090225 wrote: Problem:
echo "INNERHALB SCHLEIFE: $counter" zeigt mir immer korrekt die Anzahl der kopierten Dateien an, echo "AUSSERHALB SCHLEIFE: $counter" jedoch nicht.
Das ist logisch, denn für jede Pipeline " | while ..." muss eine zusätzliche Shell-Instanz gestartet werden. Der Code in der Schleife wird also in einem anderen Prozess ausgeführt als der außerhalb der Schleife. Um Daten zurück nach außerhalb zu geben, musst du daher eine Hilfsdatei benutzen, oder auf die Standardausgabe schreiben und diese außen wieder einlesen.

Code: Select all

# Dateien einlesen, vergleichen und kopieren 
counter=$&#40;find $inputDir -name "*.$fileExt1" | while read thisFile 
do 
  otherFile=$&#40;dirname $thisFile&#41;/$&#40;basename $thisFile .$fileExt1&#41;.$fileExt2 
  echo >&2 "DIRNAME&#58; $&#40;dirname $thisFile&#41;" 
  echo >&2 "BASENAME&#58; $&#40;basename $tisFile .$fileExt1&#41;" 
  echo >&2 "BASENAME&#58; $&#40;basename $tisFile .$fileExt1&#41;.$fileExt2" 
  &#91; -f $otherFile &#93; && cp $thisFile $otherFile $outputDir 
  printf "x"
done | wc -c&#41; 

echo >&2 "AUSSERHALB SCHLEIFE&#58; $counter" 
>&2 gibt auf die Standardfehlerausgabe aus. Nützlich für Debugzwecke. printf hat gegenüber echo den Vorteil, dass man die Ausgabe besser formatieren kann. Unter anderem gibt es ohne Auftrag kein \n aus. "wc -c" zählt die Anzahl der Zeichen ("x"se), die in der Schleife ausgegeben wurden. Dies entspricht der Zahl der Schleifendurchläufe und somit der Dateien.

Shellprogrammierung ist halt oft mal etwas durch die Brust ins Auge...

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

Post Reply