Perl: how can i pass list to script? - perl

i need to pass list to my script
how can i do it?
for example i have script: flow.pl
i need to pass to it list of fubs :
fub1, fub2, fub3
and path to database1 =
path1
and path to database2 =
path2
and how can i get this parameters?
(through $ARGV[i]?)
if i do:
flow.pl fub1,fub2,fub3 path1 path2
and in the code:
$listoffubs = $ARGV[0]
$path1 = $ARGV[1]
$path2 = $ARGV[2]
list of fubs get name of fubs like one word.

Having lists of positional arguments is ok as long as the lists are short and simple. Once you get into a larger number of arguments or you have arguments with an internal structure, then you probably want to look at named arguments.
In this case, I think I'd be looking at using GetOpt::Long to implement named arguments.

Simply split the list:
my #ListOfFubs = split /,/ , $listoffubs;

Your arguments will be space separated, so yes, fub1,fub2,fub3 will be one argument. Just use space instead, and you'll be fine. E.g.:
flow.pl fub1 fub2 fub3 path1 path2
my $fub1 = shift; # first argument is removed from ARGV and assigned to $fub1
Or
my $fub1 = $ARGV[0]; # simple assignment
All at once
my ($fub1,$fub2,$fub3,$path1,$path2) = #ARGV; # all are assigned at once
Note that using shift is removing arguments from #ARGV.
If you have a list of arguments that may vary, it is a simple fix to put them last, and then do:
flow.pl path1 path2 fub1 fub2 fub3 ... fubn
my $path1 = shift;
my $path2 = shift; # remove first two arguments, then
my #fubs = #ARGV; # assign the rest of the args to an array
For more complicated argument handling, use a module, such as Getopt::Long.

Related

How can I pass arguments for a subroutine from the command line

