Home > Software design >  moving files from a folder into subfolders based on the prefix number with Linux
moving files from a folder into subfolders based on the prefix number with Linux

Time:11-28

I'm relatively new to bash and I have tried multiples solutions that I could find here but none of them seem to be working in my case. It's pretty simple, I have a folder that looks like this:

- images/
   - 0_image_1.jpg
   - 0_image_2.jpg
   - 0_image_3.jpg
   - 1_image_1.jpg
   - 1_image_2.jpg
   - 1_image_3.jpg

and I would like to move these jpg files into subfolders based on the prefix number like so:

- images_0/
   - 0_image_1.jpg
   - 0_image_2.jpg
   - 0_image_3.jpg

- images_1/
   - 1_image_1.jpg
   - 1_image_2.jpg
   - 1_image_3.jpg

Is there a bash command that could do that in a simple way ? Thank you

CodePudding user response:

for src in *_*.jpg; do
  dest=images_${src%%_*}/
  echo mkdir -p "$dest"
  echo mv -- "$src" "$dest"
done

Remove both echos if the output looks good.

CodePudding user response:

I would do this with rename a.k.a. Perl rename. It is extremely powerful and performant. Here's a command for your use case:

rename --dry-run -p '$_="images_" . substr($_,0,1) . "/" . $_'  ?_*jpg

Let's dissect that. At the right end, we specify we only want to work on files that start with a single character/digit before an underscore so we don't do damage trying to apply the command to files it wasn't meant for. Then --dry-run means it doesn't actually do anything, it just shows you what it would do - this is a very useful feature. Then -p which handily means "create any necessary directories for me as you go". Then the meat of the command. It passes you the current filename in a variable called $_ and we then need to create a new variable called $_ to say what we want the file to be called. In this case we just want the word images_ followed by the first digit of the existing filename and then a slash and the original name. Simples!

Sample Output

'0_image_1.jpg' would be renamed to 'images_0/0_image_1.jpg'
'0_image_2.jpg' would be renamed to 'images_0/0_image_2.jpg'
'1_image_3.jpg' would be renamed to 'images_1/1_image_3.jpg'

Remove the --dry-run and run again for real, if the output looks good.

Using rename has several benefits:

  • that it will warn and avoid any conflicts if two files rename to the same thing,
  • that it can rename across directories, creating any necessary intermediate directories on the way,
  • that you can do a dry run first to test it,
  • that you can use arbitrarily complex Perl code to specify the new name.

Note: On macOS, you can install rename using homebrew:

brew install rename

Note: On some Ones, rename is referred to as prename for Perl rename.

CodePudding user response:

What about this:

$ for file in $(ls images/*.jpg | cut -d/ -f2); do
        newdir="images_${file:0:1}"
        [[ ! -d  "$newdir" ]] && mkdir $newdir
        mv "images/$file" "$newdir"
done

Assuming the image filename has single digit prefix, newdir name is formed by appending a suffix of the first character of the filename (${file:0:1} - 0 is the offset and length is 1). And if directory newdir does not exist, create one. Finally, move file from old to new.

If the filename could have a multi-digit prefix like 10_image.jpg, use this:

for file in $(ls images/*.jpg | cut -d/ -f2); do
    newdir="images_$(echo $file | cut -d_ -f1)"
    [[ ! -d  "$newdir" ]] && mkdir "$newdir"
    mv "images/$file" "$newdir"
done
  • Related