1

I have a script that is searching a directory tree for music files, extracting the relevant information from the full path name of music files according to specific attributes (Genre, Artist, Track, Album), storing the formatted substrings as elements in an array, then sorting the elements of the array based on the option which was passed on the command line (e.g. sort by album). Everything seems to be working nicely, except the resulting output of the last iteration of the for loop doesn't seem to be getting stored as a new element in the array. After looking up information on Bash arrays, I found that there is no limitation on the array size. So I am left scratching my head as to why every output of every other iteration up until the last is getting stored in the array. If you look at the output below, you can see that the last element should be the track "Tundra".

(more output above ...)

-->./Hip-Hop/OutKast/Stankonia/Toilet Tisha.aif<-- 
GENRE:
Hip-Hop
ARTIST:
OutKast
ALBUM:
Stankonia
TITLE:
Toilet Tisha

-->./Electronic/Squarepusher/Hard Normal Daddy/Cooper's World.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Hard Normal Daddy
TITLE:
Cooper's World

-->./Electronic/Squarepusher/Hard Normal Daddy/Papalon.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Hard Normal Daddy
TITLE:
Papalon

-->./Electronic/Squarepusher/Hard Normal Daddy/Vic Acid.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Hard Normal Daddy
TITLE:
Vic Acid

-->./Electronic/Squarepusher/Go Plastic/Go! Spastic.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Go Plastic
TITLE:
Go! Spastic

-->./Electronic/Squarepusher/Go Plastic/Greenways Trajectory.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Go Plastic
TITLE:
Greenways Trajectory

-->./Electronic/Squarepusher/Go Plastic/My Red Hot Car.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Go Plastic
TITLE:
My Red Hot Car

-->./Electronic/Squarepusher/Feed Me Weird Things/Kodack.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Feed Me Weird Things
TITLE:
Kodack

-->./Electronic/Squarepusher/Feed Me Weird Things/North Circular.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Feed Me Weird Things
TITLE:
North Circular

-->./Electronic/Squarepusher/Feed Me Weird Things/Tundra.aif<--
GENRE:
Electronic
ARTIST:
Squarepusher
ALBUM:
Feed Me Weird Things
TITLE:
Tundra

As you can see, the last iteration in the DEBUG section should be the title "Tundra". However, when I display the contents for array "track_list" every track is printed in the desired format except for "Tundra" (look at attached .png file). Does anyone have any idea as to why this might be? Here is a portion of my script:enter image description here

#more code above ...

#create arrays
declare -a track_list
declare -a directory_contents


#populate directory with files
cd $directory
directory_contents=$(find .  -mindepth 1 -type f)
cd ..


IFS=$'\n'


#store each file of directory in track_list
for music_file in ${directory_contents[*]}; do
    if [ -n "$DEBUG" ] ; then echo "-->$music_file<--"; fi

    this_genre=$(echo $music_file | awk -F"/" '{print $2}')
    this_artist=$(echo $music_file | awk -F"/" '{print $3}')
    this_album=$(echo $music_file | awk -F"/" '{print $4}')
    this_title=$(echo $music_file | awk -F"/" '{print $5}' |\
        awk -F".aif" '{print $1}' || awk -F".mp3" '{print $1}' ||\
        awk -F".wav" '{print $1}' || awk -F".flac" '{print $1}' \
        || awk -F".m4a" '{print $1}')

    function artist_list
    {
    track=$(printf "%-20s\t\t%-30s\t\t%-30s\t\t%-10s\n"\
            "$this_artist" "$this_title" "$this_album" "$this_genre")
    track_list=("${track_list[*]}" $track)
    }

    if [[ $genre = "true" ]] ; then
    track=$(printf "%-10s\t\t%-20s\t\t%-30s\t\t%-30s\n"\
            "$this_genre" "$this_artist" "$this_title" "$this_album")
    track_list=("${track_list[*]}" $track)
    elif [[ $artist = "true" ]] ; then
    artist_list
    elif [[ $album = "true" ]] ; then
    track=$(printf "%-30s\t\t%-20s\t\t%-30s\t\t%-10s\n"\
            "$this_album" "$this_artist" "$this_title" "$this_genre")
    track_list=("${track_list[*]}" $track)
    elif [[ $title = "true" ]] ; then
    track=$(printf "%-30s\t\t%-20s\t\t%-30s\t\t%-10s\n"\
            "$this_title" "$this_artist" "$this_album" "$this_genre")
    track_list=("${track_list[*]}" $track)
    else
    artist_list
    fi

    if [ -n "$DEBUG" ]; then
    echo "GENRE:"
    echo $this_genre
    echo "ARTIST:"
    echo $this_artist
    echo "ALBUM:"
        echo $this_album
    echo "TITLE:"
    echo $this_title
    echo ""
    fi
