• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[Gelöst]Shellskript zum automatischen Verschieben v. Dateien

groove

Newbie
Hallo zusammen,

ich habe eine Dateisammlung mit ca. 100.000 Dateien. Diese sind Grundlage für einen Abgleich mit verschiedenen CD's, die wiederum einige tausend Dateien enthalten.
Meine Sammlung ist in vier Kategorien unterteilt. Die Dateien der CD's sollen nun, abhängig von der Kategorie innerhalb meiner Sammlung, in verschiedene Verzeichnisse verschoben werden.
Zum Abgleich dient ein Windows-Programm, welches meine Sammlung mit den CD's vergleicht. Das Ergebnis wird in eine Textdatei geschrieben, die in etwa wie folgt aussieht:

~/dateisammlung/kategorie1/bild1.jpg
~/cdrom/bild1.jpg
~/cdrom/bild2.jpg
~/cdrom/bild3.jpg
~/dateisammlung/kategorie1/bild2.jpg
~/cdrom/bild4.jpg
~/dateisammlung/kategorie2/bild3.jpg
~/cdrom/bild5.jpg
~/cdrom/bild6.jpg
~/cdrom/bild7.jpg
usw.

Mein Skript erstellt zunächst Zielverzeichnisse, die etwa so aussehen:

~/kat1 (hier sollen Bilder der Kategorie 1 hinein)
~/kat2 (hier sollen Bilder der Kategorie 2 hinein)
usw.

Nun sollen alle Bilder der CD, die unterhalb der Datei "bild1.jpg" aus Kategorie 1 stehen in das Verzeichnis "kat1" verschoben werden, alle Bilder unterhalb der Kategorie 2 in '"kat2", usw.
Die Bilder der Sammlung sollen nicht verschoben werden.

Ich hoffe ich konnte das Problem verständlich genug beschreiben.

FRAGE: Mit welchem Befehl könnte ich am besten die Textdatei auslesen (also als Dateieingabe nutzen) und sicherstellen, dass der move-Befehl abhängig von der letzten Zeile, die das Wort Kategorie enthält ausgeführt wird?

MfG
 
A

Anonymous

Gast
FRAGE: Mit welchem Befehl könnte ich am besten die Textdatei auslesen (also als Dateieingabe nutzen)
read

zB. so
Code:
while read DATEINAME
do
  ..............
  mv $DATEINAME /irgendwohin/
  ...............
done < dateinamensdatei

Vorher überlegen ob du Leerzeichen (oder andere störende Sonderzeichen) in den Dateinamen oder Verzeichnissen hast, wenn ja, dann mal die Suchfunktion benutzen. Haben hier schon einige Musik- und Bildarchive umbenannt und umcopiert.

PS: man basename :wink: das wirst du vieleicht noch brauchen.

robi
 
OP
G

groove

Newbie
Das Auslesen klappt schonmal ganz gut. Allerdings bekomme ich das Verschieben, abhängig von der der Kategorie, noch nicht hin.

Vielen Dank schonmal.
 

TeXpert

Guru
in bashigem-pseudocode :) als Randgerüst:

Code:
for each line
    if is_Kategorie then katdir=make_katdir  
    else mv to kat_dir

function is_Kat()
    WORD=`echo "$IN_LINE" | cut -d"/" -f 2`
    if  $WORD == "cdrom" then return false
    return true

fuction make_katdir()
    DIR=`echo "$IN_LINE" | cut -d"/" -f 3`
    mkdir $DIR
    return $DIR
 
OP
G

groove

Newbie
Was genau bedeutet denn die Variable $IN_LINE. DAmit komme ich irgendwie nicht so recht klar. :oops:
 

TeXpert

Guru
dumm bezeichnet :) stimmt, also:

das soll die gelesene Zeile (also der Dateiname) sein, denn den musst Du ja auseinanderpflücken.
 
OP
G

groove

Newbie
Hier ist mal eine Originaltextdatei, die verarbeitet werden soll:
/home/test/auswertung/sammlung/kat1/bild1.jpg
/home/test/auswertung/cdrom/bild1.jpg
/home/test/auswertung/cdrom/bild2.jpg
/home/test/auswertung/cdrom/bild3.jpg
/home/test/auswertung/sammlung/kat2/bild2.jpg
/home/test/auswertung/cdrom/bild4.jpg
/home/test/auswertung/cdrom/bild5.jpg
/home/test/auswertung/cdrom/bild6.jpg

So weit bin ich mit dem Skript schonmal gekommen. Offensichtlich werden die Zeilen der Datei treffer.txt richtig eingelesen. Problematisch erscheint mir noch die Funktion iskat. Hier schaffe ich es nicht, dass die Variable ABGLEICH auch mal mit dem Wert cdrom gefüllt wird. Offensichtlich versucht die Funktion nicht die Zeilen aus treffer.txt zu lesen, sondern die tatsächlichen Dateien. Da in diesen natürlich weder die Trennzeichen / noch das Wort cdrom enthalten ist, kann die Variable ABGLEICH auch nicht den Wert cdrom zugewiesen bekommen. Folglich ist die die Funktion iskat immer erfüllt

Wo habe ich etwas übersehen?

Code:
#!/bin/bash
while read DATEI; do echo $DATEI; done <treffer.txt
if [ iskat ]
then
ZIEL=/home/test/auswertung/treffer/$DIR
else
cp $DATEI $ZIEL
fi
function iskat()
{
ABGLEICH=´echo "$DATEI" | cut -d"/" -f 5´
if $ABGLEICH == "cdrom"
then return false
return true
fi
}
function make_katdir()
{
     DIR=´echo "$DATEI" | cut -d"/" -f 6´
     mkdir /home/test/auswertung/treffer/$DIR
     return $DIR
}
 

TeXpert

Guru
hey das war pseudocode :) weit entfernt von korrekter Bash-Syntax...

Du musst natürlich auch den Dateinamen an die Funktion übergeben und dort verwenden..... und entsprechend die anderen Funktionen basteln (ohne Funktionen ist es für einen Einsteiger u.u. einfacher...)
 
OP
G

groove

Newbie
Tja...nach drei Tagen Arbeit sieht das ganze nun so aus:
Die Funktion selbst läuft, allerdings bin ich immer noch nicht in der Lage sie auch aufzurufen bzw. die Dateiliste an die Funktion zu übergeben.
Der Haken liegt immer noch in Zeile5. Hier soll folgendes passieren.
Wenn das Ergebnis der Funktion = 1, soll die Datei verschoben werden. Aber die Funktion wird zum Verrecken nicht aufgerufen :-(
Ich habe alle Schreibweisen getestet.
Code:
#!/bin/bash
DATEI=""
ZIEL=""
while read DATEI; do
if is_Kat=1; then mv $DATEI /home/test/auswertung/treffer/$ZIEL 
fi
done<treffer.txt
function is_Kat()
{
     ZIEL=`echo "$DATEI" | cut -d "/" -f5`
     case $ZIEL in
	sammlung) return 0;;
	*) return 1;;
     esac
 }
Hat noch jemand eine Idee?
 
A

Anonymous

Gast
Bevor du eine Funktion aufrufen kannst muss sie der Bash bekannt sein, das heißt, die Funktion muss vor ihrm ersten Aufruf definiert werden. Jetzt schau mal in dein Script wo es bei dir steht.

robi
 

taki

Advanced Hacker
Vielleicht so?

#!/bin/bash
DATEI=""
ZIEL=""
while read DATEI; do
if (is_Kat ($DATEI) == 1); then mv $DATEI /home/test/auswertung/treffer/$ZIEL
fi
done<treffer.txt
function is_Kat()
{
ZIEL=`echo "$DATEI" | cut -d "/" -f5`
case $ZIEL in
sammlung) return 0;;
*) return 1;;
esac
}
 
OP
G

groove

Newbie
robi schrieb:
Bevor du eine Funktion aufrufen kannst muss sie der Bash bekannt sein, das heißt, die Funktion muss vor ihrm ersten Aufruf definiert werden. Jetzt schau mal in dein Script wo es bei dir steht.

robi

Genau das war der Fehler, die Funktion musste nach oben gesetzt werden. Das Skript sieht folglich nun so aus:

Code:
#!/bin/bash
DATEI=""
ZIEL=""
function is_Kat() 
{ ZIEL=`echo "$DATEI" | cut -d "/" -f5`
echo "func wird ausgeführt"
case $ZIEL in
	sammlung) return 1;;
	*) return 0;;
     esac
}
while read DATEI; do
if is_Kat; then mv -v $DATEI /home/test/auswertung/treffer/$ZIEL
fi
echo $ZIEL
done<treffer.txt

Das funktioniert auch trefflich, d. h. die Variable wird auf die richtige Kategorie gesetzt, wenn es eine Datei der Sammlung ist. Die Dateien der CD werden auch verschoben.
ABER: nur in das Verzeichnis treffer. Offensichtlich bleibt die Variable $ZIEL leer beim Verschieben. Ich habe gehofft, er würde sich immer den letzten Wert (sprich aus dem letzten Durchlauf einer Sammlungs-Datei) merken (ich glaube globale Variable ist der richtige Ausdruck).

