简体   繁体   中英

Move mp3 files from one directory to separate directories by artist, album, song name

I have a backup of all my mp3s files in one folder.

Would there be a bash command to automatically move all files in their own band-album title directories that would be created on the fly?

As you can see in the image, first words before dash are artist names, then comes the album title, then comes the name of the song.

在此处输入图片说明

Example: Artist or band - album title - name of song.mp3.

So in the end, files would be in the following hierarchy.

Artist or band1/    
        album title1/
                name of song.mp3
        album title2/
                name of song.mp3    

Artist or band2/
        album title1/
                name of song.mp3

and so forth.

I don't know of a command to do this directly, but here's how I would solve this (using Perl):

perl -MFile::Path -we 'for my $file (glob "*.mp3") { my ($artist, $album, $title) = split / - /, $file, 3; mkpath "$artist/$album"; my $new = "$artist/$album/$title"; rename $file, $new or warn "$file -> $new: $!\n"; }'

Or slightly more readable:

perl -MFile::Path -we '
    for my $file (glob "*.mp3") {
        my ($artist, $album, $title) = split / - /, $file, 3;
        mkpath "$artist/$album";
        my $new = "$artist/$album/$title";
        rename $file, $new or die "$file -> $new: $!\n";
    }'

I would do it as follows in Bash:

#!/bin/bash

# Set field separator to dash
IFS=-

# Loop over mp3 files
for song in *.mp3; do

    # Read long name into dash separated array
    read -a songinfo <<< "$song"

    # Remove trailing space from band name
    band=${songinfo[0]% }

    # Remove trailing and leading space from album name
    album=${songinfo[1]% }
    album=${album# }

    # Remove leading space from song title
    title=${songinfo[2]# }

    # Make band/album directory, don't complain if they exist already
    mkdir --parents "$band/$album"

    # Move and rename song
    mv "$song" "$band/$album/$title"
done

This changes the IFS variable, but since this will be running in a child process, I didn't bother resetting it to its original value.

It's a bit lengthy due to parameter expansions to remove spaces, and it of course breaks if there is a dash in places other than between band/album/song names. For a Bash solution that also works with dashes in other places, see mklement0's answer .

melpomene's robust and efficient Perl solution is the best solution.

Here's a pure Bash implementation (except for calls to external utilities mkdir and mv ) that is also robust with respect to avoiding false - positives:

for fname in *.mp3; do
  IFS=/ read -r artist album song <<<"${fname// - //}"
  song="${song//// - }"
  mkdir -p "$artist/$album"
  mv "$fname" "$artist/$album/$song"
done
  • ${fname// - //} uses Bash parameter expansion to replace all ( // ) <space>-<space> sequences with ( / ) a / char. each.

    • The reason for this substitution is that Bash's token-splitting mechanism can only split by (one of several) single characters.
    • Auxiliary separator / was chosen because it is guaranteed not to be contained in the input filenames.
  • The result is fed to read via a here-string ( <<< ); $IFS , the internal field separator, is set to the auxiliary separator / in order to split the filename into its constituent tokens.
    Note that by specifying 3 variable names, the last variable specified receives the remainder of the input, even if it contains further instances of the separator.

  • To be safe, song="${song//// - }" then converts instances of / back to <space>-<space> sequences, so as to ultimately preserve the song part unmodified, should it contain such sequences.

  • mkdir -p "$artist/$album" then creates subfolders for the artist and album; note that mkdir -p is a (successful) no-op if the target folder already exists.

  • Finally, the mv command moves the input file to the target folder under its song-title-only name.

bash solution , to also take point into consideration that dash(-) is also present in the artist names( All-Time Quarterback ).

#!/bin/bash
ls *.mp3 > /home/user/mp3songs.list
awk '{print "\""$0"\""}' /home/user/mp3songs.list

while read -r line; do
song="$line"
artist=`echo "$song"|awk -F" - " '{print$1}'`
title=`echo "$song"|awk -F" - " '{print$2}'`
name=`echo "$song"|awk -F" - " '{print$3}'`
mkdir -p "$artist"
mkdir -p "$artist"/"$title"
printf "Moving $song to $artist/$title directory"
mv "$song" "$artist"/"$title"/"$name"
done < /home/user/mp3songs.list

rm /home/user/mp3songs.list

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM