0

I have directory structure like this:

/root/1232/medium
/root/1234/medium
/root/1245/medium
/root/1253/medium
/root/1263/medium ...

In each of these folders 1232, 1234, 1245 ... I have files that I would like to move to their medium subdirectories. It means all files from /root/1232/ should be moved to /root/1232/medium and all files from /root/1234/ should be moved to /root/1234/medium. I tried all sorts of find versions but none of them worked. The problem is that I have more then 15000 folders like that otherwise I would move it manually. Thanks.

Dražen
  • 37
  • 1
  • 5

2 Answers2

1

If you're using a somewhat recent version of bash (i.e, not much older than 10 years) you can enable extended globbing by issuing:

shopt -s extglob

Among many other useful functionalities, extended globbing allows you to do negative globbing by invoking !(pattern)

So to solve your problem you might do something like

for folder in /root/12* ; do
  mv $folder/!(medium) $folder/medium/
done

which will iterate over all folders (or any other items for that matter) matching the pattern /root/12* and then for each of them will try and move everything in there as long as it is not called medium to a directory in there which is actually called medium. And that's about it :)

See also this SO question for more information about negative/reverse globbing.

If for whatever reason you don't want to enable extended globbing (or your bash does not support it) you can do some reverse grepping instead:

for basefolder in /root/12* ; do
  for entry in $(ls $basefolder | grep -v medium) ; do
    mv $basefolder/$entry $basefolder/medium
  done
done
Community
  • 1
  • 1
ingenious
  • 966
  • 10
  • 20
0

The command:

find /root/* -mindepth 1 -maxdepth 1 -type f

finds all files (-type f) that are at depth 1 (-mindepth 1 -maxdepth 1) inside the sub-directories of /root. The -mindepth argument forces find to skip the files from /root (they are at depth 0) that would otherwise be found.

Run the command above and verify that it produces the list of files you want to move and nothing else. You can make it more strict to make sure it doesn't return files you don't want to move. For example, if you know all the subdirectories of /root that you want to process start with 12 then you can use find /root/12* ....

When you are sure you have the command that produces the correct list of files, embed it into a for command as follows:

IFS=$'\n'
for i in $(find /root/* -mindepth 1 -maxdepth 1 -type f); do
    mv "$i" "${i%/*}/medium/"
done

Just to make sure it doesn't break anything, put echo in front of mv and run it to see what it will do. If everything is ok then remove the echo and enjoy.

Remark

Make sure you run the above command using bash. The $() construct is bash-specific; other shells may or may not understand it or they may interpret it in a different way. A more-portable way to express the same behaviour is by using backticks (``) instead of $().

How it works

The command substitution operator ($() or ``) runs the command it contains and captures its output. The output of the find command is the list of files to move.

The string produced by $() is then iterated by for; on each iteration, the environment variable i is set with one line of the output of find. Each line of the output of find contains the path of one file we want to move.

Setting the IFS environment variable to $'\n' before the for block ensures the output of find is split on newlines only. The default value of IFS (the "Internal Field Separator") is <space><tab><newline> and it makes the for command incorrectly split the output of find if it finds files that have spaces in their names.

If you are absolutely sure the files you want to move do not have spaces in their names then you can remove the IFS=$'\n' line.

Next, on each iteration a mv command line is built and executed. The ${i%/*} parameter expansion removes from the value of i the shortest right piece (%) that matches the /* pattern. The removed part is the filename and the slash that precedes it in the full file path.

${i%/*}/medium/ basically replaces the file name with medium/. Another possible way to do the same thing is:

mv "$i" "$(dirname $i)/medium/"

It uses the command dirname to extract from $i only the directory (remove the file name).

The first way (${i%/*}) is preferred because it is faster.

Community
  • 1
  • 1
axiac
  • 68,258
  • 9
  • 99
  • 134
  • Axiac, you are the king. I even didn't make the bash. I run it thru the simple terminal and it's all done correctly. This is what I entered `IFS=$'\n' for i in $(find /home/username/public_html/root/* -mindepth 1 -maxdepth 1 -type f); do mv "$i" "${i%/*}/medium/" done`. I really appreciate. Many thanks. – Dražen Feb 19 '17 at 17:58