Script de shell: sélection du dossier en fonction d'une partie du nom de fichier

3

Mon projet

Je crée un script shell bash à exécuter à partir du terminal. Son but est d'archiver des tas de dossiers de projets. Chaque dossier fait suite à une nomenclature prescrite: [YYYY.MM.DD] - Medium - Client - Project name - details--details - JobNumber. Par exemple: [2006.02.01] - Print - Development - Appeal I - Kids Art Show Insert - D0601-11. Ces projets sont actuellement un dossier. Je veux les classer dans des dossiers par nom de client. Il y a 7 clients (internes), j'utilise donc le script shell suivant:

#!/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

Mon problème

Mon script mal analyser et mal enregistrer un projet nommé [2006.03.01] - Print - Development - Academics and Accreditation - D0601-08. Il faudrait lire "Universitaires" avant de passer au conditionnel pour le client "Développement". En conséquence, il s'agirait de fichiers dans "Universitaires". Et je devrais le ramasser à la main!

L'avantage de mon système

Mes collègues et moi avons scrupuleusement scrupuleusement respecté notre nomenclature (décrite ci-dessus). Je sais que le nom du client se situe entre le deuxième et le troisième tiret.

Ma question

Comment exploiter les avantages de mon système pour résoudre mon problème? Je veux que ce script ne corresponde qu'à la partie du nom de dossier qui vient après les deux premiers traits d'union et avant le troisième trait d'union, c'est-à-dire que je veux seulement que ce script recherche le "champ" Client dans le nom du dossier. Je continue de penser aux "expressions régulières" mais je ne sais pas comment les mettre en oeuvre.

Remarque: Je préfère une solution pour augmenter mon script actuel plutôt que de le remplacer. J'y suis arrivé via @patrix sur ce site et son idée a permis d'éviter certaines erreurs.

Crowder
la source
1
Pourquoi bash ? Si je pouvais vous donner un script dans une autre langue qui a fonctionné, cela vous conviendrait-il?
Ian C.
Bonne question, @IanC. Bash parce que c'est tout ce que je sais utiliser avec le terminal sous Mac OS X.
Crowder
bash est une langue limitée car les systèmes d'exploitation Unix sont maintenant livrés avec des langages tels que perl python, etc. J'écrirais plus longtemps que 3-4 lignes dans la mesure où bash n'est pas bien comporté
Mark
1
*- Academics -*?
Jason Salaz
J'ai mis à jour ma réponse
markhunte

Réponses:

3

Il y a plusieurs façons de le faire bashavec vos amis (vous pouvez vraiment vous assommer en utilisant sedou awk). Un moyen assez simple consiste à utiliser cutpour obtenir le nom du dossier

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

Il $(echo $(echo ... ))s'agit d'une approche paresseuse pour se débarrasser de l'espace de début / fin (car cutne prend pas en charge les délimiteurs multi-caractères).


Si vous voulez vous assommer, sedvous pouvez utiliser

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

au lieu de cut. Cela ne fonctionne que si le nom du dossier cible ne contient pas de nom -même.


Au lieu d'une correspondance de modèle, vous pouvez également utiliser une fonction shell pour encapsuler l'essentiel de la complexité.

#!/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
nohillside
la source
3

Pourquoi ne pas utiliser awk avec l'option de séparateur de champs -F et séparer le champ par le trait d'union. Ensuite, obtenez le troisième champ.

METTRE À JOUR

J'ai mis à jour le code pour utiliser le résultat renvoyé par l'awk pour placer le dossier de destination. Cela économise beaucoup de code. Et aussi utilisé le séparateur "-" comme Ian C souligné dans les commentaires.

#!/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

J'ai également ajouté / "$ folder" à la fin du déplacement pour que le dossier lui-même soit déplacé. vous pouvez changer cela si ce n'est pas ce que vous voulez en supprimant le "dossier" à la fin de la commande mv.


Vous pouvez également effectuer une vérification croisée avec un tableau des 7 noms afin que seuls les dossiers correspondants soient déplacés. (vous pouvez insérer une autre déclaration si nécessaire)

#!/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
markhunte
la source
awkest certainement la voie à suivre si cela doit absolument rester bash.
Ian C.
De plus, je me suis séparé au ' - 'lieu de'-'
Ian C.
@IanC. Bon point je vais ajuster ça. En fait, je viens juste de penser à utiliser le thiredfield comme variable dans le dossier de destination, ce qui aidera. (Et je vois que pendant que je dormais, c'est ce que vous avez fait :-))
markhunte
0

Si vous pouvez apprendre bash, vous pouvez certainement apprendre une meilleure langue comme Ruby pour résoudre ce problème.

Il y a une énorme marge d'amélioration dans ce que je publie, mais voici quelques ruby ​​de base qui effectuent votre nouvelle catégorisation à votre place. Quelques avantages de ce code Ruby par rapport à votre code bash:

  1. Il gère l'ajout de nouveaux clientchamps et les déplace automatiquement en fonction de votre système d'archivage préféré.
  2. Cela crée des répertoires intermédiaires s'ils n'existent pas
  3. Il s'arrête s'il y a un problème de déplacement d'un répertoire, ce qui implique que s'il ne s'arrête pas, tout a été déplacé avec succès.

Et bien sûr, si vous me le demandez, il est infiniment plus lisible et extensible. Si vous pouvez apprendre le bash, Ruby est plutôt compliqué et vous constaterez que vous pouvez l’automatiser mieux que vous ne le pouvez avec bash.

J'ai essayé de rester proche de la façon dont votre bash fonctionne pour que ça ait l'air familier. Comme vous pouvez le constater, cela fait beaucoup de bruit.

#!/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
Ian C.
la source
Vous dites donc que je peux exécuter un script Ruby à partir du terminal Mac OS X, exactement comme je le ferais avec un script bash? (Je ne suis manifestement pas encore programmeur.) Et si oui, que pourrais-je taper dans la ligne de commande pour exécuter le script Ruby?
Crowder
Sauf que dans un fichier, tout comme votre script shell, puis définissez le bit d' exécution sur elle en tapant: chmod +x <file name>. Maintenant, tapez simplement le nom du fichier et il s'exécutera. Cette première ligne magique !#/usr/bin/env rubyindique au système d'exploitation d'exécuter le script à l'aide de Ruby.
Ian C.
Et le Mac a Ruby sorti de la boîte? @IanC.
Crowder
Oui. Ruby est hors de la boîte.
Ian C.
Ce script risque-t-il de se rompre s'il rencontre un format différent de celui décrit dans le # commentaire? La details--detailssection varie beaucoup d'un dossier à l'autre. Il comprend souvent des caractères spéciaux comme [ ] -. @IanC.
Crowder