Shell-Scripting: Ordner basierend auf einem Teil des Dateinamens auswählen

Mein Projekt

Ich erstelle ein Bash-Shell-Skript, das vom Terminal ausgeführt werden soll. Sein Zweck ist es, viele, viele Projektordner zu archivieren. Jeder Ordner folgt einer vorgeschriebenen Nomenklatur: [YYYY.MM.DD] - Medium - Client - Project name - details--details - JobNumber. Zum Beispiel: [2006.02.01] - Print - Development - Appeal I - Kids Art Show Insert - D0601-11. Diese Projekte sind derzeit ein Ordner. Ich möchte sie nach Kundennamen in Ordnern sortieren. Es gibt 7 (interne) Clients, daher verwende ich das folgende Shell-Skript:

#!/bin/bash

# Go to the Completed Projects folder.
cd /Volumes/communications/Projects/Completed\ Projects/

# Find a folder with a specified string (e.g. "Academics") in its name.
# Move (not copy) the folder to its corresponding sub-folder of the Archived Projects folder. (e.g. /Academics)

for folder in *; do
    if [[ -d "$folder" ]]; then
        if [[ "$folder" == *Academics* ]]; then
            echo "Archiving $folder to Archived Projects → Academics...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/Academics/
        fi
        elif [[ "$folder" == *Admissions* ]]; then
            echo "Archiving $folder to Archived Projects → Admissions...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/Admissions/
        fi
        elif [[ "$folder" == *Alumni* ]]; then
            echo "Archiving $folder to Archived Projects → Academics...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/Alumni/
        fi
        elif [[ "$folder" == *Communications* ]]; then
            echo "Archiving $folder to Archived Projects → Academics...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/Communications/
        fi
        elif [[ "$folder" == *Development* ]]; then
            echo "Archiving $folder to Archived Projects → Academics...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/Development/
        fi
        elif [[ "$folder" == *President* ]]; then
            echo "Archiving $folder to Archived Projects → Academics...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/President/
        fi
        elif [[ "$folder" == *Student\ Life* ]]; then
            echo "Archiving $folder to Archived Projects → Academics...";
            mv "$folder" /Volumes/communications/Projects/Archived\ Projects/Student\ Life/
        fi
    else #Folders that don't match the pattern prompt the use to move them by hand.
        echo "$folder does not have a Department name. Move it by 
done

Mein Problem

Mein Skript würde ein Projekt namens [2006.03.01] - Print - Development - Academics and Accreditation - D0601-08. Es würde "Academics" lauten, bevor es überhaupt zu der Bedingung für den Client "Development" kam. Als Ergebnis wären es Dateien in "Academics". Und ich müsste es von Hand wieder herausholen!

Der Vorteil meines Systems

Meine Kollegen und ich haben unsere (oben beschriebene) Nomenklatur gewissenhaft eingehalten. Ich weiß, dass der Clientname zwischen dem 2. und 3. Bindestrich liegt.

Meine Frage

Wie kann ich den Vorteil meines Systems nutzen, um mein Problem zu lösen? Ich möchte, dass dieses Skript nur mit dem Teil des Ordnernamens übereinstimmt, der nach den ersten beiden Bindestrichen und vor dem dritten Bindestrich steht, dh ich möchte, dass dieses Skript nur das Client-„Feld“ im Ordnernamen durchsucht. Ich denke immer wieder an "reguläre Ausdrücke", habe aber keine Ahnung, wie ich sie implementieren soll.

Hinweis: Ich bevorzuge eine Lösung, die mein aktuelles Skript erweitert, anstatt es zu ersetzen. Ich bin über @patrix auf dieser Seite darauf gekommen und seine Idee hat einige Fehler umgangen.

Warum bash ? Wenn ich Ihnen ein Skript in einer anderen funktionierenden Sprache geben könnte, wären Sie damit einverstanden?
Gute Frage, @IanC. Bash, weil es alles ist, was ich mit dem Terminal in Mac OS X verwenden kann.
bash ist eine begrenzte Sprache, da Unix-Betriebssysteme jetzt Sprachen wie Perl, Python usw. enthalten. Ich würde etwas länger als 3-4 Zeilen schreiben, da sich bash nicht gut benimmt
*- Academics -*?
Ich habe meine Antwort aktualisiert
Ich weiß, was Sie fragen, und dies ist ein Kommentar, keine Antwort. Haben Sie daran gedacht, Labels in Kombination mit find zu verwenden? Es könnte genau das sein, was Sie wollen. Überprüfen Sie @grgarside apple.stackexchange.com/questions/131164/…
Keine der Antworten, die wir gegeben haben, ist an dieser Stelle besonders Mac-spezifisch. Würden Sie erwägen, etwas wie Hazel zu verwenden , um den Ordner zu verwalten?
Ich würde Hazel in Betracht ziehen, aber ich versuche zu lernen, Drehbücher zu schreiben. Auch Hazel scheint ideal für die lokale Speicherung zu sein, aber ich arbeite mit einer Netzwerkfreigabe, auf der Windows 2012 Server ausgeführt wird. @IanC.