MfG

Wie schaffe ich es also, dass der Wert für ZIEL solange bestehen bleibt, bis die nächste Datei aus dem Bereich Sammlung geprüft wird.
:)
 
A

Anonymous

Gast
Eigentlich sollte dir jetzt TeXpert weiterhelfen, der hat dir den Floh mit der Funktion ins Ohr gesetzt, aber ich will mal nicht so gemein sein :)

Die Funktion funktioniert nicht sauber, da bei Shellprogrammierung einiges mit den Variablen beachtet werden muss, und es sowas wie globale Variablen in anderen Programmiersprachen hier nicht gibt.
Bei dir sind im Moment die Variable ZIEL und DATEI in der Funktion ganz andere Variable mit ganz anderen Werten wie in der "Hauptfunktion"
Zwar kannst du den Wert der Variable DATEI dort immer auf dem richtigen Wert halten indem du entweder die Variable DATEI exportierst oder aber den Wert bei jedem Aufruf an die Funktion übergibst, und dann in der Funktion den Wert $1 auswertest. Allerdings nützt dir das überhaupt nichts, wenn du in der Funktion die Variable ZIEL setzt, denn diesen Wert kannst du nicht aus der Funktion zurück in die Hauptfunktion nehmen.

Ich würde also die Zeile ZIEL=`echo "$DATEI" | cut -d "/" -f5`direkt unter den Whileschleifen Anfang setzen, und in der Funktion wirklich nur nach dem Vorkommen von Dateisammlung oder Kategorie testen und entsprechend den Rückgabewert setzen.
(Damit wird jetzt die Funktion so klein, dass wir sie auch gar nicht unbedingt als Funktion schreiben müssten, aber damit TeXpert seinen Seelenfrieden hat, lassen wir das mal als Funktion.)

Damit laufen wir aber wieder in eine Falle, jetzt wird die Variable ZIEL jedesmal gesetzt, und zwar meistens falsch.
Also müssen wir die Zeile ZIEL=`echo "$DATEI" | cut -d "/" -f5` abhängig vom Ergebniss der Funktion machen und nur durchlaufen lassen wenn es sich um einen Verzeichnisseintrag handelt, und den eigentlichen Kopiervorgang hinterher aber dann in den elseZeig. Oder verstehe ich deine Steuerdatei nicht richtig, aber du wirst sicher wissen wohin welche Dateien von wo sollen. Einfach mal ausprobieren, dann müsste es fast funktionieren.

so jetzt bist du wieder dran, zu scripten.

robi
 

TeXpert

Guru
bevor hier jetzt noch mehr gejammert wird ;) ich dachte mein Pseudecode schubst Dich in dir richtige Richtung... egal, klar es geht auch ohne Funktionen daher hier mal schnell ohne Funktionstest runtergehackt:

insbesondere kann man das ohne cut machen und ist damit etwas flexibler.
Code:
#!/bin/bash

## script for http://linux-club.de/viewtopic.php?t=33238

DESTINATION=

if [[ -z $1 ]]
then
  echo "failure -> no input"
  exit 1
fi

while read LINE
do
  # extract directoryname
  DIRECTORY=$(basename ${LINE%?`basename $LINE`})
  if [[ "$DIRECTORY" != "cdrom" ]] 
  then
    mkdir "$DIRECTORY"
    DESTINATION=$DIRECTORY
  else
    if [[ -z $DESTINATION ]]
    then 
      echo "failure -> no category!"
      exit 1
    else
      mv "$LINE" "$DESTINATION"
    fi
  fi
done <$1

wenn Du die Sicherheitsnetze weglässt wirds noch kürzer...
 
OP
G

groove

Newbie
Hier ist die (dann doch so einfache) Lösung:
Code:
function do_mv()
{
   zielverzeichnis_neu=`echo $datei | cut -d "/" -f5`
   case $zielverzeichnis_neu in
        sammlung) zielverzeichnis=`echo $datei | cut -d "/" -f6`
                   return ;;
	*)         cp $datei /home/test/auswertung/$zielverzeichnis ;;
     esac
 }
echo $name
while read datei; do
do_mv
done</home/test/auswertung/treffer.txt

Vielen Dank für die super Mithilfe. Ihr habt mir einige Stunden Zeit gespart.
:)
 

TeXpert

Guru
bei diesem Konstukt würde ich aber auf die Funktion verzichteten ;) egal, schau Dir mal meine Alternative mit basename an, dann bist Du unabhängig von der absoluten Pfadlänge (Anzahl Verzeichnisse)
 
Oben