staging.inyokaproject.org

Bash Skript mit Bedingung für Gruppierung

Status: Ungelöst | Ubuntu-Version: Kubuntu 16.10 (Yakkety Yak)
Antworten |

napterk

Anmeldungsdatum:
17. Februar 2008

Beiträge: 147

Hallo zusammen, ich habe bei http://askubuntu.com/questions/538269/how-to-move-files-based-on-first-word-in-file-name dies Zeile

1
find . -maxdepth 1 -type f -name '*.jpg' -exec perl -e '$a="\Q@ARGV"; $a =~ /(\w+)/; `mkdir -p $1 && mv $a $1`' "{}" \;

gefunden, um eine Liste von Dateien nach dem ersten Wort im Namen in entsprechende Ordner zu sortieren.

Jetzt möchte ich jedoch nicht nach dem ersten Wort, sondern nach dem Beginn der durch Unterstriche getrennt ist, sortieren, entsprechend foo_b_a_r_x und foo_b_a_r_y und foo_b_a_r_z zum Ordner foo_b_a_r. Sorry, verstehe Regex nicht.

Hierbei ist auch die Länge der identischen Beginne der Dateinamen unterschiedlich, so ist es manchmal auch foo_b_a oder foo_b, jedoch denke würde es ausreichen mindestens zwei der drei identische Wörter getrennt durch Unterstriche zu finden. Trotzdem wäre es auch super hier vielleicht eine degressive Schleife zu machen, die absteigend die Gleichheit der Namen abfragt.

Viel wichtiger ist jedoch, dass in der Sortierung eine Bedingung lautet, wenn mindestens drei Dateien mit dem selben Dateinamen beginnen, dann sortieren oder gruppieren zum Ordner, ansonsten in einem Misc Ordner schieben.

Vielen Dank! napterk

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11278

napterk schrieb:

Jetzt möchte ich jedoch nicht nach dem ersten Wort, sondern nach dem Beginn der durch Unterstriche getrennt ist, sortieren, entsprechend foo_b_a_r_x und foo_b_a_r_y und foo_b_a_r_z zum Ordner foo_b_a_r.

Also interessiert dich nur der letzte Unterstrich im Wort? Das kannst du mit Bash-Mitteln lösen - vgl. http://www.tldp.org/LDP/abs/html/string-manipulation.html

1
2
3
4
5
for file in *.jpg; do
    target_dir=${file%_*}
    mkdir -p "$target_dir"
    mv "$file" "$target_dir"
done

Das sähe dann z.B. so aus:

$ ls
foo_b_a_r_x.jpg  foo_b_a_r_y.jpg  foo_b_a_r_z.jpg
$ for file in *.jpg; do
>     target_dir=${file%_*}
>     mkdir -p "$target_dir"
>     mv "$file" "$target_dir"
> done
$ tree
.
└── foo_b_a_r
    ├── foo_b_a_r_x.jpg
    ├── foo_b_a_r_y.jpg
    └── foo_b_a_r_z.jpg

1 directory, 3 files

Sorry, verstehe Regex nicht.

Da kann man Abhilfe schaffen - einfach mal an einem oder mehreren regnerischen Wochenenden die Perl-Dokumentation wälzen: http://perldoc.perl.org/perlre.html

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

napterk schrieb:

... um eine Liste von Dateien nach dem ersten Wort im Namen in entsprechende Ordner zu sortieren.

So ein find- Befehl arbeite nicht eine Liste ab, sondern einen ganzen Verzeichnisbaum. → Du müsstest für Dich zuerst mal klären, ob Du wirklich einen ganzen Verzeichnisbaum abgrasen willst.

...
Viel wichtiger ist jedoch, dass in der Sortierung eine Bedingung lautet, wenn mindestens drei Dateien mit dem selben Dateinamen beginnen, dann sortieren oder gruppieren zum Ordner, ansonsten in einem Misc Ordner schieben.

Das ist ja nun schon wesentlich komplexer ! - da kann man nämlich nicht einfach mehr nur blind die Dateien verschieben, sondern muss vorher erstmal die gesamte Liste aufstellen, und dann, wenn es genügend Dateien von einer Sorte gibt, verschieben.

Eine ganz andere Aufgabenstellung !

Wie gesagt: zuerst müsstest Du uns mal verraten, ob Du

  1. ein Verzeichnis abgrasen willst - oder

  2. einen ganzen Verzeichnisbaum abgrasen willst - oder

  3. eine schon vorhandene Liste auswerten willst.

LG,

track

napterk

(Themenstarter)

Anmeldungsdatum:
17. Februar 2008

Beiträge: 147

track schrieb:

Wie gesagt: zuerst müsstest Du uns mal verraten, ob Du

  1. ein Verzeichnis abgrasen willst - oder

  2. einen ganzen Verzeichnisbaum abgrasen willst - oder

  3. eine schon vorhandene Liste auswerten willst.

Ja, ich denke ich will einen ganzen Verzeichnisbaum abgrasen. Die Dateien liegen in mehreren wahllos unterteilten Ordner, jedoch sind meist die Dateien mit ähnlichem Namen in einem Ordner. Es können aber auch über die Ordnergrenze hinweg ähnliche Dateien verteilt sein.

Sorry, ich hoffe ich hab es jetzt nicht noch unverständlicher gemacht.

Grundsätzlich ist aber ein gewisser Fehleranteil tolerierbar, es muss nicht perfekt sein.

Edit: Hab grad nochmal überlegt, um einfacherer eine Liste aufzustellen, können auch alle Dateien in einen Ordner verschoben werden. Nur das wird dann ein riesiges Verzeichnis!

gruß, napterk

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

napterk schrieb:

Ja, ich denke ich will einen ganzen Verzeichnisbaum abgrasen.

Gut. Also kommen wir um find nicht drum herum.

Da Du allerdings zuerst mal alle Dateinamen einlesen musst (Du willst sie ja pro Gruppe zählen können !), musst Du in jedem Fall daran ein Skript anhängen. Dabei ist es egal - oder Geschmackssache - ob das als Shellskript, Perl, awk oder was weiß ich was geschrieben ist.

Auf jeden Fall solltest Du die Liste der Dateinamen als Null-terminierte Strings übergeben (sonst kommst Du ganz schnell in Teufels Küche !) → also mit find .... -print0
Für den Rest würde ich assoziative Arrays vorschlagen, die lassen sich für diese Aufgabe recht hübsch handhaben, denke ich mal.

So, das ist erstmal die Grobskizze. - jetzt kannst Du ja mal überlegen, welche Programmiersprache Dir am besten liegt, denn das wäre wohl hier das 1. Kriterium. Rein technisch können die genannten ja alle mit assoziativen Arrays umgehen, das ist das Problem nicht.

LG,

track

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

track schrieb:

napterk schrieb:

Ja, ich denke ich will einen ganzen Verzeichnisbaum abgrasen.

Gut. Also kommen wir um find nicht drum herum.

Je nach Sprache heißt das Konstrukt dann u.U. anders. ☺

Da Du allerdings zuerst mal alle Dateinamen einlesen musst (Du willst sie ja pro Gruppe zählen können !), musst Du in jedem Fall daran ein Skript anhängen. Dabei ist es egal - oder Geschmackssache - ob das als Shellskript, Perl, awk oder was weiß ich was geschrieben ist.

Ich würde dafür allerdings gleich eine vernünftige Programmiersprache nehmen. Für die Erkennung gleicher Präfixe, die m.E. im Eingangsposting gefordert wird, baut man am besten eine Baumstruktur auf; hier würde sich ein Trie anbieten.

Auf jeden Fall solltest Du die Liste der Dateinamen als Null-terminierte Strings übergeben (sonst kommst Du ganz schnell in Teufels Küche !) → also mit find .... -print0

Ich würde dann alles in Ruby (oder Python) machen. Dann muss man auch nix übergeben.

Ciao

robert

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

rklm schrieb:

Ich würde dafür allerdings gleich eine vernünftige Programmiersprache nehmen. Für die Erkennung gleicher Präfixe, die m.E. im Eingangsposting gefordert wird, baut man am besten eine Baumstruktur auf; hier würde sich ein Trie anbieten.

Übrigens müsste noch geklärt werden,

  • ob Dateien mit gleichen Präfixen aber in verschiedenen Verzeichnissen gruppiert werden sollen,

  • wo die Zielverzeichnisse für die Gruppierung angelegt werden sollen (lokal in den Verzeichnissen, wo die Dateien gefunden werden oder zentral; die Antwort auf den ersten Punkt beeinflusst, was hier sinnvoll ist),

  • wie mit unterschiedlich langen Präfixen umgegangen werden soll (z.B. "a_b_c", "a_b_d", "a_x" - gruppieren nach "a" oder "a_b" und "a_x" separat - ich tippe nach Deiner Beschreibung auf letztere Variante).

Ich habe mal eine Fingerübung gemacht, die global gruppiert:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/ruby

require 'pathname'

dir = ARGV.shift or abort "ERROR: need a directory argument"

data = Hash.new {|h, k| h[k] = h.clone.clear}

def data.leafs
  inject(0) {|s, (k,v)| s + (Pathname === v ? 1 : v.leafs)}
end

Pathname(dir).find do |f|
  next unless f.file?
  # puts f

  *names, last = f.basename.to_s.scan(/[^_]+/)
  names.inject(data) {|current, m| current[m]}[last] = f
end

# debug:
require 'pp'
pp data

Wenn Du pro Ordner gruppieren willst, dann musst Du nur Zeile 18 ändern:

1
  names.inject(data[f.dirname]) {|current, m| current[m]}[last] = f

Antworten |