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

Why doesn't this work? It is supposed to go through a database and return the entries beginning with a certain letter.

#!usr/bin/perl my $data; my $currentline; my @data; print "Letter? "; $letter = <STDIN>;             open (DATA, "books.txt"); @data = <DATA>; $currentline = 0; foreach $data(@data) { $line = @data[$currentline]; if ($line =~ /^$letter/i) { print "${line}"; } $currentline++ }
It works fine every time I put a plain character between /^ and / instead of $letter. Why doesn't $letter work? Please help me out.

Replies are listed 'Best First'.
Re: Trying to make a search
by mdillon (Priest) on Jun 03, 2000 at 22:27 UTC
    the value of $letter has a line ending on the end. try this:
    print "Letter? "; my $input = <STDIN>; # get rid of line ending chomp($input); # take the first character and escape it if it is a possible metachara +cter my $letter = quotemeta substr $input, 0, 1; open DATA, 'books.txt' or die "Couldn't open books.txt: $!"; my @data = <DATA>; close DATA; foreach my $data (@data) { print $data if $data =~ /^$letter/i; }
Re: Trying to make a search
by lhoward (Vicar) on Jun 03, 2000 at 23:17 UTC
    There are several problems with your code... As mdillon already pointed out you should chomp $input to remove the cr/lf. The other major error is in the following line:
    print "${line}";
    That should read $line and not ${line}.

    Now I've fixed your code (you can see my fixes below) to make it functional, but it still isn't very "perl" looking and has some needless code.

    #!/usr/bin/perl my $ans; my $currentline; my @data; print "Letter? "; $letter = <STDIN>; chomp $letter; open (DATA, "books.txt"); @data = <DATA>; $currentline = 0; foreach $data(@data) { $line = $data[$currentline]; # there are brackets around $cu +rrentline if ($line =~ /^$letter/i) { print $line; } $currentline++ }
    Now here is a much more "perl-looking" implementation:
    #!/usr/bin/perl -w use strict; print "Letter? "; my $letter = <STDIN>; chomp $letter; open (DATA, "books.txt") or die "error opening file $!"; my $line; foreach $line(<DATA>) { if ($line =~ /^$letter/i) { print $line; } } close DATA;
      hehe, on the idea of more "perl looking", in my opinion:
      #/usr/bin/perl -w use strict; print "Letter: "; chomp(my $letter = <STDIN>); open(DATA, "books.txt") or die "error opening file $!"; while(<DATA>) { if (/^$letter/i) { print; } }

      charlie schmidt
      ishamael@themes.org
      www.diablonet.net/~ishamael/
      Since we're already picking nits and playing reductionists; here's removing a little more redundancy. Hopefully without crossing over and becoming obscure (versions involving map()/grep()-combos deliberately left as an excersice for the reader ;D)
      #!/usr/bin/perl -w
      
        use strict;
      
        print "Letter? ";
        my $letter = <STDIN>;   
        chomp $letter;
        open DATA, "books.txt" or die "open(books.txt) returned: $!\n";
        foreach my $line (<DATA>) {
          print $line if $line =~ /^$letter/i;
        }  
        close DATA;
      
        Grep is not obscure. It is much clearer than dumb loops that are really just low level implementations of list operations.
        #!/usr/bin/perl -w use strict; print "Letter? "; chomp(my $letter = <STDIN>); open DATA, "books.txt" or die "open(books.txt) returned: $!\n"; print grep {/^$letter/io} <DATA>;
        Of course, I kind of like something like
        perl -e 'print grep {/^a/io} <>' /usr/dict/words
        Try it, you'll like it too!
      > The other major error is in the following line:
      ><BR > print "${line}";
      >
      > That should read $line and not ${line}.

      Actually, it doesn't make a difference. ${line} is just as good (and sometimes better) as $line in double-quotish contexts.
Re: Trying to make a search
by takshaka (Friar) on Jun 04, 2000 at 05:45 UTC
    This thread has gotten out of hand; but as long as we're going for Perlishly obscure, here's something silly...
    #!/usr/bin/perl -wp use strict; use vars '$letter'; s/^(?!\Q$letter\E).*//is; BEGIN { @ARGV = 'books.txt'; print 'Letter: '; chomp($letter = <STDIN>); }
Re: Trying to make a search
by Anonymous Monk on Jun 04, 2000 at 05:12 UTC
    As long as we're going for perl looking, we might as well go for obscure...
    #/usr/bin/perl -w use strict; print "Letter: "; chomp(my $letter = <STDIN>); open(DATA, "books.txt") || die "error opening file $!"; while(<DATA>) { (/^$letter/i) && print; }

      Please, before posting, read the guidelines and use a </{0,1}CODE> tags around your code, or else it will look like the above, which is not valid perl. For those curious, the poster meant for a <STDIN> to appear in the "chomp" line, and the while line to say while(<DATA>) {

      P.S. I think that

      print if /$letter/i;
      is shorter and a bit clearer.

RE: Trying to make a search
by turnstep (Parson) on Jun 04, 2000 at 19:15 UTC

    I would test the file before STDIN:

    open(DATA, "books.txt" or die "Could not open file: $!\n"; print "Letter: " and chomp (my $letter=<STDIN>); while(<DATA>) { print if /^$letter/io; }
    Note: mdillon is correct below: the ^ was added after his comment.
      this won't fulfill the original author's requirement of matching lines that _begin_ with a certain letter. you're missing the '^' at the beginning of your regular expression.

      also, why does everyone but me just assume in their solution that user will be well-behaved and only enter a single letter? i know we're not writing production code here, but it seems a little odd to me that i was the only one to bother to do 'substr $input, 0, 1'.

      one thing i liked that no one else remembered to do was turnstep's use of the 'o' regular expression modifier to only compile the regexp once.

        also, why does everyone but me just assume in their solution that user will be well-behaved and only enter a single letter? i know we're not writing production code here, but it seems a little odd to me that i was the only one to bother to do 'substr $input, 0, 1'.

        Perhaps it is a bleed-over of Perl's DWIM concept. It seems logical (to me, at least) that if the user inputs a string rather than a single character the program should return all lines beginning with that string. But then I imagine it is just as logical to you that the program should only use the first character as the search prefix.

        In legitimate production code where we are concerned about how many characters the user inputs, it would probably be better to allow only one character--rather than an entire line--to be input in the first place (don't want to confuse those end-users). And since the OP does say 'letter', we'd also reject any input that isn't one.