done


unset IFS


if [[ $genre = "true" ]] ; then
    ./mulib g
elif [[ $artist = "true" ]] ; then
    ./mulib a
elif [[ $album = "true" ]] ; then
    ./mulib m
elif [[ $title = "true" ]] ; then
    ./mulib t
else
    ./mulib
fi

echo "$track_list" | sort
echo ""
5
  • i am not sure which part of the last question addresses the issue at hand. can you be more specific how the last post would address the issue of the last iteration not being stored as a new element in the array? As far as I can see, that is not what I was asking in the last question... Commented May 27, 2011 at 20:10
  • 2
    @Dylan: What Fredrik means is that if one of the answers on the previous question solved it, you should tick the check mark next to the answer. As for this question, that's quite a long bit of code, I recommend first trying to debug it yourself by putting set -x at the top of the script to produce a trace of every line as it's run, and trying to find where the trace output starts looking wrong. Commented May 27, 2011 at 20:17
  • You already got several good advices how to write your homework in 10 lines of code... What you want achieve? Commented May 27, 2011 at 20:21
  • @Gille and jm666 s: Ah, now I understand. I didn't understand what Fredrik meant previously. I am new to this site, so bear with me. Also, I don't mean to inconvenience people here by adding additional reading material, but I do think the problem I am discussing here (why the last iteration of the loop is not being stored in the array) is distinct from the one mentioned in the last post. I tried searching Google, but I couldn't find a solution that addressed this particular problem (I will continue looking). I will take your advice, Gilles, and use the set -x debugging method. Thanks. Commented May 27, 2011 at 22:54
  • I used a modified form of one of the answers from my previous post, which seemed to eliminate this problem (I am not sure how though). I am still not sure why, in the above configuration, the last iteration of the output of the for loop would not be stored as the last element of the array... if there are any ideas, please let me know. Thanks!! Commented May 27, 2011 at 23:45

1 Answer 1

3

Dylan,

Your homework give me an idea to do something with my music library too, and I see than you trying do your homework alone, so here is some comments - how to make an musicsort command :)

The main power of shell programming is in his ability pipelining and decomposing the job into small parts, while these small parts can easily play together.

therefore,
It is not a good idea changing the column orders. See for example ls -l. It does not matter by what you want sort the output (e.g. ls -lt = by time, or ls -ltr = by time but reversed) the column orders remain the same. This way, you can easily pipe output from ls to another command without worrying about the column order. And if you really need changing it, here are already tools what can do it effectively.

My 1st suggestion - don't change the output columns order only sort by them.

Second - the header line. Print put only when really want it. (e.g. for normal output), becuse when you later want pipelining output from your brand new musicsort command, headers will cause much problems. So,

my 2nd suggestion - print header only based on command argument.

When we decomposing your problem, we get:

  1. need some command-line argument handling
  2. need set some defaults, if here are no arguments
  3. need find music files in your music directory
  4. need sort them by criteria
  5. need print them - in sorted order

Skipping 1,2 for now.

finding files in your musicdir is easy.

find "$musicdir" -type f -print

will print out all files recusively. ofc, here can be some cover images and txt lyrics so, need filter them, for example with

find "$musicdir" -type f -print | egrep -i '\.(mp3|aif*|m4p|wav|flac)$'

we have all your music files. Nicely delimited with the '/' character and in order

/path/to/musicdir/genre/artist/album/track.suffix

For the output we need remove the /path/to/musicdir/. It is easy. Here is more way, for example sed.

sed "s:^$musicdir/::;s:\.[^/][^/]*$::"

The above command do two things: 1.) removing the $musicdir path from your filelist, and remove any .suffix too. (like .mp3 .flac etc.). The result is:

genre/artist/album/track

