LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (http://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   Script to move directories based on first letter to a new directory of that letter (http://www.linuxquestions.org/questions/linux-newbie-8/script-to-move-directories-based-on-first-letter-to-a-new-directory-of-that-letter-523632/)

tworkemon 01-28-2007 07:43 PM

Script to move directories based on first letter to a new directory of that letter
 
Basically what i am trying to do is this.

/Blah/A.blah.blah
/Blah/A.blah2.blah2
/Blah/B.blah.blah
/Blah/B.blah2.blah2
/Blah2/A2.blah.blah
/Blah2/A2.blah2.blah2
/Blah2/B2.blah.blah
/Blah2/B2.blah2.blah2

I want to move all the A* directories into /A and all the B* directories into B. Now each one of those have files in them which I would like to move as well.

I've tried
Code:

find . -type d -iname "A*" -exec mv {} /A \;
but that sometimes gives me weird Files/Directories are not found and doesn't create one of the directories but puts the file of that directory in /A vs /A/A.blah.blah .

Any help with this script would be great or how to automate this better.

tworkemon 01-28-2007 08:46 PM

After messing around with this some more, I still havent been able to automate this but this is what I have.
Code:

find . -type d -iname "A*" | cut -f 3 -d "/"| sed '/^[  ]*$/d'| awk '{print "mv " $1 " /A/"}' > move.sh
I am outputting this to a file now because when I do it this way I dont get the funky File/Directory not found message.

I would like to somehow automate this so I dont have to go down the list of A-Z and just run one script and it would do it.

wjevans_7d1@yahoo.co 01-28-2007 08:58 PM

A quick and dirty way would be to have a script containing 26 lines, one for each letter of the alphabet, and on each line, instead of saying:

> move.sh

you say:

>> move.sh

to append any new commands to the end of the already-existing file. If you do this, you'll want an additional three lines at the beginning:

rm -f move.sh
touch move.sh
chmod 700 move.sh

The first of those lines will remove any move.sh that exists from a prior run. The second one creates an empty move.sh which you can execute (because you've changed the protection to 700) without error even if it's empty.

Two improvements are suggested.

The first is to suppress error messages, if you wish. The phrase

2> /dev/null

is recommended. Try experimenting with it in different places in the script, so the only time error messages are suppressed is if there are no files beginning with a certain letter. If you want to suppress errors at a particular point in piped commands, use parentheses (surrounded by spaces). This works by firing off a subshell to get the work done, but you don't need to know that. Example of a piped command where only a certain command in the middle has its error messages suppressed, but the other error messages appear, is this:

alpha | ( beta 2> /dev/null ) | gamma | delta

The other thing I'd suggest (but it's less important) is that if you want to not have 26 separate commands for the 26 letters of the alphabet, try this example:

for xxx in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
do
echo $xxx
done

... and then plug in your command, using $xxx instead of A. Notice that the xxx is not preceded by the dollar sign on the "for" line, but is preceded by the dollar sign elsewhere.

Hope this helps.

tworkemon 01-29-2007 10:45 AM

Ok so I am taking your advice and im still having some problems :(

At first I was trying with
Code:

awk '{print "mv " $1  $i}' > test2
but I couldnt figure out how to get $i to work within awk. So now I am trying something like this. I want the mkdir and mv command to go to file for now so I can make sure it works properly. I am sure its something simple.

Any help would be great ... Thanks :)

Code:

#!/bin/bash
for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
do
Dir=`find . -type d -iname $i* |cut -f 3 -d "/"`
echo "$Dir"| sed '/^[  ]*$/d'
echo mv $Dir $i/
#mkdir /$i >>test2
#mv $DIR /$i >>test2
done

Also I am not sure how to put a debug in case if there is already a directory with one of the $i variables.

tworkemon 01-29-2007 02:28 PM

After further testing, this is what I have now... I noticed that at times my previous code would want to move the main directory prior to moving its subdirectories. I am trying to put in an IF statement to ignore just a mv /ABC /A and only do mv /ABC/ABC2 /A

Code:

#!/bin/bash
for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
do
mkdir -p /$i
find . -type d -iname "$i*" -exec echo "mv {} /$i" \;

if  [ "$#" < "2" ]
        echo "Will not move directory"
else
        #This is where I want to output the find command from above to a file
fi
done


wjevans_7d1@yahoo.co 01-30-2007 04:31 AM

I'm not sure what your current question is (grin), but I'd like to focus on something you said earlier:

Quote:

At first I was trying with

Code:

awk '{print "mv " $1 $i}' > test2
but I couldnt figure out how to get $i to work within awk.
What's in your way is not awk. It's how the shell is interpreting $1 and $i.

What you want the shell to do is substitute a particular letter for the $i, but pass the $1 to awk just as it is.

Try this:

Code:

for xxx in A B C
do
  echo literal: '$xxx'
  echo expanded: "$xxx"
done

See the difference in the output when you tried that? The shell will leave everything enclosed in single quotes alone, but process the dollar signs in double quotes. In either case, the shell doesn't pass the surrounding quotation marks themselves to awk; what awk sees is everything between them (expanded by the shell or left alone, depending on what you tell the shell to do).

What you shouldn't do is this:

Code:

awk "{print "mv " $1 $i}" > test2
for two reasons. The first reason is that you want to pass just one string to awk, the string between the first and final " in that line. But you have two other " in there as well, and bash will match the first with the second, and the third with the fourth.

Try this:

Code:

echo "abc \"def\" ghi"
See what happend with the internal quotes when you tried that? So the following is an improvement ...

Code:

awk "{print \"mv \" $1 $i}" > test2
... but not quite good enough yet. The shell is gonna do the right thing with the $i, but it's gonna mess up the $1. You want the $1 to go to awk without the shell messing with it. What makes the shell spring into action, in this case unwanted action, is the dollar sign. Try this:

Code:

for xxx in A B C
do
  echo expanded: "$xxx"
  echo literal: "\$xxx"
done

See the difference in the output when you tried that? The shell will not process a dollar signs in double quotes if it's "escaped" with a backslash.

So what you want is this:

Code:

awk "{print \"mv \" \$1 $i}" > test2
As you've probably guessed up to now, in figuring out how things are quoted and expanded by the shell before being passed to other programs such as awk, the echo command is your friend.

Incidentally, I'd recommend that you stay away from single-letter variables such as i. A problem arises if later you use a text editor to look for each occurrence of that variable. You'll also find all other i's in the script, even if they're part of a comment or the i in "if", for example. That's why I used xxx in my prior post.

Hope this gets you closer to where you want to be.

tworkemon 01-30-2007 08:23 AM

Thanks, that did help and I did learn from that. I still cant get the $xxx variable to print from the awk command. Ideally I would like it to ignore just the /Blah from the outputs and only use /Blah/A.Blah. I guess I would like to use the Find command to only search through subdirectories and out put full path ??

My current code is this
Code:

#!/bin/bash
for xxx in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
do
#mkdir -p /$xxx
Dir=`find . -type d -iname $xxx* | cut -f 1-3 -d "/"`
echo "$Dir" | sed '/^[  ]*$/d' | awk "{print \"mv \" \$1 $xxx}" >>test2
done

The output is this right now. As you can see it is still missing the $xxx variable at the end of each line. I tried various " and \ with $xxx and couldnt get it. Any additional help would be great.
Code:

mv /Blah
mv /Blah/A.Blah
mv /Blah2
mv /Blah2/A2.Blah

My main goal is to search through
Code:

/Blah
/Blah/A.Blah
/Blah2
/Blah2/A2.Blah

and only Find the subdirectories and move them to a main directory starting with its first letter. So the output would be this
Code:

mv /Blah/A.Blah /A
mv /Blah2/A2.Blah /A


tworkemon 01-30-2007 08:54 AM

Ok after some googling I found the -mindepth variable with Find. I am still now able to output $xxx through awk. With this I dont need the Cut command. I still need the sed to remove empty/blank lines.
Code:

!/bin/bash
for xxx in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
do
#mkdir -p /$xxx
Dir=`find -mindepth 2 -type d -iname $xxx*`
echo "$Dir" | sed '/^[  ]*$/d' | awk "{print \"mv \" \$1 $xxx}" >> test2
done

Code:

mv /Blah/Z.blah
mv /Blah/A.Blah
mv /Blah2/A2.Blah
mv /Blah2/Z2.blah


tworkemon 01-30-2007 07:18 PM

I finally got something I can work with :) Thanks for all the help

Code:

#!/bin/bash

rm movedirs.sh
touch movedirs.sh
chmod +x movedirs.sh

for xxx in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
do
echo "mkdir -p ./$xxx" >> movedirs.sh
find -mindepth 2 -type d -iname "$xxx*" -exec echo "mv {} ./$xxx" \; >> movedirs.sh
done



All times are GMT -5. The time now is 01:55 PM.