Take the 2-minute tour ×
Ask Different is a question and answer site for power users of Apple hardware and software. It's 100% free, no registration required.

My project

I'm creating a bash shell script to execute from the Terminal. Its purpose is to archive lots and lots of project folders. Each folder follows a prescribed nomenclature: [YYYY.MM.DD] - Medium - Client - Project name - details--details - JobNumber. For example: [2006.02.01] - Print - Development - Appeal I - Kids Art Show Insert - D0601-11. These projects are currently one folder. I want to sort them into folders by Client name. There are 7 (internal) clients, so I'm using the following shell script:

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

My problem

My script would mis-parse and mis-file a project named [2006.03.01] - Print - Development - Academics and Accreditation - D0601-08. It would read "Academics" before it ever got to the conditional for the client "Development". As a result, it would be files into "Academics". And I'd have to pick it back out by hand!

My system's advantage

My colleagues and I have been scrupulous about our nomenclature (described above). I know that the Client name falls in between the 2nd and 3rd hyphens.

My question

How to leverage my system's advantage to solve my problem? I want this script to match only the part of the folder name that comes after the first two hyphens and before the third hyphen, i.e., I only want this script to search the Client "field" in the folder name. I keep thinking "regular expressions" but have no idea how to implement them.

Note: I prefer for a solution to augment my current script, rather than replace it. I arrived at it via @patrix on this site and his idea circumvented some errors.

share|improve this question
1  
Why bash? If I could give you a script in another language that worked would you be okay with that? –  Ian C. May 22 at 21:31
    
Good question, @IanC. Bash because it's all I know how to use with the Terminal in Mac OS X. –  Luce Goose May 22 at 22:40
    
bash is a limited language as Unix OSs now come with languages like perl python etc I would write anything longer than 3-4 lines in that as bash is not well behaved –  Mark May 22 at 23:39
1  
*- Academics -* ? –  Jason Salaz May 22 at 23:51
    
I have updated my answer –  markhunte May 23 at 6:06

3 Answers 3

How about using awk with the field separator option -F and separate the field by the hyphen. Then get the third field.

UPDATE

I have updated the code to use the result returned from the awk to place the destination folder. This saves on a lot of code. And also used the separator " - " as Ian C pointed out in the comments.

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

I have also added /"$folder" at the end of the move so the folder itself is moved. you can change this if thats not what you want by removing the "$folder" from the end of the mv command.


You can also cross check against an array of the 7 names so only those folders that correspond will be moved. ( you can insert an else statement where needed)

#!/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
share|improve this answer
    
awk is definitely the way to go if this must absolutely stay in bash. –  Ian C. May 23 at 1:00
    
Also I'd split on ' - ' instead of just '-' –  Ian C. May 23 at 2:00
    
@IanC. Good point I will Adjust that. I actually just got up with the thought in my head of using the thiredfield as a var in the destination folder so that will help. ( And I see while I was asleep you did just that :-) ) –  markhunte May 23 at 4:56

There are several ways to get this done in bash and friends (you could really knock yourself out using sed or awk). A rather simple way is to use cut to get the name of the folder

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

The $(echo $(echo ... )) is a lazy approach to get rid of the leading/trailing space (because cut doesn't support multi-char delimiters).


If you want to knock yourself out with sed you can use

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

instead of cut. This only works if the target folder name doesn't contain a - itself.


Instead of pattern matching you could also use a shell function to encapsulate most of the complexity.

#!/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
share|improve this answer

If you can learn bash you can certainly learn a better language like Ruby to solve this problem.

There is massive room for improvement in what I'm posting, but here's some basic Ruby that does your re-categorizations for you. Some advantages to this Ruby code over your bash code:

  1. It handles the addition of new client fields and automatically moves them according to your preferred archiving scheme
  2. It makes intermediate directories if they don't exist
  3. It halts if there's a problem moving a directory which implies that if it doesn't halt, everything moved successfully

And of course, if you ask me, it's infinitely more read-able and expandable. If you can learn bash, Ruby is much of a stretch and you'll find you can automate better with it than you can with bash.

I tried to stay close to how your bash works so it'd look familiar. As you can see, it's quite a bit terser than that 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
share|improve this answer
    
So you're saying I can execute a Ruby script from the Mac OS X terminal just like I would a bash script? (I'm clearly not a programmer—yet.) And if so, what would I type into the command line to execute said Ruby script? –  Luce Goose May 24 at 16:21
    
Save that to a file, just like your shell script, and then set the execute bit on it by typing: chmod +x <file name>. Now just type the name of the file and it'll run. That magic first line !#/usr/bin/env ruby tells the OS to run the script using Ruby. –  Ian C. May 24 at 17:53
    
And the Mac has Ruby out of the box? @IanC. –  Luce Goose May 25 at 21:09
    
Yes. Ruby is there out of the box. –  Ian C. May 25 at 23:13
    
Is this script likely to break in the event that it encounters a format different that the one described in the #comment? The details--details section varies widely from folder to folder. It often includes special characters like [ ] -. @IanC. –  Luce Goose Jun 10 at 19:21

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.