First off, my background is primarily in Python and I am relatively new at using Perl.
I am using tcsh for passing options into my .pl file.
In my code I have:
if( scalar #ARGV != 1)
{
help();
exit;
}
# Loading configuration file and parameters
our %configuration = load_configuration($ARGV[0]);
# all the scripts must be run for every configuration
my #allConfs = ("original");
sub Collimator
{
my $z = -25.0;
my %detector = init_det();
$detector{"pos"} = "0*cm 0.0*cm $z*cm"
}
foreach my $conf ( #allConfs )
{
$configuration{"variation"} = $conf ;
#other geometries ....
Collimator();
}
I want to add something that allows me to change the parameters of the subroutine in the .pl file from the command line. Currently, to generate the geometry I pass the following command into the tcsh CLI: perl file.pl config.dat. What I want is to be able to pass in something like this: perl file.pl config.dat -20.0.
I'm thinking that I need to add something to the effect of:
if($ARGV[1]){
$z = ARGV[1]}
However, I am not sure how to properly implement this. Is this something that I would specify within the subroutine itself or outside of it with the loading of the configuration file?
Use a library to handle command-line arguments, and Getopt::Long is excellent
use warnings;
use strict;
use Getopt::Long;
my ($config_file, #AllConfs);
my ($live, $dry_run) = (1, 0);
my $z;
GetOptions(
'config-file=s' => \$config_file,
'options-conf=s' => \#AllConfs,
'geom|g=f' => \$z,
'live!' => \$live,
'dry-run' => sub { $dry_run = 1; $live = 0 },
# etc
);
# Loading configuration file and parameters
# (Does it ===really=== need to be "our" variable?)
our %configuration = load_configuration($config_file);
my #data_files = #ARGV; # unnamed arguments, perhaps submitted as well
Options may be abbreviated as long as they are unambiguous so with my somewhat random sample above the program can be invoked for example as
prog.pl --conf filename -o original -o bare -g -25.0 data-file(s)
(Or whatever options for #AllConf are.) Providing explicitly g as another name for geom makes it a proper name, not an abbreviation (it doesn't "compete" with others).
Note that one can use -- or just -, and choose shorter or long(er) names for convenience or clarity etc. We get options, and there is a lot more than this little scoop, see docs.
Once the (named) options have been processed the rest on the command line is available in #ARGV, so one can mix and match arguments that way. Unnamed arguments are often used for file names. (The module offers a way to deal with those in some capacity as well.)
if ( #ARGV != 2 ) {
help();
}
my ( $path, $z ) = #ARGV;
perl gets input as list, so you can get its value using #ARGV or using $ARGV[0] and $ARGV[1]
die if ($#ARVG != 1);
#the first argument
print "$ARGV[0]\n";
#the second argument
print "$ARGV[1]\n"

$log = LogFile->new($cfg{logdir} . '/tpchc.log', ($tpchc->{args}->{debug}) ? 1 : 0) what does this mean please explain

$log = LogFile->new($cfg{logdir} . '/tpchc.log', ($tpchc->{args}->{debug}) ? 1 : 0)
Can someone please tell me what this means
The code is creating a new LogFile object, which takes two arguments: the path to the log file and a debug boolean flag.
In this case the first argument is using concatenation (with the period) and the second argument is being set using a ternary if-else.
Perl is a compile-at-run-time language so it allows you to write arguments directly in a constructor this way (though there are limits to this). It's the same as doing:
my $file = $cfg{logdir} . '/tpchc.log';
my $debug = $tpchc->{args}->{debug} ? 1 : 0;
my $log = LogFile->new($file, $debug);
Incidentally, simple hashes and hashrefs (but not objects) can be interpolated in a string, so you don't actually need to concatenate the $file line:
my $file = "$cfg{logdir}/tpchc.log";
Some people find that easier to read and some don't so to each their own.

Count number of files in a folder with Perl

I would like to count the number of files inside a folder with Perl. With the following code I can list them, but how can I count them in Perl?
$dir = "/home/Enric/gfs-0.5.2016061400";
opendir(DIR, "$dir");
#FILES = grep { /gfs./ } readdir(DIR);
foreach $file (#FILES) {
print $file, "\n";
}
closedir(DIR);
If you want to just count them, once you have a directory open for reading you can manipulate context so that readdir returns the list of all entries but then assign that to a scalar. This gives you the length of the list, ie. the number of elements
opendir my $dh, $dir;
my $num_entries = () = readdir($dh);
The construct = () = imposes list context on readdir and assigns (that expression†) to a scalar, which thus gets the number of elements in that list.‡ §   See it in perlsecret. Also see this page.
There are clearer ways, of course, as below.
If you want to count certain kinds of files, pass the file list through grep first, like you do. Since grep imposes the list context on its input readdir returns the list of all files, and after filtering grep itself returns a list. When you assign that to a scalar you get the length of that list (number of elements), ie. your count. For example, for all regular files and /gfs./ files
use warnings;
use strict;
my $dir = '/home/Enric/gfs-0.5.2016061400';
opendir my $dh, $dir or die "Can't open $dir: $!";
my $num_files = grep { -f "$dir/$_" } readdir($dh);
rewinddir($dh); # so that it can read the dir again
my $num_gfs = grep { /gfs./ } readdir($dh);
(This is only an example, with rewinddir so that it works as it stands. To really get two kinds of files from a directory better iterate over the entries one at a time and sort them out in the process, or read all files into an array and then process that)
Note that readdir returns the bare filename, without any path. So for most of what is normally done with files we need to prepend it with the path (unless you first chdir to that directory). This is what is done in the grep block above so that the -f file test (-X) has the correct filename.
If you need to use the file list itself, get that into an array and then assign it to a scalar
# Get the file list, then its length
my #files_gfs = map { "$dir/$_" } grep { /gfs./ } readdir($dh);
my $num_gfs = #files_gfs;
Here map builds the full path for each file. If you don't need the path drop map { }. Note that there is normally no need for the formal use of scalar on the array to get the count, like
my $num_gfs = scalar #files_gfs; # no need for "scalar" here!
Instead, simply assign an array to a scalar instead, it's an idiom (to say the least).
If you are processing files as you read, count as you go
my $cnt_gfs = 0;
while (my $filename = readdir($dh)) {
$cnt_gfs++ if $filename =~ /gfs./;
# Process $dir/$filename as needed
}
Here readdir is in the scalar context (since its output is assigned to a scalar), and it iterates through the directory entries, returning one at a time.
A few notes
In all code above I use the example from the question, /gfs./ -- but if that is in fact meant to signify a literal period then it should be replaced by /gfs\./
All this talk about how readdir returns bare filename (no path) would not be needed with glob (or then better File::Glob), which does return the full path
use File::Glob ':bsd_glob'; # (better with this)
my #files = glob "$dir/*";
This returns the list of files with the path $dir/filename.
Not that there is anything wrong with opendir+readdir. Just don't forget the path.
Yet another option is to use libraries, like Path::Tiny with its children method.
† The assignment () = readdir $dh itself returns a value as well, and in this case that whole expression (the assignment) is placed in the scalar context.
‡ The problem is that many facilities in Perl depend in their operation and return on context so one cannot always merely assign what would be a list to a scalar and expect to get the length of the list. The readdir is a good example, returning a list of all entries in list context but a single entry in scalar context.
§ Here is another trick for it
my $num_entries = #{ [ readdir $dh ] };
Here it is the constructor for an anonymous array (reference), [], which imposes the list context on readdir, while the dereferencing #{ } doesn't care about context and simply returns the list of elements of that arrayref. So we can assign that to a scalar and such scalar assignment returns the number of elements in that list.
You have the list of files in #FILES. So your question becomes "how do I get the length of an array?" And that's simple, you simply evaluate the array in scalar context.
my $number_of_files = #FILES;
print $number_of_files;
Or you can eliminate the unnecessary scalar variable by using the scalar() function.
print scalar #FILES;
Try this code for starters (this is on Windows and will include . , .. and folders. Those can be filtered out if you want only files):
#!/usr/bin/perl -w
my $dirname = "C:/Perl_Code";
my $filecnt = 0;
opendir (DIR, $dirname) || die "Error while opening dir $dirname: $!\n";
while(my $filename = readdir(DIR)){
print("$filename\n");
$filecnt++;
}
closedir(DIR);
print "Files in $dirname : $filecnt\n";
exit;
I know this isn't in Perl, but if you ever need a quick way, just type this into bash command line:
ls -1 | wc -l
ls -1 gives you a list of the files in the directory, and wc -l gives you the line count. Combined, they'll give you the number of files in your directory.
Alternatively, you can call bash from Perl (although you probably shouldn't), using
system("ls -1 | wc -l");

Perl - Subroutine argument is another subroutine call

I have a subroutine called grepText, which simply greps a text from another variable. I am trying to split the output. Is it possible to pass the output of grepText as an argument to split directly? without putting the value of grepText in a variable first ? grepText returns a string.
What i am trying to do is:
$output = (split ":", grepText("findThis", $Alltext))[1];
grepText is as follows
sub grepText(){
my #text = split "\n", $_[1];
my $output = grep /$_[0]/, #text;
return $output;
}
it doesn't work. Error is
Too many arguments for main::grepText at a line 115, near "$Alltext)"
It is very much possible to pass the input of a subroutine to any perl function directly without using a perl variable.
I think the issue might be with your "grepText" subroutine. To debug the issue in detail, much more information is required.
I did try your routine and I was able to get the required output:
#!/usr/bin/perl
sub grepText
{
return "hello:world"; # returns a test string
}
my $output = (split ":", grepText($textToFind, $Alltext))[1];
print "$output";
Output:
world
Sure it is. But as you've written it grepText is getting some strange parameters. In
(split ":", grepText(/$textToFind/, $Alltext))[1];
you're calling grepText(/$textToFind/, $Alltext) which is searching for the value of $textToFind in the global variable $_ and, in list context, is inserting either an empty list () or a list containing 1 (1) into the parameters
So you're calling grepText($Alltext) or grepText(1, $Alltext) depending on whether $_ contains the regex pattern in $textToFind
I'm pretty certain that's not what you want to do, so some more information would be nice!
However, whatever grepText returns will be split on colons : and (split ":", grepText(...))[1] will give you the second colon-separated field, which seems to be what you're asking

How to use "or " operator to assign several values to a variable?

I'm new with perl.
I would like to say that a variable could take 2 values, then I call it from another function.
I tried:
my(#file) = <${dirname}/*.txt || ${dirname}/*.xml> ;
but this seems not working for the second value, any suggestions?
When using the <*> operator as a fileglob operator, you can use any common glob pattern. Available patterns are
* (any number of any characters),
? (any single character),
{a,b,c} (any of the a, b or c patterns),
So you could do
my #file = glob "$dirname/*.{txt,xml}";
or
my #file = (glob("$dirname/*.txt"), glob("$dirname/*.xml"));
or
my #file = glob "$dirname/*.txt $dirname/*.xml";
as the glob pattern is split at whitespace into subpatterns
If I understood correctly, you want #files to fallback on the second option (*.xml) if no *.txt files are found.
If so, your syntax is close. It should be:
my #files = <$dirname/*.txt> || <$dirname/*.xml>;
or
my #files = glob( "$dirname/*.txt" ) || glob( "$dirname/*.xml" );
Also, it's a good idea to check for #files to make sure it's populated (what if you don't have any *.txt or *.xml?)
warn 'No #files' unless #files;
my (#file) = (<${dirname}/*.txt>, <${dirname}/*.xml>);
my(#file) = <${dirname}/*.txt>, <${dirname}/*.xml> ;
<> converts it into an array of file names, so you are essentially doing my #file = #array1, #array2. This will iterate first through txt files and then through xml files.
This works
my $file = $val1 || $val2;
what it means is set $file to $val1, but if $val1 is 0 or false or undef then set $file1 to $val2
In essence, surrounding a variable with < > means either
1) treat it as a filehandle ( for example $read=<$filehandle> )
2) use it as a shell glob (for example #files=<*.xml> )
Looks to me like you wish to interpolate the value $dirname and add either .txt or .xml on the end. The < > will not achieve this
If you wish to send two values to a function then this might be what you want
my #file=("$dirname.txt","$dirname.xml");
then call the function with #file, ie myfunction(#file)
In the function
sub myfunction {
my $file1=shift;
my $file2=shift;
All this stuff is covered in perldocs perlsub and perldata
Have fun