9

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

4 Answers 4

10

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
0
1

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?

3
  • 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, 2010 at 22:20
  • 1
    @jl6 I see, and only files need moving? So C doesn't have to move to B, and B not to A, etc...
    – Pylsa
    Sep 12, 2010 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, 2010 at 23:00
0

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.

6
  • What does find do when there are two files wit the same name?
    – user16115
    Sep 12, 2010 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, 2010 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, 2010 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, 2010 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. Sep 12, 2010 at 23:03
0

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .