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

I would like to call a sub where the sub name is the same as the changing file name (as I read each file in the directory). For example, I would like to call sub "allergies" if I am reading the allergies file. I would like to call sub "immunizations" if I am reading the immunizations file. I could add a lot of if statements but there must be a clean way to to accomplist this task. Does anyone have an idea?

The line in the code below is "$file();"

Thanks, Kevin
# version 1.0 use warnings; use strict; use Shell; my $dir = 'input_files'; die "can't opendir $!" unless opendir DIR, $dir; while (defined(my $file = readdir DIR)) { do { print "The directory and file are $dir/$file\n"; die "Can't open input file $!" unless open IN, "< $dir/$file"; use File::Glob (); while (defined($_ = glob(' IN '))) { # print "Hello $_"; } while (<IN>){ # Reading contents of file next if (/^#/); $file(); ##### File name can be many things print "Bye " . $_ ; } }; } closedir DIR; sub allergies { print "in allergies\n"; } sub immunizations { print "in immunizations\n"; }

Replies are listed 'Best First'.
Re: How to call a Sub / Function with changing name
by choroba (Cardinal) on Oct 28, 2010 at 22:19 UTC
    I would use a dispatch table:
    my %function = ( allergies => sub { print "in allergies\n" }, immunizations => sub { print "in immunizations\n" }, ); ... $function{$file}->() if exists $function{$file};

    (Untested)
    The empty while part can be deleted, its condition is probably wrong anyway.
      It worked! Thank you.
      # version 1.0 use warnings; use strict; use Shell; my %function = ( allergies => sub { funct( "hello" ) }, immunizations => sub { print "in immunizations\n" }, ); my $dir = 'input_files'; die "can't opendir $!" unless opendir DIR, $dir; while (defined(my $file = readdir DIR)) { do { # print "The directory and file are $dir/$file\n"; die "Can't open input file $!" unless open IN, "< $dir/$file"; use File::Glob (); while (defined($_ = glob(' IN '))) { # print "Hello $_"; } while (<IN>){ # Reading contents of file next if (/^#/); $function{$file}->() if exists $function{$file}; # print "Bye " . $_ ; } }; } closedir DIR; sub funct{ my ($temp) = @_; print "$temp\n"; }
      output:
      hello hello hello in immunizations
      I have never heard of a dispatch table but I like it. I would like to pass a filehandle to the function but I have not gotten it to work. Can it be done? Can variables be passed?
      my %function = ( allergies => sub { print_allergies ( my $var) }, immunizations => sub { print "in immunizations\n" }, ); my $dir = 'input_files'; die "can't opendir $!" unless opendir DIR, $dir; while (defined(my $file = readdir DIR)) { do { # print "The directory and file are $dir/$file\n"; die "Can't open input file $!" unless open IN, "< $dir/$file"; use File::Glob (); while (defined($_ = glob(' IN '))) { # print "Hello $_"; } # print_allergies (*IN); $function{$file}->() if exists $function{$file}; }; } closedir DIR; sub print_allergies{ local (*FH) = @_; while (<FH>){ next if (/^#/); print $_ ; } }
      Allergy file looks like this
      # Input file for allergies # Date, Diagnosed By, Type, allergy, reaction,specifics 2009-05-16,Children's Hospital Boston,drugs,penicillin,Blue rash,This +only happens on weekends 2009-05-17,Boston Medical Group,drugs,Vitamin B,Rash on torso,This hap +pens after 9PM 2009-05-17,Children's Hospital,food,Diary,Upset stomach and gas, Happe +ns after drinking whole milk

        You can pass variables and file handles to subroutines. See perlsub for details. Here is an example:

        use strict; use warnings; my %function = ( allergies => \&print_allergies, immunizations => sub { my ($fh, $dir) = @_; print "\nin immunizations from directory $dir\n"; }, ); my $dir = 'input_files'; opendir DIR, $dir or die "$dir: $!"; while (defined(my $file = readdir DIR)) { next if($file =~ /^\.+$/); print "The directory and file are $dir/$file\n"; open my $fh, '<', "$dir/$file" or die "$dir/$file: $!"; $function{$file}->($fh, $dir) if exists $function{$file}; } closedir DIR; sub print_allergies{ my ($fh, $dir) = @_; print "\nprinting allergies from directory $dir\n"; while (<$fh>){ next if (/^#/); print $_ ; } }

        update: added example of passing a variable other than a file handle.

      Wow. Thanks! I'll try it. Kevin
Re: How to call a Sub / Function with changing name
by Marshall (Canon) on Oct 28, 2010 at 22:30 UTC
    Make a dispatch table:
    #!/usr/bin/perl -w use strict; my %dispatch = ( 'immunizations' => \&immunizations, 'allergies' => \&allergies); sub immunizations { print "inside immunizations\n"; } sub allergies { } $dispatch{'immunizations'}->(); #prints: inside immunizations
Re: How to call a Sub / Function with changing name
by kcott (Archbishop) on Oct 29, 2010 at 01:08 UTC

    If that's for a one-off script, you appear to have your answer. For a solution that's to be used in the longer term, you might consider something along the lines of the following skeleton code:

    In a module, say Medical.pm:

    package Medical; sub new { ... } sub allergies { ... } sub immunizations { ... }

    Then, in your script:

    use Medical; my $medical = Medical->new(); ... $medical->$filename(); ...

    When you later need to process a new file, say diseases, you don't need to update a dispatch table or change your script in any way. Just add sub diseases { ... } to Medical.pm.

    -- Ken

Re: How to call a Sub / Function with changing name
by aquarium (Curate) on Oct 29, 2010 at 02:31 UTC
    there are places to use dispatch tables, closures, or even self modifying code in rare instances. but sometimes doing these things can cloud further development of code, which should just be written in a clearer modular fashion. Case in point deciding sub names depending on input filename, is probably not the best of things to do. it's implying semantics due to a particular filename. should (i think) be explicit instead, e.g. via parameter -filetype or similar explicit contraption
    the hardest line to type correctly is: stty erase ^H