Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I have a large number of small files, f, arranged in a directory structure like so:

/A/B/C/f

There are 11 directories at the level of A, each with about 100 directories at the level of B, each with about 30 directories at the level of C, each with one file f.

How do I move all files f up one level? For example, given this set of files...

/A/B/C/f1
/A/B/C/f2
/A/B/C/f3
/A/B/C/f4

I want the directory /A/B/ to contain 4 files, f1 through f4. Removing directories C is not necessary.

I'm hoping this is a solved problem, possibly involving find, xargs, and whatnot. Any ideas?

Cheers,

James

share|improve this question
add comment

4 Answers

up vote 8 down vote accepted

It's fairly simple with GNU find (as found on Linux) or any other find that supports -execdir:

find A -type f -execdir mv -i {} .. \;

With a standard find:

find A -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

With zsh:

zmv -Q -o-i 'A/(**/)*/(*)(.)' 'A/$1$2'

If the directory structure always has the same nesting level, you don't need any recursive traversal (but remove empty directories first):

for x in */*; do; echo mv -i "$x"/*/* "$x"/..; done
share|improve this answer
    
Gilles has it! Thanks everybody :D –  jl6 Sep 12 '10 at 23:15
add comment

For that set of files, this would do:

$ cd /A/B/C/
$ mv ./* ../

But I'm expecting that your problem is somewhat more complicated... I can't answer to this... I'm not quite sure how your dir structure is... Could you clarify?

share|improve this answer
    
This will obviously work for any one directory /A/B/C, but as I have about 30000 such directories, I am hoping for a command that can be executed at the top of the hierarchy to take care of them all at once. –  jl6 Sep 12 '10 at 22:20
    
@jl6 I see, and only files need moving? So C doesn't have to move to B, and B not to A, etc... –  BloodPhilia Sep 12 '10 at 22:32
    
That's correct - the operation should only be performed on files, at the lowest level of the hierarchy, and they should only be moved one level up. –  jl6 Sep 12 '10 at 23:00
add comment

My first guess is

$ find A -type f -exec mv {} .. \;  

as long as you don't specify -depth it should be ok. I have NOT tried it so test it first before you commit to it.

share|improve this answer
    
What does find do when there are two files wit the same name? –  bytesum Sep 12 '10 at 22:26
    
Find isn't the problem, the imbedded "mv" command is. Dupe names would not be good as mv is not going to rename, it's going to overwrite. You could use mv -i to help a little, but with 30,000+ files that might be painful. If you need to have that much control over the situation you may need a program written in a scripting language (my choice would be python, YMMV). –  hotei Sep 12 '10 at 22:30
    
I haven't used "backups" with mv, but looking at the man page it looks like that might be a solution to your problem of dupe names. "find A -type f -exec mv --backup numbered {} .. \;" might work (note that's a double dash in --backup) –  hotei Sep 12 '10 at 22:37
    
The find example given will move all files at the lowest level to a directory one above A (I believe the ".." is getting interpreted by the shell before being passed to find/mv?). I need the files to move just one level up in the hierarchy. One further clarification: there will be no duplicate filenames, as long as the files only move ONE level up. –  jl6 Sep 12 '10 at 22:57
    
@hotei: The mv commands are executed in the current directory, so .. doesn't do what you hope. See my answer for solutions. @jl6: the shell has nothing to do with .., that's handled by the kernel. –  Gilles Sep 12 '10 at 23:03
show 1 more comment

If you only want to move files that are in leaf directories (i.e. you don't want to move /A/B/file to /A if B contains subdirectories) then here are a couple of ways to do that:

Both require this

leaf ()
{
    find $1 -depth -type d | sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'
}
shopt -s nullglob

This one works:

leaf A | while read -r dir
do
    for file in "$dir"/*
    do
        parent=${dir%/*}
        if [[ -e "$parent/${file##*/}" ]]
        then
            echo "not moved: $file"
        else
            mv "$file" "$parent"
        fi
    done
done

This will be faster, but it doesn't like empty source directories:

leaf A | while read -r dir
do
    mv -n "${dir}"/* "${dir%/*}"
    remains=$(echo "$dir"/*)
    [[ -n "$remains" ]] && echo "not moved: $remains"
done
share|improve this answer
add comment

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.