Antworten (3)

Es gibt mehrere Möglichkeiten, dies in und Freunden zu erledigen (Sie könnten sich wirklich mit oder bashKO schlagen ). Ein ziemlich einfacher Weg ist, um den Namen des Ordners zu erhaltensedawkcut

if [[ -d "$folder" ]]; then
    target=$(echo $(echo "$folder" | cut -d- -f 3))
    echo "Archiving $folder to Archived Projects → $target...";
    mv "$folder" /Volumes/communications/Projects/Archived\ Projects/$target/
fi

Das $(echo $(echo ... ))ist ein fauler Ansatz, um das führende/nachgestellte Leerzeichen loszuwerden (weil cutTrennzeichen mit mehreren Zeichen nicht unterstützt werden).


Wenn Sie sich damit ausknocken wollensed , können Sie verwenden

    target=$(echo "$folder" | sed -n 's/^[^\-]*-[^\-]*- \([^\-]*\) -.*/\1/p')

statt cut. Dies funktioniert nur, wenn der Name des Zielordners kein -sich selbst enthält.


Anstelle des Musterabgleichs können Sie auch eine Shell-Funktion verwenden, um den größten Teil der Komplexität zu kapseln.

#!/bin/bash

function checkAndMove() {
    if [[ "$1" == *$2* ]]; then
        echo "Archiving $1 to Archived Projects → $2...";
        mv "$1" /Volumes/communications/Projects/Archived\ Projects/$2/
    fi
}

cd /Volumes/communications/Projects/Completed\ Projects/

for folder in *; do
    if [[ -d "$folder" ]]; then
        checkAndMove Academics
        checkAndMove Admissions
        ...
    fi
done

Wie wäre es, wenn Sie awk mit der Feldtrennoption -F verwenden und das Feld durch den Bindestrich trennen. Dann holen Sie sich das dritte Feld.

AKTUALISIEREN

Ich habe den Code aktualisiert, um das vom awk zurückgegebene Ergebnis zu verwenden, um den Zielordner zu platzieren. Das spart viel Code. Und benutzte auch das Trennzeichen "-", wie Ian C in den Kommentaren betonte.

#!/bin/bash

# Go to the Completed Projects folder.
cd /Volumes/communications/Projects/Completed\ Projects/

# Find a folder with a specified string (e.g. "Academics") in its name.
# Move (not copy) the folder to its corresponding sub-folder of the Archived Projects folder. (e.g. /Academics)

for folder in *; do
    if [[ -d "$folder" ]]; then
        thirdfield=`echo "$folder" | /usr/bin/awk -F ' - ' '{print $3}'`;
        echo "Archiving $folder to Archived Projects → $thirdfield...";
        mv "$folder" /Volumes/communications/Projects/Archived\ Projects/"$thirdfield"/"$folder"    
    fi     
done

Ich habe am Ende des Umzugs auch /"$folder" hinzugefügt, damit der Ordner selbst verschoben wird. Sie können dies ändern, wenn Sie dies nicht möchten, indem Sie das "$folder" am Ende des mv-Befehls entfernen.


Sie können auch mit einem Array der 7 Namen abgleichen, sodass nur die entsprechenden Ordner verschoben werden. (Sie können bei Bedarf eine Else-Anweisung einfügen.)

#!/bin/bash

# Go to the Completed Projects folder.
cd /Volumes/communications/Projects/Completed\ Projects/

# Find a folder with a specified string (e.g. "Academics") in its name.
# Move (not copy) the folder to its corresponding sub-folder of the Archived Projects folder. (e.g. /Academics)

# Array of names to check against
ArrayName=(Academics Admissions  Alumni Communications Development President Student)