Nice string, clearly separated - so sortable. For the sorting we have the sort command. the sort can sort by any field, and is possible tell him what is the field separator.
E.g.

sort -df -t/ -k2,2

will sort the input separated with '/' by second field (artist). For the -df see man sort.

and finally, we need read the already sorted list of files into variables and print out. (here is ofc another way too). For this bash has the read command. And we must tell bash what is its temporary field separator (IFS), so:

IFS=/; read genre artist album track

and because we have more lines on input need do this in cycle, while we have lines on input.

The final script is here:

musicdir="."
FORMAT="%-20s%-35s%-35s%-35s\n"
sortby=2 #for this example - artist

find "$musicdir" -type f -print |\
egrep -i '\.(aif*|mp3|flac|m4a|wav)$' |\
sed "s:^$musicdir/::;s:\.[^/][^/]*$::" |\
sort -t/ -k$sortby,$sortby | (
IFS=/; while read genre artist album track
do
        printf "$FORMAT" $genre $artist $album $track
done)

As you see, the whole searching, sorting, printing is done in few lines. (parts 3, 4, 5).

For the final, need make some argument handling. I wrote one, it is not 100% ok, but working.

The final script what can handle some arguments, set up defaults, and do the main functionality can look like the next: (ofc, here is possible do zilion optimizations, for example combine the egrep and the sed into only one sed and etc...)

#!/bin/bash
#argument handling - not 100% correct, but working...
while getopts "hHgaltd:" arg
do
case "$arg" in
    g) sortby=1;;
    a) sortby=2;;
    l) sortby=3;;
    t) sortby=4;;
    d) musicdir=$OPTARG;;
    H) header=y;;
    h|?)    echo "Usage: $0 [-d music_dir] [-g|-a|-l|-t] [-H]";
        echo ' -d music_dir = path to your music directory (default ".")'
        echo ' -g|-a|-l|-t = for sorting by Genre/Artist/aLbum/Track (default "-a")'
        echo ' -H print header (default no)'
        exit 1;;
esac
done

#defaults
sortby=${sortby:=2};
musicdir=${musicdir:=.}
FORMAT="%-20s%-35s%-35s%-35s\n"

#header only if want one
if [[ $header == "y" ]]
then
    printf "$FORMAT" genre artist album track
    printf -v line '%*s' 125; echo ${line// /-}
fi

#the main part - search, sort, read into variables, print
find "$musicdir" -type f -print |\
egrep -i '\.(aif*|mp3|flac|m4a|wav)$' |\
sed "s:^$musicdir/::;s:\.[^/][^/]*$::" |\
sort -t/ -k$sortby,$sortby | (
IFS=/; while read genre artist album track
do
    printf "$FORMAT" $genre $artist $album $track
done)

so for example the

$ musicsort -t -H -d .

will produce the output sorted by tracks, print header and music is in current directory

genre               artist                             album                              track                              
-----------------------------------------------------------------------------------------------------------------------------
Electronic          Squarepusher                       Hard Normal Daddy                  Cooper's World                     
Electronic          Squarepusher                       Go Plastic                         Go! Spastic                        
Electronic          Squarepusher                       Go Plastic                         Greenways Trajectory               
Electronic          Squarepusher                       Feed Me Weird Things               Kodack                             
Electronic          Squarepusher                       Go Plastic                         My Red Hot Car                     
Electronic          Squarepusher                       Feed Me Weird Things               North Circular                     
Electronic          Squarepusher                       Hard Normal Daddy                  Papalon                            
Hip-Hop             OutKast                            Stankonia                          Toilet Tisha                       
Electronic          Squarepusher                       Feed Me Weird Things               Tundra                             
Electronic          Squarepusher                       Hard Normal Daddy                  Vic Acid        

As you see, the 3/4 lines are argument handling and other things. The main part is done in few lines.

If you really need change the columns order, can be easily done, by adding few formatting lines...

Sign up to request clarification or add additional context in comments.

2 Comments

Whoa! That is impressive. I got everything working, but your solution is much more intuitive than mine. I will take a look at my solution and revise it with some of your suggestions. Thanks!
Also, apropos the header, since the other requirement for this assignment is to use this utility in conjunction with another programmed in C, I simply just made the header utility in C. Kinda pointless, I guess, but I needed to incorporate C code somehow. I will take your suggestion and add an option for the header. Thanks!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.