get user input and print into file

98 views Asked by At

I would like to add a feature in below code to check if both fruit and info input provided by user in cmd exist in the file. Meanwhile to fullfill 3 requirements below, first and second are completed with code below.

  • If the fruit provided in cmd line not exist in file, then add it in with the info provided.
  • If the fruit provided in cmd already exist in file, then add in info provided into it. Info 1 is mapping fruit1
  • If both fruit and info provided in cmd already exist in file, skip it

cmd: <script> -fruit apple,orange,jackfruit -info y,c,a

File example:

apple
     x
     y

orange
     a
     b

Expected output (Since apple and y already in the file, skip it):

    apple
         x
         y

    orange
         a
         b
         c

    jackfruit
         a

Code:

   use strict;
    use warnings;
    use Tie::File;
    my $help=0;
    my $info;
    my $fruit;
    use Getopt::Long;

if (! GetOptions(
     "fruit=s" => \$fruit,
     "info=s" => \$info,  
)){
  print "\nEntered Arguments are not enough!\nPlease Use Switch '-help' or '-h' For More Information.\n";
   exit;
}

my $split_info;
my $split_fruit;
my @split_info = split(',', $info);
my @split_fruit = split(',', $fruit);

my %new_data;
@new_data{@split_fruit}=@split_info;


tie my @file, 'Tie::File', "/nfs/site/disks/ipg_da_00003/ip7nm/sungyuan/script/regression_indicator/testing/2022.12.SP1/config.txt" or die $!;

for (0 .. $#file) {
    next if /^\s/;

    if (exists $new_data{$file[$_]}) {
      splice @file, $_ + 1, 0, "\t$new_data{$file[$_]}\n";
      # Delete our current key/value pair from the hash
      delete $new_data{$file[$_]};
    }
}

# For each key left in the hash...
for (keys %new_data) {
  # Push two new lines into our tied file array
  # (This adds new lines to the end of the file)
  push @file, "$_\n", "\t$new_data{$_}\n";
}
2

There are 2 answers

3
Håkon Hægland On

It is not completely clear if the info values should be unique or not. Anyway I think this problem is more easily solved using a json file than using a custom file format. Here is an example using JSON input file format:

config.json

{
   "apple" : [
      "x",
      "y"
   ],
   "orange" : [
      "a",
      "b",
   ]
}

Then using this Perl script to read and modify the JSON input file:

use Object::Pad;
{

    my $self = Main->new(config_fn => 'config.json');
    $self->parse_commandline();
    $self->read_config();
    $self->update_config();
    $self->save_config();
}

class Main;
use feature qw(say);
use Getopt::Long;
use JSON;

field $_config_fn :param;
field %_config;
field @_fruits;
field @_info;

method parse_commandline() {
    my $fruit;
    my $info;
    my $msg1 = "Unable to parse command line options. ";
    my $msg2 = "Please use switch '-help' or '-h' for more information.";
    if (! GetOptions(
        "fruit=s" => \$fruit,
        "info=s" => \$info,
    )){
        die "${msg1} ${msg2}\n";
    }
    die "${msg1}: fruit parameter not given. ${msg2}\n" if !defined $fruit;
    die "${msg1}: info parameter not given. ${msg2}\n" if !defined $info;
    @_fruits = split ",", $fruit;
    @_info = split ",", $info;
    die "${msg1}: fruit and info parameters must have the same number of items. ${msg2}\n"
        if @_fruits != @_info;
}

method read_config() {
    my $fn = $_config_fn;
    open (my $fh, "<", $fn) or die "Could not open file '$fn': $!";
    my $str = do {local $/; <$fh>};
    %_config = %{ decode_json( $str ) };
    close $fh;
}

method save_config() {
    my $fn = $_config_fn;
    my $json_text = to_json( \%_config, {utf8 => 1, pretty => 1});
    # TODO: remember to take a backup of this file first!!
    open (my $fh, ">", $fn) or die "Could not open file '$fn': $!";
    print $fh $json_text;
    close $fh;
    say "Updated $fn";
}

method update_config() {
    for my $i (0..$#_fruits) {
        my $fruit = $_fruits[$i];
        my $info = $_info[$i];
        my %infos = map {$_ => 1} @{$_config{$fruit}};
        $infos{$info}++;
        @{$_config{$fruit}} = sort keys %infos;
    }
}

If you prefer the original file format, you could try this instead:

use Object::Pad;
{

    my $self = Main->new(config_fn => 'config.txt');
    $self->parse_commandline();
    $self->read_config();
    $self->update_config();
    $self->save_config();
}

