2

I have a bunch of files like this

5830_1.png
5830_2.png
5830_3.png
5831_1.png
5832_1.png
5832_2.png

I'd like to sort these files all into directories that take the prefix of the file name as the name of the directory.

So I'd end up with

5830/
    5830_1.png
    5831_2.png
    ...etc
5831/
5832/

Is there an easy way to do this?

8

POSIX sh one-liner:

for file in *.png; do mkdir -p -- "${file%%_*}" && \
    mv -- "$file" "${file%%_*}"; done

Explanation:

  • Loop over every .png file in current directory.
  • mkdir with the file prefix; ${file%%_*} removes the first underscore and everything following it; the -p ensures mkdir will be quiet if directory already exists.
  • mv the file into the directory.

Per HalosGhost's suggestion below, if the desired end structure is

5830/
    1.png
    2.png
    3.png
5831/
    1.png
5832/
    1.png
    2.png

the above one-liner can be modified slightly:

for file in *.png; do mkdir -p -- "${file%%_*}" && \
    mv -- "${file}" "${file%%_*}/${file##*_}"; done

This time, the destination is composed of the directory ${file%%_*} ($file before the underscore) and the new file name ${file##*_} ($file after the underscore).

  • Though the OP didn't specify, it looks like they may be expecting the files to be placed at, e.g., 5830/1.png, not 5830/5830_1.png (which, if I'm not mistaken, is where they will end). Perhaps it would be useful to offer that as another one-liner? – HalosGhost Jun 5 '14 at 19:48
  • I tried this, and at first I didn't think it was working, but it just took some time to go through all the thousands of files I had – Houseman Jun 5 '14 at 19:57
1

I've had to do something like this to sort out a bunch of files a few times. I have found that bash variable expansion is your friend.

   ${parameter%word}
   ${parameter%%word}

Remove matching suffix pattern. The word is expanded to produce a pattern just as in pathname expansion.

Initial setup:

-rw-r--r-- 1 falsenames falsenames 0 Jun  5 17:35 5830_1.png
-rw-r--r-- 1 falsenames falsenames 0 Jun  5 17:35 5830_2.png
-rw-r--r-- 1 falsenames falsenames 0 Jun  5 17:35 5830_3.png
-rw-r--r-- 1 falsenames falsenames 0 Jun  5 17:35 5831_1.png
-rw-r--r-- 1 falsenames falsenames 0 Jun  5 17:35 5832_1.png
-rw-r--r-- 1 falsenames falsenames 0 Jun  5 17:35 5832_2.png


$ for i in $(find . -maxdepth 1 -type f); do mkdir -p ${i%_*};mv -v $i ${i%_*}; done
'./5830_3.png' -> './5830/5830_3.png'
'./5831_1.png' -> './5831/5831_1.png'
'./5830_1.png' -> './5830/5830_1.png'
'./5832_1.png' -> './5832/5832_1.png'
'./5832_2.png' -> './5832/5832_2.png'
'./5830_2.png' -> './5830/5830_2.png'

You can get more specific using '-name *png' or some other flags on find, too. I ran that through a find with maxdepth set to only check the specified directory. This way it can be run again without modification if more images are added to that directory. I also used % instead of %% so that this will not break if you have to specify a full directory that includes an underscore, like "/home/user/my_files/pngs"

$ for i in $(find move_files -maxdepth 1 -type f); do mkdir -p ${i%_*};mv -v $i ${i%_*}; done
'move_files/5830_3.png' -> 'move_files/5830/5830_3.png'
'move_files/5831_1.png' -> 'move_files/5831/5831_1.png'
'move_files/5830_1.png' -> 'move_files/5830/5830_1.png'
'move_files/5832_1.png' -> 'move_files/5832/5832_1.png'
'move_files/5832_2.png' -> 'move_files/5832/5832_2.png'
'move_files/5830_2.png' -> 'move_files/5830/5830_2.png'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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