Shell scripting: seleccione una carpeta basada en parte del nombre del archivo

3

Mi proyecto

Estoy creando un script de shell bash para ejecutar desde la Terminal. Su propósito es archivar montones y montones de carpetas de proyectos. Cada carpeta sigue una nomenclatura prescrita: [YYYY.MM.DD] - Medium - Client - Project name - details--details - JobNumber . Por ejemplo: [2006.02.01] - Print - Development - Appeal I - Kids Art Show Insert - D0601-11 . Estos proyectos son actualmente una carpeta. Quiero clasificarlos en carpetas por nombre de Cliente. Hay 7 clientes (internos), así que estoy usando el siguiente script de shell:

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

Mi problema

Mi secuencia de comandos analizaría incorrectamente y archivaría erróneamente un proyecto llamado [2006.03.01] - Print - Development - Academics and Accreditation - D0601-08 . Se leería "Académico" antes de llegar al condicional para el "Desarrollo" del cliente. Como resultado, sería archivos en "Académicos". ¡Y tendría que retirarlo con la mano!

La ventaja de mi sistema

Mis colegas y yo hemos sido escrupulosos con respecto a nuestra nomenclatura (descrita anteriormente). Sé que el nombre del Cliente se encuentra entre el segundo y el tercer guión.

Mi pregunta

¿Cómo aprovechar la ventaja de mi sistema para resolver mi problema? Quiero que este script coincida solo con la parte del nombre de la carpeta que aparece después de los dos primeros guiones y antes del tercer guión, es decir, solo quiero que este script busque el "campo" del Cliente en la carpeta nombre. Sigo pensando "expresiones regulares" pero no tengo idea de cómo implementarlas.

Nota: Prefiero una solución para aumentar mi script actual, en lugar de reemplazarlo. Llegué a través de @patrix en este sitio y su idea evitó algunos errores.

    
pregunta Crowder 22.05.2014 - 22:43

3 respuestas

3

Hay varias maneras de hacer esto en bash y amigos (realmente podrías dejar de hacerlo usando sed o awk ). Una forma bastante simple es usar cut para obtener el nombre de la carpeta

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

El $(echo $(echo ... )) es un enfoque perezoso para deshacerse del espacio inicial / final (porque cut no admite delimitadores de carácter múltiple).

Si quiere eliminarse con sed , puede usar

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

en lugar de cut . Esto solo funciona si el nombre de la carpeta de destino no contiene un - .

En lugar de la coincidencia de patrones, también podría usar una función de shell para encapsular la mayor parte de la complejidad.

#!/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
    
respondido por el nohillside 23.05.2014 - 07:48
3

¿Qué hay de usar awk con la opción de separador de campo -F y separar el campo con un guión? Luego consigue el tercer campo.

ACTUALIZACIÓN

He actualizado el código para utilizar el resultado devuelto por el awk para colocar la carpeta de destino. Esto ahorra en un montón de código. Y también usé el separador "-" como lo señaló Ian C en los comentarios.

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

También agregué / "$ folder" al final de la mudanza para que la carpeta se mueva. puedes cambiar esto si eso no es lo que quieres eliminando la "carpeta $" del final del comando mv.

También puede realizar una comprobación cruzada contra una matriz de los 7 nombres, de modo que solo se moverán las carpetas que correspondan. (puede insertar una declaración else donde sea necesario)

#!/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
    
respondido por el markhunte 23.05.2014 - 01:33
0

Si puedes aprender bash, ciertamente puedes aprender un mejor lenguaje como Ruby para resolver este problema.

Hay mucho espacio para mejorar en lo que estoy publicando, pero aquí hay algunos Ruby básicos que se encargan de las reclasificaciones. Algunas ventajas de este código Ruby sobre tu código bash:

  1. Maneja la adición de nuevos campos client y los mueve automáticamente de acuerdo con su esquema de archivo preferido
  2. Hace directorios intermedios si no existen
  3. Se detiene si hay un problema al mover un directorio, lo que implica que si no se detiene, todo se moverá con éxito.

Y, por supuesto, si me preguntas, es infinitamente más legible y expandible. Si puedes aprender a bash, Ruby es bastante difícil y verás que puedes automatizar mejor con bash que con bash.

Traté de mantenerme cerca de cómo funciona tu fiesta para que te resulte familiar. Como puedes ver, es un poco más terser que esa fiesta.

#!/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
    
respondido por el Ian C. 23.05.2014 - 03:50

Lea otras preguntas en las etiquetas