for folder in *; do
    if [[ -d "$folder" ]]; then
        thirdfield=`echo "$folder" | /usr/bin/awk -F ' - ' '{print $3}'`;

        for var in "${ArrayName[@]}"; do
            # Only move the folder if its key name exists in the arrary
            if [ "${var}" = "$thirdfield" ]; then
                echo "Archiving $folder to Archived Projects → $thirdfield...";
                mv "$folder" /Volumes/communications/Projects/Archived\ Projects/"$thirdfield"/"$folder"   
            fi
        done
    fi
done
awkist definitiv der richtige Weg, wenn dies unbedingt drin bleiben muss bash.
Auch würde ich aufteilen ' - 'statt nur'-'
@IanC. Guter Punkt, ich werde das anpassen. Ich bin gerade mit dem Gedanken aufgestanden, das Thiredfield als Variable im Zielordner zu verwenden, damit das hilft. (Und ich sehe, während ich schlief, hast du genau das getan :-) )

Wenn Sie Bash lernen können, können Sie sicherlich eine bessere Sprache wie Ruby lernen, um dieses Problem zu lösen.

Es gibt viel Raum für Verbesserungen in dem, was ich poste, aber hier ist etwas grundlegendes Ruby, das Ihre Neukategorisierungen für Sie durchführt. Einige Vorteile dieses Ruby-Codes gegenüber Ihrem Bash-Code:

  1. Es verarbeitet das Hinzufügen neuer clientFelder und verschiebt sie automatisch gemäß Ihrem bevorzugten Archivierungsschema
  2. Es erstellt Zwischenverzeichnisse, wenn sie nicht existieren
  3. Es wird angehalten, wenn beim Verschieben eines Verzeichnisses ein Problem auftritt, was bedeutet, dass alles erfolgreich verschoben wurde, wenn es nicht angehalten wird

Und natürlich, wenn Sie mich fragen, ist es unendlich besser lesbar und erweiterbar. Wenn Sie Bash lernen können, ist Ruby eine große Herausforderung, und Sie werden feststellen, dass Sie damit besser automatisieren können als mit Bash.

Ich habe versucht, nah dran zu bleiben, wie Ihr Bash funktioniert, damit es vertraut aussieht. Wie Sie sehen können, ist es ein bisschen knapper als diese Bash.

#!/usr/bin/env ruby

require 'fileutils'

SOURCE = '/Users/ianc/tmp/ad'
DESTINATION = '/Users/ianc/tmp/ad-new'

Dir.chdir(SOURCE)

Dir['**'].each do |f|
  if File.exists?(f) && File.directory?(f)
    # Format: [YYYY.MM.DD] - Medium - Client - Project name - details--details - JobNumber
    date, medium, client, project, details, job_number = f.split(' - ', 6)
    if client
      destination = File.join(DESTINATION, client)
      FileUtils.mkpath destination if !File.exists?(destination)
      destination = File.join(destination, f)
      source = File.join(SOURCE, f)
      puts 'Moving: ' + source + ' --> ' + destination
      FileUtils.mv(source, destination)
    else
      puts 'Skipping: ' + f
    end
  end
end
Sie sagen also, ich kann ein Ruby-Skript vom Mac OS X-Terminal aus ausführen, genau wie ich es mit einem Bash-Skript tun würde? (Ich bin eindeutig kein Programmierer – noch nicht.) Und wenn ja, was würde ich in die Befehlszeile eingeben, um das Ruby-Skript auszuführen?
Speichern Sie das in einer Datei, genau wie Ihr Shell-Skript, und setzen Sie dann das Ausführungsbit darauf, indem Sie Folgendes eingeben: chmod +x <file name>. Geben Sie jetzt einfach den Namen der Datei ein und es wird ausgeführt. Diese magische erste Zeile !#/usr/bin/env rubyweist das Betriebssystem an, das Skript mit Ruby auszuführen.
Und der Mac hat Ruby aus der Box? @IanC.
Ja. Ruby ist sofort einsatzbereit.
Wird dieses Skript wahrscheinlich beschädigt, wenn es auf ein anderes Format stößt als das im #comment beschriebene? Der details--detailsAbschnitt variiert stark von Ordner zu Ordner. Es enthält oft Sonderzeichen wie [ ] -. @IanC.
Es wird bei allen Instanzen von „-“ geteilt – also wird alles, solange das Muster „-“ innerhalb des details--detailsAbschnitts des Namens nicht vorkommt, nicht unterbrochen.
Schade. Manchmal hat es '-' innerhalb des details--detailsAbschnitts. Soll ich das oben angeben?