class Main;
use feature qw(say);
use Getopt::Long;

field $_config_fn :param;
field %_config;
field @_cmdline_fruits;
field @_cmdline_info;
field @_config_fruits;

method parse_commandline() {
    my $fruit;
    my $info;
    my $msg1 = "Unable to parse command line options. ";
    my $msg2 = "Please use switch '-help' or '-h' for more information.";
    if (! GetOptions(
        "fruit=s" => \$fruit,
        "info=s" => \$info,
    )){
        die "${msg1} ${msg2}\n";
    }
    die "${msg1}: fruit parameter not given. ${msg2}\n" if !defined $fruit;
    die "${msg1}: info parameter not given. ${msg2}\n" if !defined $info;
    @_cmdline_fruits = split ",", $fruit;
    @_cmdline_info = split ",", $info;
    die "${msg1}: fruit and info parameters must have the same number of items. ${msg2}\n"
        if @_cmdline_fruits != @_cmdline_info;
}

method read_config() {
    my $fn = $_config_fn;
    open (my $fh, "<", $fn) or die "Could not open file '$fn': $!";
    my %config;
    my $fruit;
    my @fruits;
    while (my $line = <$fh>) {
        chomp $line;
        next if $line !~ /\S/;  #skip empty lines
        if ($line !~ /^\t/) { # a line that does not start with a tab is a fruit
            $fruit = $line;
            push @_config_fruits, $fruit;  # use an array to record the ordering of fruits
        }
        else {  # assume it is a info line
            my $info = $line =~ s/^\t//r;  # remove initial tab character
            push @{$_config{$fruit}}, $info;
        }
    }
    close $fh;
}

method save_config() {
    my $fn = $_config_fn;
    # TODO: remember to take a backup of this file first!!
    open (my $fh, ">", $fn) or die "Could not open file '$fn': $!";
    for my $fruit (@_config_fruits) {
        say $fh $fruit;
        for my $info (@{$_config{$fruit}}) {
            say $fh "\t", $info;
        }
    }
    close $fh;
    say "Updated $fn";
}

method update_config() {
    for my $i (0..$#_cmdline_fruits) {
        my $fruit = $_cmdline_fruits[$i];
        my $info = $_cmdline_info[$i];
        my %infos = map {$_ => 1} @{$_config{$fruit}}; # use hash to make info items unique
        $infos{$info}++;
        @{$_config{$fruit}} = sort keys %infos;  #keep the info items in sorted order
    }
}
0
zheng liew On

I use a hash of array to approach your question The arrangement for apple,orange,jackfruit will messed up since I store in a hash, but you can try to assign weightage for each keys inside the hash and loop through it.

use warnings;
use strict;
use Getopt::Long;
use Data::Dumper;

my $path = '/home/liew/perl_programming/module/fruit.txt';
my %hash_of_array;
my $fruits;
my $info;
my @info;
my $key;
my %hash_input;
my @deref_arr;

GetOptions(
'fruits=s'=>\$fruits,
'info=s'=>\$info
);

print "Not enough argument\n" if (!$fruits|!$info);

open (my $fh, '<', "$path") or die "Can't open file:$!";

while (my $line =<$fh>) {
    chomp $line;
    if ($line =~ /^(\w+)$/) {
        $key = $1;
    }
    else {
       push (@{$hash_of_array{$key}} ,$line); 
    }
}
close ($fh);

my @splited_fruit = split(',', $fruits);
my @splited_info = split(',', $info);

@hash_input{@splited_fruit} = @splited_info;

foreach my $key (keys %hash_input) {

if (exists $hash_of_array{$key}) {
@deref_arr = @{$hash_of_array{$key}};

    if (!grep(/$hash_input{$key}/, @deref_arr)) {

    push(@{$hash_of_array{$key}}, "\t$hash_input{$key}");

    }
}

else {

    $hash_of_array{$key} = "\t$hash_input{$key}";
}

}

print Dumper \%hash_of_array, "\n";

sub write_file {
foreach my $key (keys %hash_of_array) {

print "$key\n";
my $value = $hash_of_array{$key};
if (ref($value) eq "ARRAY") {
    foreach my $element (@{ $hash_of_array{$key} }) {
        print "$element\n";

        }
}

else {

  print "$hash_of_array{$key}\n";
}

}
}

#write to file

open (my $fh_w, '>', "$path") or die "Can't write to file:$!";

select $fh_w;
write_file;
select STDOUT;

close ($fh_w);