1

I'm trying to sort all mp3 files by artist and name. At the moment, they're in 1 giant file name. E.g Artist - Song name.mp3 I want to convert this to Artist/Song name.mp3

Here's what I've tried so far. In this case, I was using a test file named "hi\ -\ hey":

#!/bin/bash
# filters by all .mp3 extensions in the current working directory 
for f in *.mp3; do
# extract artist and song name and remove spaces
        artist=${f%  -*}
        song=${f#*-  }
#make directory with the extracted artist name and move + rename the file into the directory
        mkdir -p $artist
        mv $f $artist/$song;
done

For some reason, it's creating a directory with the song name instead of the artist in addition to a load of errors:

mv: cannot move 'hey.mp3' to a subdirectory of itself, 'hey.mp3/hey.mp3'
mv: cannot stat 'hi': No such file or directory
mv: cannot stat '-': No such file or directory
mv: 'hey.mp3/hi' and 'hey.mp3/hi' are the same file
mv: cannot stat '-': No such file or directory
teleluck
  • 35
  • 4
  • 1
    Quote your variables. – anubhava Oct 14 '19 at 08:03
  • You might get hints if try: echo mv $f $artist/$song – Yuji Oct 14 '19 at 08:07
  • If your directories for artist name are already created, [perl rename](http://www.unix.com/man-pages.php?section=0&os=Linux&query=prename) is very handy and shorter: `prename 's/ *- */\//' *.mp3`. This will rename all `"artist - name.mp3"` to `"artist/name.mp3"`. – anishsane Oct 14 '19 at 09:28
  • 1
    Try using quotes like this: artist="${f% -*}" // mkdir -p "$artist" // etc. – Meixner Oct 14 '19 at 09:34
  • Consider using `rename` - it can create directories, do dry runs, calculate and split things however you want https://stackoverflow.com/a/49412361/2836621 – Mark Setchell Oct 14 '19 at 09:54

4 Answers4

2

By far the simplest way of doing this is to use rename a.k.a. Perl rename.

Basically, you want to replace the sequence SPACE-DASH-SPACE with a forward slash directory separator, so the command is:

rename --dry-run -p 's| - |/|' *mp3

Sample Output

'Artist - Song name.mp3' would be renamed to 'Artist/Song name.mp3'
'Artist B - Song name 2.mp3' would be renamed to 'Artist B/Song name 2.mp3'

If that looks correct, just remove --dry-run and run it again for real. The benefits of using rename are:

  • it can do a dry-run to test before you run for real
  • it will create all necessary directories with the -p option
  • it will not clobber (overwrite) files without warning
  • you have the full power of Perl available to you and can make your renaming as sophisticated as you wish.

Note that you can install on macOS with homebrew:

brew install rename
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
1

Just in case you don't have the rename utility. A fix on your original script.

for f in *.mp3; do
  # extract artist and song name and remove spaces
  artist=${f%% -*}
  song=${f##*- }
  #make directory with the extracted artist name and move + rename the file into the directory
  echo mkdir -p -- "$artist" && echo mv -- "$f" "$artist/$song"
done
  • Remove the echo If you're satisfied with the output.
Jetchisel
  • 7,493
  • 2
  • 19
  • 18
-1

Assuming there are many files, it's probably much faster to do this using pipes instead of a for loop. This has the additional advantage of avoiding complicated bash-specific syntax and using core unix/linux command line programs instead.

find *-*.mp3 |
  sed 's,\([^-]\+\)\s*-\s*\(.*\),mkdir -p "\1"; mv "&" "\1"/"\2",' |
  bash

Explanation:

This find to find all the files matching -.mp3 in the current directory.

This sed command changes each line to a command string, e.g.:

aaa - bbb.mp3
->
mkdir -p "aaa"; mv "aaa - bbb.mp3" "aaa"/"bbb.mp3"

The bash command runs each of those command strings.

webb
  • 4,180
  • 1
  • 17
  • 26
-1

you can try this.

#!/usr/local/bin/bash
for f in *.mp3
do
artist=`echo $f | awk '{print $1}' FS=-`
song=`echo $f | awk '{print $2}' FS=-`
mkdir -p $artist
mv $artist-$song $song
mv $song ./$artist
done

here I am using two variable artist and song. as your test file name is "hi\ -\ hey" so I change the awk delimiter to "-" to store variable according to it.

we don't need to use awk..by using bash parameter expansion.... it is working.

#!/usr/local/bin/bash
for f in *.mp3
do
artist=`echo ${f%-*}`
song=`echo ${f#*-}`
mkdir -p $artist
mv $artist-$song $song
mv $song ./$artist
done
ABHIShek
  • 34
  • 4
  • You don't need `awk` for simple `cut` like operation. simple [bash parameter expansion](http://mywiki.wooledge.org/BashFAQ/073) will do that in pure bash (like OP has done). The advantage is - not spawning a process when it is not needed. – anishsane Oct 14 '19 at 09:31
  • ya...anishsane... thanks for that .. actually i was working on tcsh shell so forget to use this..here i converted to bash and then posted this.. – ABHIShek Oct 14 '19 at 09:49