Remember that search for “stand” that topped the list with a bunch of older artists? Try that search again without asking for a summary and most of them don’t have “stand” anywhere in the song, artist, or album.
The genre for those songs is Standards. Ask for raw format and you’ll see that. Our search is searching through the entire line, both the stuff we can see and the stuff we can’t.
Currently, our script searches everything for the text we specify. It would be nice to be able to focus our search on just the artist, just the album, or just the song. This way, we can search for songs about Yellow without songs about Yellow without picking up albums that mention Yellow.
If we want to search for all songs that mention “yellow” by an artist whose name contains “joni”, we might use:
./show –artist Joni –song yellow songs.txt
The first step to doing this is to add artist, song, and album to the list of switches.
First, make a list of valid fields to search in:
#options for fields to search in
@validFields = ("artist", "album", "song");
$validFields = englishJoin(", ", "and", @validFields);
Second, add another elif to the switches area:
} elsif (grep(/^$switch$/, @validFields)) {
if ($searchText = shift) {
$searches{$switch} = $searchText;
} else {
print "\nSearching in $switch requires text to search on.\n\n";
help();
exit;
}
We’re storing the search text in an associative array whose key is the field we want to search on.
Because we are now going to be doing multiple searches, we’re going to want a subroutine to do the search. Otherwise, we’ll have to duplicate the “if ($sensitive)” lines for each field we want to search on:
sub match {
my($searchIn) = shift;
my($searchFor) = shift;
my($matched) = 0;
if ($sensitive) {
$matched = $searchIn =~ /$searchFor/;
} else {
$matched = $searchIn =~ /$searchFor/i;
}
return $matched;
}
Change “if ($searchFor = shift) {“ to:
if (%searches) {
Instead of expecting some search text, we’re now checking to see if at least one of the searches has been specified. The if block will only be performed if the associative array called “searches” exists and isn’t empty.
And finally, replace the “if ($sensitive)” blocks with:
foreach $searchField (keys %searches) {
$needle = $searches{$searchField};
$haystack = $$searchField;
$matched = match($haystack, $needle);
last if !$matched;
}
Go to the command line and type:
./show --album yellow --song girl songs.txt
You should get back three songs. The albums “Mellow Yellow” and “Goodbye Yellow Brick Road” both contain at least one song whose title contains “yellow”.
First, we assign the number ‘1’ to the variable $matched. By default, we’re assuming that we found a match.
Next, we loop through each field for which we want to search for text. For each such field:
1. We pull the text we’re looking for back out of the “searches” associative array, and assign that text to the variable $needle.
2. We grab the haystack—the text of the current field, that we want to search through, through a little trick called dereferencing a symbolic reference. Imagine that we are searching for an artist. The %searches array contains “artist” as the key and “some text” as the value. So, $searchField will be “artist”. Now, look up above and see that we have a variable called $artist. If $searchField is “artist”, then $$searchField is the same as $artist. So when we say $haystack = $$searchField, this is the same as saying $haystack = $artist.
3. We set $matched to whether or not $needle can be found in $haystack. If the needle can’t be found, $matched will be false.
4. If $matched is false, there is no need to go any further, so the last line exits if !$matched.
5. At the end of this loop, $matched is either true or false. If it is true, this track matched our search. Otherwise it did not. It failed at least one of the searches requested on the command line.
If $matched can go through all three checks without becoming zero, that means that this song matches our search. Remember that some checks will be skipped, and thus not affect $matched.
Go ahead and play around with some searches. You can find all of the Elton John songs about girls on albums about yellow, with:
./show --album yellow --song girl --artist "Elton John" songs.txt
All of the Elton John songs about girls can be found with:
./show --song girl --artist "Elton John" songs.txt
And, of course, don’t forget to add a line to the help for this item! You’ll need to change the top item:
print "Syntax: show [options] [song files]\n";
And add a few lines to the bottom:
print "\t--$validFields <searchtext>: search in the $validFields field\n";
print "At least one of the search requests must be specified.\n";
That’s it!
Symbolic references can be taken to any level. If $key contains “artist”, $artist contains “Baez”, and $Baez contains “Joan”, then $$key is the same as $artist which is the same as “Baez”. $$$key is the same as $$artist which is the same as $Baez which is the same as “Joan”. Symbolic references are a powerful tool, but can easily make your script confusing. Use them carefully.
- Custom search: New fields
- Now that we have an array of valid fields to search through, it’s easy enough to add new ones. Go ahead and add “genre” to the list of valid fields:
- Custom sort
- If you don’t get dereferencing, go back and take another look at it, because we’re going to do a different kind of dereferencing here. Arrays can have multiple dimensions. So far, all of the arrays we’ve used have had a single dimension: our simple arrays have been a list of single items, and our associative arrays have been simple sets of keys and values. But arrays can have rows and columns much like a spreadsheet; they can even mix simple…
- Backquoting special characters
- Go ahead and look up songs from the album “4”:
- The current script
- This script is beginning to be useful. You should start thinking about the data you work with on a regular basis, and how these techniques could automate what you have to do to this data. Scripts like this can easily be set to run automatically through the use of cron or similar tools.