Searching /etc/passwd for username - regex

I'm trying to search the /etc/passwd file for a username that the use unputs but I'm not sure about the if statement syntax. My current code is:
print "Username?";
$username = <STDIN>;
open (PASSWD, '/etc/passwd')
while (<PASSWD>);
if (PASSWD ((split(/:/)[0] =~ $username)) {
print ((split(/:/)[5]), "\n");
}
close (PASSWD);
Something is wrong with the syntax and I'm having difficulty finding the correct way despite searching stackoverflow and google. Any help would be appreciated.

Perl has built-in functions for that, see getpwnam or User::pwent:
use warnings;
use strict;
print "Username? ";
chomp( my $username = <STDIN> );
die "Unknown user $username\n" unless getpwnam($username);
my $dir = (getpwnam($username))[7];
print $dir, "\n";
# - or -
use User::pwent;
print getpwnam($username)->dir, "\n";

I assume the missing semicolon at the end of your open() line is a typo.
Your while statement needs a block, not a semicolon.
while (<PASSWD>) {
... # stuff
}
When you run this line:
$username = <STDIN>;
Then $username will end containing all of the characters the user has typed at the command line. Crucially, that includes the newline character that was generated when they pressed the "Enter" key.
You then go on to compare that variable with the first field from the records in /etc/passwd. Those fields don't contain a newline character, so the match never succeeds.
You'll want to remove the newline from the end of $username. That's what chomp() is for.
chomp($username = <STDIN>);
Also, the PASSWD in your if statement is very strange. I'm not sure why you think it's necessary. It's not.
if ( (split(/:/)[0] =~ $username) {
But actually, a regex check is overkill here. You should be checking for string equality.
if ((split(/:/)[0] eq $username) {
A couple of other tips:
Always use strict and use warnings.
Use lexical variables for filehandles, use the three-argument version of open() and always check the return value from open()
open my $passwd_fh, '<', '/etc/passwd'
or die "Cannot open /etc/passwd: $!\n";

Related

perl regex: searching thru entire line of file

I'm a regex newbie, and I am trying to use a regex to return a list of dates from a text file. The dates are in mm/dd/yy format, so for years it would be '55' for '1955', for example. I am trying to return all entries from years'50' to '99'.
I believe the problem I am having is that once my regex finds a match on a line, it stops right there and jumps to the next line without checking the rest of the line. For example, I have the dates 12/12/12, 10/10/57, 10/09/66 all on one line in the text file, and it only returns 10/10/57.
Here is my code thus far. Any hints or tips? Thank you
open INPUT, "< dates.txt" or die "Can't open input file: $!";
while (my $line = <INPUT>){
if ($line =~ /(\d\d)\/(\d\d)\/([5-9][0-9])/g){
print "$&\n" ;
}
}
A few points about your code
You must always use strict and use warnings 'all' at the top of all your Perl programs
You should prefer lexical file handles and the three-parameter form of open
If your regex pattern contains literal slashes then it is clearest to use a non-standard delimiter so that they don't need to be escaped
Although recent releases of Perl have fixed the issue, there used to be a significant performance hit when using $&, so it is best to avoid it, at least for now. Put capturing parentheses around the whole pattern and use $1 instead
This program will do as you ask
use strict;
use warnings 'all';
open my $fh, '<', 'dates.txt' or die "Can't open input file: $!";
while ( <$fh> ) {
print $1, "\n" while m{(\d\d/\d\d/[5-9][0-9])}g
}
output
10/10/57
10/09/66
You are printing $& which gets updated whenever any new match is encountered.
But in this case you need to store the all the previous matches and the updated one too, so you can use array for storing all the matches.
while(<$fh>) {
#dates = $_ =~ /(\d\d)\/(\d\d)\/([5-9][0-9])/g;
print "#dates\n" if(#dates);
}
You just need to change the 'if' to a 'while' and the regex will take up where it left off;
open INPUT, "< a.dat" or die "Can't open input file: $!";
while (my $line = <INPUT>){
while ($line =~ /(\d\d)\/(\d\d)\/([5-9][0-9])/g){
print "$&\n" ;
}
}
# Output given line above
# 10/10/57
# 10/09/66
You could also capture the whole of the date into one capture variable and use a different regex delimiter to save escaping the slashes:
while ($line =~ m|(\d\d/\d\d/[5-9]\d)|g) {
print "$1\n" ;
}
...but that's a matter of taste, perhaps.
You can use map also to get year range 50 to 99 and store in array
open INPUT, "< dates.txt" or die "Can't open input file: $!";
#as = map{$_ =~ m/\d\d\/\d\d\/[5-9][0-9]/g} <INPUT>;
$, = "\n";
print #as;
Another way around it is removing the dates you don't want.
$line =~ s/\d\d\/\d\d\/[0-4]\d//g;
print $line;

Perl: unable to get the correct match from the file

I need help with my script. I am writing a script that will check if the username is still existing in /etc/passwd. I know this can be done on BASH but as much as possible I want to avoid using it, and just focus on writing using Perl instead.
Okay, so my problem is that, my script could not find the right match in my $password_file. I still got the No root found error even though it is still in the file.
Execution of the script.
jpd#skriv ~ $ grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
jpd#skriv ~ $ ~/Copy/documents/scripts/my_perl/test.pl root
Applying pattern match (m//) to #id will act on scalar(#id) at /home/jpd/Copy/documents/scripts/my_perl/test.pl line 16.
No root found!
jpd#skriv ~ $
Also, why do I always get this "Applying pattern match..." warning?
Here's the code:
#!/usr/bin/perl
use strict;
use warnings;
my $stdin = $ARGV[0];
my $password_file = '/etc/passwd';
open (PWD, $password_file) || die "Error: $!\n";
my #lines = (<PWD>);
close PWD;
for my $line (#lines) {
my #list = split /:/, $line;
my #id = "$list[0]\n";
if (#id =~ m/$stdin/) {
die "Match found!\n";
} else {
print "No $stdin found!\n";
exit 0;
}
}
Thanks in advance! :)
Regards,
sedawkgrep
Perl Newbie
I have a few things to point out regarding your code:
Good job using use strict; and use warnings;. They should be included in EVERY perl script.
Pick meaningful variable names.
$stdin is too generic. $username does a better job of documenting the intent of your script.
Concerning your file processing:
Include use autodie; anytime you're working with files.
This pragma will automatically handle error messages, and will give you better information than just "Error: $!\n". Also, if you are wanting to do a manual error messages, be sure to remove the new line from your message or die won't report the line number.
Use Lexical file handles and the three argument form of open
open my $fh, '<', $password_file;
Don't load an entire file into memory unless you need to. Instead, use while loop and process the file line by line
Concerning your comparison: #id =~ m/$stdin/:
Always use a scalar to the left of comparison =~
The comparison operator binds a scalar to a pattern. Therefore the line #id =~ m/$stdin/ is actually comparing the size of #id to your pattern: "1" =~ m/$stdin/. This is obviously a bug.
Be sure to escape the regular expression special characters using quotemeta or \Q...\E:
$list[0] =~ m/\Q$stdin/
Since you actually want a direct equality, don't use a regex at all, but instead use eq
You're exiting after only processing the first line of your file.
In one fork you're dying if you find a match in the first line. In your other fork, you're exiting with the assumption that no other lines are going to match either.
With these changes, I would correct your script to the following:
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
my $username = $ARGV[0];
my $password_file = '/etc/passwd';
open my $fh, '<', $password_file;
while (<$fh>) {
chomp;
my #cols = split /:/;
if ($cols[0] eq $username) {
die "Match found!\n";
}
}
print "No $username found!\n";
#!/usr/bin/perl
use strict;
use warnings;
my $stdin = $ARGV[0];
my $password_file = '/etc/passwd';
open (PWD,"<$password_file");
my #lines = <PWD>;
my #varr = grep (m/root/, #lines);
Then check varr array and split it if you need.
You'd be better off using a hash for key lookups, but with minimal modification this should work:
open my $in, '<', 'in.txt';
my $stdin = $ARGV[0];
while(<$in>){
chomp;
my #list = split(/\:/);
my ($id) = $list[0];
if ($id eq $stdin) {
die "Match found\n";
}
}

use of uninitialized value $line in string ne

I am writing a program of pattern matching in perl ..but getting a error ..I have seen all the earlier posts regarding this matter but didn't find the solution...As I am new to perl So I am not getting exactly what is this error all about..
use of uninitialized value $line in string ne at line .. and in line ...
I am attaching here a perl file
use strict;
use warnings;
my $line = "";
open(OUTFILE, ">output.txt") or die ("cannot open file.\n");
if(open(file1,"match.txt") or die "Cannot open file.\n"){
$line = <file1>;
while ($line ne "") {
if (defined($line) && (line =~ m/\sregion\s/i)) {
print OUTFILE ("$line")};
$line = <file1>; # Problem Here
if (defined($line) && ($line =~ /\svth\s/)) {
print OUTFILE ("$line")
};
$line = <file1>; # Problem Here
}
}
My match.txt file contain this type of data..
Some text here
region Saturati Saturati Linear Saturati Saturati
id -2.1741m -2.1741m -4.3482m 2.1741m 2.1741m
vth -353.9140m -353.9141m -379.2704m 419.8747m 419.8745m
Some text here
Please solve the problem....thanks
The reason you are seeing those errors is that the variable $line contains undef. The reason it contains undef is that you assigned it a value from readline() (the <file1>) after the file had reached its end eof. This is described in perldoc -f readline:
In scalar context, each
call reads and returns the next line until end-of-file is
reached, whereupon the subsequent call returns "undef".
The reason you are encountering this error is that you are not using a traditional method of reading a file. Usually, you would do this to read a file:
while (<$fh>) {
...
}
This will iterate over all the lines in the file until it reaches end of file, after which, as you now know, the readline returns undef and the while loop is exited.
This also means that you do not have to check every other line whether $line is defined or empty. Moreover, you can combine your regexes into one, and generally remove a lot of redundant code:
while (<>) {
if (/\b(?:region|vth)\b/i) {
print;
}
}
This is the core of the functionality you are after, and I am using some Perl idioms here: the diamond operator <> will read from the file names you give the script as argument, or from STDIN if no arguments are given. Many built-in functions use the $_ variable as default if no argument is given, which is what print does, and the while loop condition.
You might also note that I use word boundary \b instead of whitespace \s in the regex, and also use alternation | with non-capturing parentheses (?:...), meaning it can match one of those strings.
With this simplified script, you can do:
perl script.pl match.txt > output.txt
To provide your file names.
If you can't read anything, your string will come back undefined... which is why you are seeing that message.
Also, probably better to check that you open input file first before creating an output file at all, so something like this:
open(INFILE, "<match.txt") or die "Cannot open input file";
open(OUTFILE, ">output.txt") or die "cannot open output file";
my $line;
while($line = <INFILE>){
...
}
Perl will end the loop if $line is undefined or an empty string.
From the looks of it, it seems like you're trying to go through the match file and print all the lines that match region or vth to output.txt.
I simplified the code for you to do this:
use strict;
use warnings;
open(my $out_fh, ">", "output.txt") || die ("Cannot open file.\n");
open(my $file1, "<", "match.txt") || die ("Cannot open file.\n");
while( <$file1> ) {
if ( /\s(region|vth)\s/i) {
print $out_fh $_;
}
}
This question goes into more detail about checking whether a variable is defined or empty: In Perl, how can I concisely check if a $variable is defined and contains a non zero length string?
Here is more information about opening files: What's the best way to open and read a file in Perl?

Regex: How to remove extra spaces between strings in Perl

I am working on a program that take user input for two file names. Unfortunately, the program can easily break if the user does not follow the specified format of the input. I want to write code that improves its resiliency against these types of errors. You'll understand when you see my code:
# Ask the user for the filename of the qseq file and barcode.txt file
print "Please enter the name of the qseq file and the barcode file separated by a comma:";
# user should enter filenames like this: sample1.qseq, barcode.txt
# remove the newline from the qseq filename
chomp ($filenames = <STDIN>);
# an empty array
my #filenames;
# remove the ',' and put the files into an array separated by spaces; indexes the files
push #filename, join(' ', split(',', $filenames))
# the qseq file
my $qseq_filename = shift #filenames;
# the barcode file.
my barcode = shift #filenames;
Obviously this code runs can run into errors if the user enters the wrong type of filename (.tab file instead of .txt or .seq instead of .qseq). I want code that can do some sort of check to see that the user enters the appropriate file type.
Another error that could break the code is if the user enters too many spaces before the filenames. For example: sample1.qseq,(imagine 6 spaces here) barcode.txt (Notice the numerous spaces after the comma)
Another example: (imagine 6 spaces here) sample1.qseq,barcode.txt (This time notice the number of spaces before the first filename)
I also want lines of code that can remove extra spaces so that the program doesn't break. I think the user input has to be in the following kind of format: sample1.qseq, barcode.txt. The user input has to be in this format so that I can properly index the filenames into an array and shift them out later.
Thanks any help or suggestions are greatly appreciated!
The standard way to deal with this kind of problem is utilising command-line options, not gathering input from STDIN. Getopt::Long comes with Perl and is servicable:
use strict; use warnings FATAL => 'all';
use Getopt::Long qw(GetOptions);
my %opt;
GetOptions(\%opt, 'qseq=s', 'barcode=s') or die;
die <<"USAGE" unless exists $opt{qseq} and $opt{qseq} =~ /^sample\d[.]qseq$/ and exists $opt{barcode} and $opt{barcode} =~ /^barcode.*\.txt$/;
Usage: $0 --qseq sample1.qseq --barcode barcode.txt
$0 -q sample1.qseq -b barcode.txt
USAGE
printf "q==<%s> b==<%s>\n", $opt{qseq}, $opt{barcode};
The shell will deal with any extraneous whitespace, try it and see. You need to do the validation of the file names, I made up something with regex in the example. Employ Pod::Usage for a fancier way to output helpful documentation to your users who are likely to get the invocation wrong.
There are dozens of more advanced Getopt modules on CPAN.
First, put use strict; at the top of your code and declare your variables.
Second, this:
# remove the ',' and put the files into an array separated by spaces; indexes the files
push #filename, join(' ', split(',', $filenames))
Is not going to do what you want. split() takes a string and turns it into an array. Join takes a list of items and returns a string. You just want to split:
my #filenames = split(',', $filenames);
That will create an array like you expect.
This function will safely trim white space from the beginning and end of a string:
sub trim {
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}
Access it like this:
my $file = trim(shift #filenames);
Depending on your script, it might be easier to pass the strings as command line arguments. You can access them through the #ARGV array but I prefer to use GetOpt::Long:
use strict;
use Getopt::Long;
Getopt::Long::Configure("bundling");
my ($qseq_filename, $barcode);
GetOptions (
'q|qseq=s' => \$qseq_filename,
'b|bar=s' => \$barcode,
);
You can then call this as:
./script.pl -q sample1.qseq -b barcode.txt
And the variables will be properly populated without a need to worry about trimming white space.
You'll need to trim spaces before handling the filename data in your routine, you could check the file extension with yet another regular expression, as nicely described in Is there a regular expression in Perl to find a file's extension?. If it's the actual type of file that matters to you, then it might be more worthwile to check for that instead with File::LibMagicType.
While I think your design is a little iffy, the following will work?
my #fileNames = split(',', $filenames);
foreach my $fileName (#fileNames) {
if($fileName =~ /\s/) {
print STDERR "Invalid filename.";
exit -1;
}
}
my ($qsec, $barcode) = #fileNames;
And here is one more way you could do it with regex (if you are reading the input from STDIN):
# read a line from STDIN
my $filenames = <STDIN>;
# parse the line with a regex or die with an error message
my ($qseq_filename, $barcode) = $filenames =~ /^\s*(\S.*?)\s*,\s*(\S.*?)\s*$/
or die "invalid input '$filenames'";

Perl writing to a 'memory file' plays tricks with pattern matching

When I run this code, I get "no" printed out:
my $memory_file;
my $fh;
open ($fh, '>', \$memory_file);
print $fh "abc";
if( $memory_file =~ m/^.*$/ )
{ print "yes\n" }
else
{ print "no\n" }
If I print out $memory_file, the contents are indeed "abc".
If I change the pattern to .* (no ^ or $) it works as expected.
If I put the line $memory_file = "abc" before the match, I get 'yes' printed out (as originally expected).
What on earth is going on here?
(This is perl 5.14.1)
Update: Some more discussion on PerlMonks. It is seeming like a bug, I will log it.
Update 2: The fine Perl developers have fixed this bug: https://rt.perl.org/rt3//Public/Bug/Display.html?id=108398
It is the end of line character that is messing things up. While a regular assignment works:
my $str = "abc";
print "Works" if $str =~ /^.*$/;
...the code in the question does not. This regex should match any string, since it also matches the empty string. Even undefined values would match (though it would cause a warning). Also, ^.* does match.
The only reasonable explanation is that for some reason, whatever check is performed to match an end of string, it is not finding it. The end of the string is missing.
Curiously, replacing $ with \z works. But not \Z.
Adding a newline also works. Which would sort of make sense, as adding a newline would imply that an end of the string is also added, in the non-multiline regex sense.
I don't know the inner workings of why this happens, but I suspect that when using this particular form of "assignment", an end-of-the-string marker is never placed on the string. A sort of "raw" assignment, which confuses the check for end of string in the regex.
It feels like a bug. Perhaps this particular feature has not been properly maintained.
just use an auxiliary variable.
#$| = 1; # AutoFlush
my $memory_file;
open ( my $fh, '>>', \$memory_file) or die $!;
print $fh "abc";
my $buff = $memory_file;
if( $buff =~ m/^abc$/ ){ # or m/^.*$/
print "yes\n";
}else{
print "no\n";
}
I think problem is the newline and $ anchor. It checks the string after the newline and your string has not it.
EDIT:
See a detail explanation of the problem in other answers because mine was incorrect. But I will give two options to solve it:
Write "abc\n" to your file and check with $
Check end of string with \z.
Well, my first instinct is to say that files in memory do not act the same as physical files. Try changing print $fh "abc"; to print $fh "abc\n"; to see if your input 'becomes' a line.
My other instinct is that your file isn't actually getting written to before you read from it. Flush your buffer with $|++;
So, try:
my $memory_file;
my $fh;
open ($fh, '>', \$memory_file);
print $fh "abc";
$|++;
if( $memory_file =~ m/^.*$/ )
{ print "yes\n" }
else
{ print "no\n" }