brawal128 has asked for the wisdom of the Perl Monks concerning the following question:

I need some advice/help on getting this to work. I'm taking an on-line Perl Training Course and I am stumped on getting my code to work. Please be kind in your responses as I'm new to Perl. I am trying to open a path to a folder on one of my drives and then read the files to get the information and print a statement out from those files. I was instructed to use the <> diamond operator to read the directory and to also read the files. I have setup hashes to set the key and values up or at least that's what I thought I was doing. I did write a script that used opendir to get to the files and print them out. The opendir has been the only thing that worked so far. However, I'm having a hard time creating/reading the files to do something with and create the $hash{$key} = $values. But, is there a way to use <> to open the directory path and then use the <> to read the files and do something with the information. Any help on this would greatly appreciated, as I know there are some Perl Guru's on here.

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $filename; my $FH; my %length; my %songs; opendir DH, "./software/Perl2/Music" or die "Could not open directory! +"; #open(my $file1, "<", "The Bravery-Believe.txt") or die "Could not ope +n file $!"; while($filename = readdir(DH)){ if ($filename =~ /.txt/){ chomp $filename; my ($artist, $song_title) = split '-', $filename, 3; $length{$artist}{$artist} = $artist; $length{$artist}{$song_title} = $song_title; #print $artist,"\n"; #print $song_title,"\n"; } #elsif ($filename =~ /.txt/){ #open FH, "<", $filename or die "Could not open file '$filename' $ +!"; } print Dumper %songs; print Dumper %length; foreach my $artist ( sort keys %length ) { print "$artist is the name of the Artist\n"; } foreach my $album ( sort keys %length ) { print "$album is this now\n"; }

Thanks, Brandon

  • Comment on I need help with opening a directory and reading files in that directory.
  • Download Code

Replies are listed 'Best First'.
Re: I need help with opening a directory and reading files in that directory.
by shadowsong (Pilgrim) on Sep 04, 2015 at 23:18 UTC

    Brandon,

    Welcome to the Monastery. Let's see if we can get your script working as expected - let us break it down one step at a time and solve each problem in turn.

    I'm having a hard time creating/reading the files to do something with and create the $hash{$key} = $values. But, is there a way to use <> to open the directory path

    We do not use <> when opening a directory path. Here is a small script to open a directory, read the contents of that directory and list/print the files therein

    #! perl -slw use strict; opendir DH, "." or die $!; # open current directory while($_ = readdir(DH)){ # don't evaluate special dirs '.' & '..' next if $_ eq "." or $_ eq ".."; print "$_"; # print this file/directory's name } __END__

    One can also use a construct known as a glob to evaluate the contents of a directory

    #! perl -slw use strict; my @files = glob("*"); print "Files matched via glob pattern *: @files\n"; __END__
    and then use the <> to read the files and do something with the information.

    Reading files and doing something with the information is relatively straightforward (but beware this can get hairy real fast based on what your particular environment is like - let's keep it simple for now. It's enough that you've been made aware that what we're doing here is scratching the surface).

    #! perl -slw use strict; opendir DH, "." or die $!; # open current directory while($_ = readdir(DH)){ # don't evaluate special dirs '.' & '..' next if $_ eq "." or $_ eq ".."; print "$_"; # print this file/directory's name # as a by-product of readdir; use the file's stat info # to check whether this is indeed a regular file we can # open for reading/further processing if (-f $_) { open FH, "<$_" or die $!; print "output for file: $_\n"; while (my $line = <FH>) { print $line; } close FH; } } __END__

    Let us know if you have any issues w/ the syntax for using a hash.

    p.s. Careful when using RegExps; certain characters carry special meaning and need to be "escaped". So instead of having

    if ($filename =~ /.txt/)

    You might want to have:

    if ($filename =~ /\.txt/)

      Thanks shadowsong. I was looking through the perldocs and did see a lot of the information that you presented. It does help to get others opinion on this because the instructor that I have was telling me to us @ARGV to open the directory. That confused me even more because I had just did another assignment that parsed two files and did something with those files. But, the files were local to the .pl file I wrote. So, you could get away with that.

      I figured that the glob or opendir would work just you look stated and based on the research I did on the internet and this site. So, I'm assuming that you can replace the $_ default variables with any name that you want. I do like how you explained the information on using stat info to see if it really is a file that we want to look at. I will get to work on fixing what I have created and I will let you guys know how it goes.

      Thanks, Brandon

        So, I'm assuming that you can replace the $_ default variables with any name that you want.

        Yes. But let's be clear. The name of the variable is kind of irrelevant here. The contents of the variable hold the name of the file being processed, which is probably more interesting to you.

        Most solutions, including the one presented to you, will loop through a list of data (like names of files) and then assign the different data to the same variable each time through the loop. Perl provides the default variable $_, but you can declare a variable of your own with your own name if you wish (I'm not sure this is what you meant, but maybe it was).

        A good rule of thumb is that if you need to write out $_, to be clear, you should assign a variable name. Usually you don't need to write it out as it is implicitly handed to functions, e.g. as in:

        while (<$FH>) { print; # prints the current line using $_ implicitly }
        But if you need to or want to use a variable name of your choice, you can. (Also recommended is the 3-argument form of open):
        while ( my $filename = readdir(DH) ) { next if $filename eq '.' or $filename eq '..'; print $filename; if ( -f $filename ) { open my $FH, '<', $filename or die "open: $!\n"; while ( my $line = <$FH> ) { print $line; } close FH or die "close: $!\n"; } }
        But the "any name you want" applies to the variable ... if you are looking for a particular name, as in a piece of data, you'll have to loop through and find it. While you can certainly say:
        if ( -f 'The Bravery-Believe.txt' ) { # do something }
        . . . it wouldn't make sense to do that in a loop through a list, as you'd be asking each element of the list about a certain other element, when they don't "know anything about each other."

        What you might do is check to see if you are dealing with the file you want and skip the rest: then you would have use for the specific filename, which is what I think you might be getting at:

        my $wanted = 'The Bravery-Believe.txt'; while ( my $filename = readdir(DH) ) { next unless $filename eq $wanted; # do something . . . }
        Hope this helps build on shadowsong's answer.

        The way forward always starts with a minimal test.

      Shadowsong, this is what I came up with. The directory is being read and information is being stored in memory, but I'm have having trouble opening the .txt files to read the data in those files. I thought I was doing it the right way by using hashes to keying a value. But, it is still not reading the data in the .txt file. Am I doing something wrong or am I still not reading what is in the file?

      #!/usr/bin/perl -slw use strict; use warnings; use Data::Dumper; my %length; my %songs; opendir DH, "Y://perlscripts2/software/perl2/music" or die $!; # open +current directory while($_ = readdir(DH)){ # don't evaluate special dirs '.' & '..' next if $_ eq "." or $_ eq ".."; if ($_ =~ /\.txt/){ chomp $_; my ($artist, $song_title) = split '-', $_, 3; $length{$artist}{$artist} = $artist; $length{$artist}{$song_title} = $song_title; print "$_"; # print this file/directory's name } # as a by-product of readdir; use the file's stat info # to check whether this is indeed a regular file we can # open for reading/further processing if (-f $_) { open FH, "<$_" or die $!; print "output for file: $_\n"; while (my $line = <FH>) { print "$line\n"; if ($line =~ /\.txt/){ my($album, $minutes, $seconds, $genre) = split ':', $l +ine, 4; $songs{$album} = $album; $songs{$minutes} = $minutes; $songs{$seconds} = $seconds; $songs{$genre} = $genre; print $album, "\n"; } close FH; } } }closedir DH; foreach my $artist ( sort keys %length ) { print "$artist is the name of the Artist\n"; } foreach my $album ( sort keys %songs ) { print "$album is this now\n"; }

      Thanks, Brandon

        Brandon,

        Let's looks at the solution you have come up with; but before we do - just a quick note:

        #!/usr/bin/perl -slw use strict; use warnings;

        The use warnings; pragma is redundant if you supply the -w switch
        --

        Now, looking at your solution we can see that you've opened a target directory and proceeded to read its contents via the excerpt below:

        opendir DH, "Y://perlscripts2/software/perl2/music" or die $!; # open +current directory while($_ = readdir(DH)){ # don't evaluate special dirs '.' & '..' next if $_ eq "." or $_ eq "..";

        What comes next is a bit peculiar..

        if ($_ =~ /\.txt/){ chomp $_; my ($artist, $song_title) = split '-', $_, 3; $length{$artist}{$artist} = $artist; $length{$artist}{$song_title} = $song_title; print "$_"; # print this file/directory's name }

        What you're saying above is to match filenames with ".txt" in them. Are you sure this is what you want to do? Do you have ".txt" embedded within your music filenames?

        Notwithstanding your expectations of the existence of a ".txt" filename which you'd like to process in your music files directory - you should be doing that check within the 2nd if (-f $_) conditional; so you only check legitimate regular files for the ".txt" string.

        Here's a small script I wrote to process some flac files within one of my music directories (which is devoid of any ".txt" files)

        #! perl -slw use strict; use Data::Dumper; $Data::Dumper::Sortkeys = 1; my ($id,$songs_ref,$artists_ref,$albums_ref,%songs,%artists,%albums); opendir DH, "D:/music/Alice In Chains/2001 - Greatest Hits" or die $!; # open current directory while($_ = readdir(DH)){ # don't evaluate special dirs '.' & '..' next if $_ eq "." or $_ eq ".."; # music files in this directory are *.flac files with the # naming style: <track#> - <band> - <album> - <track.flac> # no need to check stat info - we're processing files that # end in .flac (notice the end-of-line match char '$'?) if (m{\.flac$}){ # tip: m{..} is another way of saying /../ # add .flac to the split pattern so that it is not # included in our "<track>" field my ($track_num, $band, $album, $track) = split / \- |\.flac/; # increment our hash ref id so we don't overwrite entries $id++; # ------------------------------------------------------- # Example Using Hash References # ------------------------------------------------------- $songs_ref->{$id}->{TITLE} = $track; $songs_ref->{$id}->{ARTIST} = $band; $songs_ref->{$id}->{FAVOURITE} = 1 if $track eq 'Would'; $songs_ref->{$id}->{TRACK_LENGTH} = undef; # to do later $artists_ref->{$id}->{NAME} = $band; $artists_ref->{$id}->{SONGS}->{TITLE} = $track; $artists_ref->{$id}->{SONGS}->{ALBUM} = $album; $albums_ref->{$id}->{ARTIST} = $band; $albums_ref->{$id}->{NAME} = $album; $albums_ref->{$id}->{SONGS}->{TITLE} = $track; $albums_ref->{$id}->{SONGS}->{TRACK_NUM} = $track_num; # ------------------------------------------------------- # Example Using Hashes # ------------------------------------------------------- $songs{$id}{TITLE} = $track; $songs{$id}{ARTIST} = $band; $songs{$id}{TRACK_LENGTH} = undef; # to do later $artists{$id}{NAME} = $band; $artists{$id}{SONGS}{TITLE} = $track; $artists{$id}{SONGS}{ALBUM} = $album; $albums{$id}{ARTIST} = $band; $albums{$id}{NAME} = $album; $albums{$id}{SONGS}{TITLE} = $track; $albums{$id}{SONGS}{TRACK_NUM} = $track_num; } } closedir DH; # use perl's object data serializer to view our data print Dumper $songs_ref; print Dumper $artists_ref; print Dumper $albums_ref; print Dumper \%songs; print Dumper \%artists; print Dumper \%albums; __END__

        I would like to focus on why it is you're not getting those ".txt" files open for processing, but to do that we need to establish which files are in the directory that you're reading from? Have a look at the example provided and if you find it's still necessary to open and read some kind of ".txt" file - please provide us with the output of dir "Y://perlscripts2/software/perl2/music"

        Good luck!

        opendir DH, "Y://perlscripts2/software/perl2/music" or die $!; while($_ = readdir(DH)){ ... if (-f $_) {

        The file test will work when the value being tested is a path which can be accessed from the current directory where the program was started. As is, $_ contains only the bare file name, devoid of directory name.

        open FH, "<$_" or die $!; print "output for file: $_\n"; while (my $line = <FH>) { ... if ($line =~ /\.txt/){ ... } close FH; }

        A closure of the file handle while still iterating over it will result in "readline() on closed filehandle" (perl 5.16.2) error message, causing premature demise of the program. If the file handle needs to be closed early, then should skip the whole loop via last.

Re: I need help with opening a directory and reading files in that directory.
by wjw (Priest) on Sep 04, 2015 at 23:21 UTC
    There are a few assumptions made here

    • Your file names have a '-' in them
    • You file names end in '.txt'
    • (odd for a music file)

    I ran you code and it is fine as far as it goes. You are not using your '%songs' hash, so that must be just left over. The '%length' hash is fine as close as I can tell.

    What is not happening? Use the debugger (perl -d 'your_perl_script') and watch to see if what you expect to happen does. Hope that helps.

    ...the majority is always wrong, and always the last to know about it...

    A solution is nothing more than a clearly stated problem...

      Thanks for the response wjw. I issue that I was having was, I was able to open the directory and get the files based on the "-" and ".txt", but I was unable to figure out how to open the ".txt" files and pull the information out to print the total amount of music the artists had in each file. Honestly, the class that I'm taking is through Oreilly School of Technology and the instructions on the assignments are really vague. So, I get to spend most of my time trying to figure out what it is they are wanting. We only have a couple of guys at work that deal with Perl and I'm a third that is trying to learn without formal training.

      I appreciate you telling me about the debugger -d 'perlscript.pl'. I would not have been aware of that and I probably would have found out about it much later when I really needed yesterday(If you know what I mean). I figured that I was setting the $values of the $hash{$keys} properly and I was also told that you could add as many keys to a hash as you wanted. Is that true?

      Thanks, Brandon

        I was also told that you could add as many keys to a hash as you wanted. Is that true?
        The short answer is yes.

        With two caveats, though:

        1. Hashes can't have duplicate keys, so that if you have twice the same key in your input, you'll end up with only one entry (probably the last one, depending on how you populate the hash exactly) in your hash.

        2. The other limitation is sheer size of the data compared to the available memory on your computer. If you have tens of millions of hashes entries, you might end up at some point with an "out of memory" exception. To give a very rough idea of the limit, with one of the specific servers I am using at work, I can usually store 5 to 8 million hash entries (depending on the size of the keys and values), but usually not much more. But the limitation is only with the memory available on the platform, not with hashes or with Perl in general.

Re: I need help with opening a directory and reading files in that directory.
by locked_user sundialsvc4 (Abbot) on Sep 05, 2015 at 14:04 UTC

    Although I realize that this is a Perl training course, and that you therefore have been given a particular way to approach this task, I’d like to make one comment about this sort of task “in general.”

    I really like to use file-traversal objects, such as File::Find, to navigate directory trees.   These are both cleaner and OS-indepedent, and they push the “niggling details of how to do what I want to do” out of my code.   But then, if I want to manipulate the files that I have chosen, I have learned (the hard way) to first accumulate a list of file-names into an array, then terminate the search and process the file-names that I have stashed.   Particularly in the Windows environment, there are a limited number of file-search resources and those searches can, in my experience at least, be “messed up” by doing anything that might affect the structure.   (I have even encountered oddities when merely reading the files ...)   The situation gets even more complicated if the process does not have access to every file and/or directory that it can see, and tries to do so and is denied.   So, I have learned to avoid mixing searches with anything-else.   Your Mileage May Vary.™