Some simple Perl and Regex
my $cols   = {'list'=&gt;0, 'email'=&gt;1, 'sub'=&gt;2};


I'd call this 

my %index_of = { 'list' =&gt; 0, 'email' =&gt; 1, 'sub' =&gt; 2 };


and later, change $return to 

my $index = $index_of{'email'};


The following is used oddly:  

print get_list(@ARGV);
foreach (@ARGV) {


You are calling the function with parameters @ARGV but you never use the parameters in the function.  Instead, you use @ARGV again.  Better would be to use the function parameters:

my @arguments = @_;
foreach my $argument (@arguments) {
   my ($key, $value) = split {=}, $argument;


I also switched to using a named element instead of letting it default to $_.  

Alternately, you could pass nothing into the function and use @ARGV in the foreach as you did.  That would work but isn't a best practice.  

You may not have realized but the '=' was a regular expression with single quotes as the delimiters.  I prefer to use curly brackets for regular expressions.  If you wanted to be really plain about it, you could have said qr{=}.  

# default patterns that can be overridden by command line arguments
my $list   = qr{[a-z0-9_\- ]+};
my $email  = qr{[a-z0-9@\._\-]+};
my $subls  = qr{[a-z0-9\. ]+};
  $value = qr{} if not defined $value; 


Using qr{} here makes it more obvious that these are regular expression patterns.  

open my $fh, '&lt;', $file 


I'd prefer a variable here rather than a globally scoped bareword.  

my $SEPARATOR = qr{\s*\t\s*}
my %emails = map { (split $SEPARATOR)[$return] =&gt; 'tacos' }
             grep m{^${list}${SEPARATOR}${email}(${SEPARATOR}${subls})?\s*$}gi, 
             &lt;$fh&gt;;


By defining $SEPARATOR, we make the regular expressions clearer and reduce the amount of repeated code.  

my @entries = ();
while ( my $line = &lt;$fh&gt; ) {
   @columns = m{\A(${list})${SEPARATOR}(${email})(${SEPARATOR}(${subls}))?\s*\z}gi;
   if ( scalar @columns &gt;= 2 ) {
      push @entries, $columns[$index];
   }
}
close $fh;

return (join ', ', @entries); 


This is clearer about what it is doing than the map of the grep results.  Also, it doesn't parse each line twice.  And of course it doesn't define a hash just to get the keys.  

In general, I would make more use of paragraphing.  This is extra new lines to separate blocks of code.  This allows you to easily see what pieces of code go together.  For example, note how I separate the return from the file handling code.  

You have comments explaining what the file format should be, but you don't explain what the command line arguments should be.  A user would have to read the code to figure it out.  

Your function does two distinct things.  First, it reads the command line arguments.  Second, it opens a file and processes it based on the command line arguments.  It would be better if you could separate that into two functions.