Updated file strings words together - arrays

My script works, except when I append the file: it bunches my words into one string like so:
Joe Smo45MaleSingle
Originally, in the text file, they are in a column. I want the appended file to stay in a column.
#!/usr/bin/perl
use strict;
use warnings;
use File::Slurp;
my #array1 = ("Full Name:", "Age:", "Gender:", "Marital Status:");
my #array2 = read_file("empdata.txt", chomp => 1);
print "$array1[0] $array2[0]\n";
print "$array1[1] $array2[1]\n";
print "$array1[2] $array2[2]\n";
print "$array1[3] $array2[3]\n";
print "Do you want to change the age? (y or n) :";
chomp(my $answer = <STDIN>);
if ($answer eq "y") {
print "What is the new age? :";
chomp(my $age = <STDIN>);
$array2[1] = $age;
write_file("empdata.txt",#array2);
print "Do you want to change marital status? (y or n) :";
chomp(my $answer = <STDIN>);
if ($answer eq "y") {
print "What is the new marital status? :";
chomp(my $status = <STDIN>);
$array2[3] = $status;
write_file("empdata.txt",#array2);
}
} else {
print "Do you want to change marital status? (y or n) :";
chomp(my $answer = <STDIN>);
if ($answer eq "y") {
print "What is the new marital status? :";
chomp(my $status = <STDIN>);
$array2[3] = $status;
write_file("empdata.txt",#array2);
}
}
close || die "could not close file";

I'm guessing, because you haven't shown us your input data. But I think you're saying that your input file looks like this:
Joe Smo
45
Male
Single
And, after running your program, you end up with this:
Joe Smo45MaleSingle
When you read the file, you use the chomp => 1 option to read_file(). That removes newlines from the end of the lines in the file. But write_file() doesn't have an unchomp option to replace the newlines.
The quickest, dirtiest, fix is to stop removing the newlines. You'll want to remove the chomp => 1 option from read_file() and also stop calling chomp() on the new $status values as you read them in. You can also remove the "\n" from the lines where you are printing the original records (as they will have a newline attached).
However, I'd caution you against using File::Slurp. It has some problems with UTF8 data that are likely to burn you in the future. I'd recommend Path::Tiny (which has slurp() and spew() methods) in its place.
But, really, it looks like you're writing a database. So why not use a database?

Related

Perl: How to reference to an array?

I have a program here that will allow user to enter in words that will be used as filenames later on. The user's input is inserted into an array. I then create a simple menu asking what the user wants to do with the "filename": create, reame, copy, and delete. Using subroutines, I believe I've successfully executed the code. However, I am having trouble trying to reference the array element.
In short, how do I create, rename, copy, and delete the filenames that are saved in the array? The issues I cant resolve are asterisked
#!/usr/bin/perl
use strict;
use warnings;
use File::Copy qw/copy/;
my $new;
my $old;
my $file;
print "Enter number of lines\n";
chomp(my $n = <STDIN>);
my #lines;
print "Enter some words!\n";
for (1..$n) {
chomp(my $input = <STDIN>);
#will add the user input at the beginning of the array
push #lines, $input;
}
print "#lines\n";
#create a menu
my $in = '';
print "1. Create\n2. Rename\n3. Copy\n4. Delete\n5. Quit\n";
while ($in ne "quit")
{
print "\nEnter the your choice:\n";
chomp($in = <STDIN>);
print "For which file do you want to $in?\n";
print "#lines\n";
**#I need to reference the array here***********************
if ($in eq "create") {
***How to create the file that the user inputted?*****************
&createFile;
print "\nFile has been created\n";
}
elsif ($in eq "rename") {
#print "Enter the old name\n";
# chomp($old = <STDIN>);
#*********refer to array of FILES*********************
print "Enter the new name for this file!\n";
chomp($new = <STDIN>);
&renameFile;
print "File has successfully been renamed.";
}
elsif ($in eq "copy") {
#********file and array refering***************************
&copyFile;
print "Hopefully, the file has beed copied!";
}
elsif ($in eq "delete") {
#******refer to file from user*******************
&deleteFile;
print "Deleted";
}
}
#create subroutine
sub createFile {
open('>' .*****issue here too**) or die "\nCannot create";
}
sub renameFile {
rename ($old, $new);
}
sub copyFile {
copy $old, $new;
}
sub deleteFile {
#unlink *****************************
}
First line is $line[0], second is $line[1] and so on...
You can get user choice like this:
$line[$in]
About how to refer to perl data structures see here
$lines[0]; #First line
$lines[1]; #Second line
$array = \#lines; # Get reference to the list of lines
$array->[0]; #First line
$array->[1]; #Second line
to create file and put data into just use:
open $fh, '>filename';
print $fh #lines;
print $fh #$array; # ... or same for reference
Notice lack of coma after $fh

What is the proper way to search array using Smart Match?

I'm new to programming much less Perl; I'm having difficulty with searching an array I've made from an external text file. I'm looking for a simple way to check if the user entry is located in the array. I've used the Smart Match function before but never in an "if" statement and can't seem to get it to work. Am I implementing this function wrong, or is there an easier way to check if the user's string is in the array?
#!/usr/bin/perl
use 5.010;
#Inventory editing script - Jason Black
#-------------------------------------------------------------------------------
print "1. Add Items\n";
print "2. Search Items\n";
print "Please enter your choice: ";
chomp ($userChoice = <STDIN>); #Stores user input in $userChoice
if($userChoice == 1){
$message = "Please enter in format 'code|title|price|item-count'\n";
&ChoiceOne;
}
elsif($userChoice == 2){
$message = "Enter search terms\n";
&ChoiceTwo;
}
sub ChoiceOne{
print "$message\n";
chomp($userAddition = <STDIN>); #Stores input in $userAddition
$string1 = "$userAddition";
open (FILE, "FinalProjData.txt") or die ("File not found"); #"FILE" can be named anything
#array = <FILE>;
if ( /$string1/ ~~ #array){
print "This entry already exists. Would you like to replace? Y/N \n";
chomp($userDecision = <STDIN>); #Stores input in $userDecision
if ($userDecision eq "Y"){
$string1 =~ s/$userAddition/$userAddition/ig;
print "Item has been overwritten\n";}
elsif($userDecision eq "N"){
print FILE "$string1\n";
print "Entry has been added to end of file.\n";}
else{
print "Invalid Input";
exit;}
}
else {
print FILE "$string1\n";
print "Item has been added.\n";}
close(FILE);
exit;
}#end sub ChoiceOne
sub ChoiceTwo{
print "$message\n";
}
If you want to avoid using smartmatch alltogether:
if ( grep { /$string1/ } #array ) {
To actually match the $string1, however, it needs to be escaped, so that | doesn't mean or:
if ( grep { /\Q$string\E/ } #array ) {
or just a simple string compare:
if ( grep { $_ eq $string } #array ) {

Perl: Empty/broken AoH

I have subroutine in my module which checks (regular) user password age using regex search on shadow file:
Module.pm
my $pwdsetts_dump = "tmp/shadow_dump.txt";
system("cat /etc/shadow > $pwdsetts_dump");
open (my $fh1, "<", $pwdsetts_dump) or die "Could not open file '$pwdsetts_dump': $!";
sub CollectPWDSettings {
my #pwdsettings;
while (my $array = <$fh1>) {
if ($array =~ /^(\S+)[:][$]\S+[:](1[0-9]{4})/) {
my $pwdchange = "$2";
if ("$2" eq "0") {
$pwdchange = "Next login";
}
my %hash = (
"Username" => $1,
"Last change" => $pwdchange
);
push (#pwdsettings, \%hash);
}
}
my $current_date = int(time()/86400); # epoch
my $ndate = shift #_; # n-days
my $search_date = int($current_date - $ndate);
my #sorted = grep{$_->{'Last change'} > $search_date} #pwdsettings;
return \#sorted;
}
Script is divided in 2 steps:
1. load all password settings
2. search for password which is older than n-days
In my main script I use following script:
my ($user_changed_pwd);
if (grep{$_->{'Username'} eq $users_to_check} #{Module::CollectPWDSettings("100")}) {
$user_changed_pwd = "no";
}
else {
$user_changed_pwd = "yes";
}
Problem occurs in first step, AoH never gets populated. I'm also pretty sure that this subroutine always worked for me and strict and warnings never complained about it, nut now, for some reason it refuses to work.
I've just run your regex against my /etc/shadow and got no matches. If I drop the leading 1 I get a few hits.
E.g.:
$array =~ /^(\S+)[:][$]\S+[:]([0-9]{4})/
But personally - I would suggest not trying to regex, and instead rely on the fact that /etc/shadow is defined as delimited by :.
my #fields = split ( /:/, $array );
$1 contains a bunch of stuff, and I suspect what you actually want is the username - but because \S+ is greedy, you might be accidentally ending up with encrypted passwords.
Which will be $fields[0].
And then the 'last change' field - from man shadow is $fields[2].
I think your regex pattern is the main problem. Don't forget that \S matches any non-space character including colons :, and \S+ will try to match as much as possible so it will happily skip over multiple fields of the file
I think using split to separate each record into colon-delimited fields is a better approach. I also think that, instead of the array of two-element hashes #pwdsettings it would be better to store the data as a hash relating usernames to their password history
Here's how I would write this. It prints a list of all usernames whose password history is greater than 90 days
use strict;
use warnings;
use Time::Seconds 'ONE_DAY';
my #shadow = do {
open my $fh, '<', '/etc/shadow' or die qq{Unable to open "/etc/shadow" for input: $!};
<$fh>;
};
print "$_\n" for #{ collect_pwd_settings(90) };
sub collect_pwd_settings {
my ($ndate) = #_;
my %pwdsettings;
for ( #shadow ) {
my ($user, $pwdchange) = (split /:/)[0,2];
$pwdsettings{$user} = $pwdchange;
}
my $current_date = time / ONE_DAY;
my #filtered = grep { $current_date - $pwdsettings{$_} > $ndate } keys %pwdsettings;
return \#filtered;
}

printing arrays to text file (v2)

yesterday i asked this question and got many helpful replies, hoping the same will be true today. Here is my revised script.
#! /usr/bin/perl
use strict;
use warnings;
my $line;
my #array;
my $print;
open (OUT , ">","output.txt")or die "cant open: $!";
while ($line = <>){
chomp($line);
push(#array, $line);
if(#array == 250){
$print = print "[", join(",",#array), "]", "\n";
print OUT $print;
#array = []
}
}
Originally i simply stated that i need to print out the first 250 elements of an array to a text file, and that this array was built from standard input. What i didnt state is that the input, from which the array is built, may consist of several thousand lines. The reason for creating an array from this input is so that i can limit the size of the array to 250 entities, and then print the array as a formatted string. I then need to flush the array and resume building at what will become the 251st line of the input, and continue doing this process for the remainder of the input. Lets say the input is 5k lines, i want my output to be a text file containing the original 5k lines of input, but divided into strings made up of 250 array entities.
Currently the script is just printing the array to the screen and inside "ouput.txt" is a single line reading: 1111111.
$print = print "[", join(",",#array), "]", "\n";
print OUT $print;
Here you are assigning the return value of print to $print, which is 1, because the printing is successful. What you want to do is this:
print OUT "[", join(",",#array), "]", "\n";
Here's another option which just slightly modifies your script:
use strict;
use warnings;
my #array;
local $" = ',';
while (<>) {
chomp;
push #array, $_;
if ( #array == 250 ) {
print "[#array]\n";
undef #array;
}
}
Usage: perl script.pl Infile [>outFile]
The last, optional parameter directs output to a file.
The variable $" is holds Perl's list separator that's applied to an interpolated array, so join's not needed here.
Hope this helps!
#! /usr/bin/perl
use strict;
use warnings;
my $line;
my #array;
my $print;
open (OUT , ">","moloch_chunker_output.txt")or die "cant open: $!";
while ($line = <>){
chomp($line);
push(#array, $line);
if(#array == 250){
print OUT "[", join(",",#array), "]", "\n";
#array = ();
}
}
Thank you davs, that fixed it.

Perl read a file and an array and find common words

This is kind of a small issue and I hope you are able to help me. My code is probably rubbish. For an example, I have a file in which the only statement is John is the uncle of Sam. My Perl script should copy the file contents into an array. User should be able to input different names and search if those names are mentioned in the file. There should be an array with relationships like "uncle aunt, mother, father etc" in the program.
#use warnings;
use Array::Utils qw(:all);
print "Please enter the name of the file\n";
my $c = <STDIN>;
open(NEW,$c) or die "The file cannot be opened";
#d = <NEW>;
print #d, "\n";
#g = qw(aunt uncle father);
chomp #d;
chomp #g;
my $e;
my $f;
print "Please enter the name of the first person\n";
my $a = <STDIN>;
print "Please enter the name of the second person\n";
my $b = <STDIN>;
my #isect = intersect(#g, #d);
print #isect;
foreach(#d)
{
if ($a == $_)
{
$e = $a;
}
else
{
print "The first person is not mentioned in the article";
exit();
}
if ($b == $_)
{
$f = $b;
}
else
{
print "The second person is not mentioned in the article";
exit();
}
}
print $e;
print $f;
close(NEW);
This is something that I have done so far, the intersection is not giving the word uncle which is the word common in both arrays. The program is taking any random name and printing them. It is not saying that the name doesn't exist in the file when I enter a different name other than John and Sam
There are several problems:
You do not chomp $c. The filename contains a newline at the end.
You use the 2-argument form of open, but do not test the second argument. This is a security problem: do you know what happens if the user input contains > or |?
You use == to compare strings. String equality is tested with eq, though, == tests numbers.
Moreover, you do not want to know whether "Sam" equals to "John is the uncle of Sam". You want to know whether it is a part of it. You might need to use index or regular expressions to find out.
Do not use $a as the name of a variable, it is special (see perlvar).
Do not try to compare strings with ==! Use eq (equals) instead. Also you didnt chomp your input $a$b`. I think this is what you're trying to do:
#!/usr/bin/perl
use strict;
use warnings;
print "Please enter the name of the file\n";
my $c = <STDIN>;
open(NEW,$c) or die "The file cannot be opened";
my #d = <NEW>;
chomp #d;
my $e;
my $f;
print "Please enter the name of the first person\n";
my $aa = <STDIN>;
print "Please enter the name of the second person\n";
my $bb = <STDIN>;
chomp $aa;
chomp $bb;
my $pattern_a = quotemeta $aa;
my $pattern_b = quotemeta $bb;
foreach (#d){
if ($_ =~ /$pattern_a/){
$e = $aa;
}
elsif ($_ =~ /$pattern_b/){
$f = $bb;
}
}
close(NEW);
unless ($e){
print "First person not mentionend\n";
}
unless ($f){
print "Second person not mentioned\n";
}

Resources