in reply to Re^2: genpass Password Generator
in thread genpass Password Generator
A few more comments mixing in random order matters of personal style and more substantial things:
Getopt::Long::Configure ("bundling");
I know this is taken straight out of G::L's docs, but it is a sub call and I can't understand why it doesn't look like one. Incidentally perldoc perlstyle also recommends against doing so:
* No space between function name and its opening parenthesis.
## PARSE AND SET COMMAND-LINE OPTIONS ## -----------------------------------------------------
Again, I'm of the good-code-should-be-self-explaining school of thought and I would avoid such massive comments, but that's just me. However you may want to complete your code with real pod documentation.
my %flags=('symbols', 0, 'numbers', 0, 'uppercase', 0, 'lowercase', 0, + 'confusable', 0, 'help', 0, 'qty', 1);
While this is officially ok, people like to use => a.k.a. fat comma to define hashes, which is especially good with keys like these ones, due to its autoquoting property. Better yet, since most values are null, you may use a map:
my %flags=map {$_ => 0 } qw/symbols numbers uppercase lowercase confusable help); $flags{qty}=1;
which IMHO makes for clearer syntax.
my $pwdchars = join( '', map {chr} ( 0x21 .. 0x7e )); $pwdchars =~ s/\d+// if ( $flags{numbers} ); $pwdchars =~ s/[A-Z]+// if ( $flags{uppercase} ); $pwdchars =~ s/[a-z]+// if ( $flags{lowercase} ); $pwdchars =~ s/[_\W]+//g if ( $flags{symbols} ); $pwdchars =~ tr/1Il0O//d if ( $flags{confusable} );
Again, it's just me, but I like to avoid unnecessary parentheses and to use C<for> just for aliasing when it's convenient. Thus
my $pwdchars = join '', map chr, 0x21 .. 0x7e; for ($pwdchars) { s/\d+// if $flags{numbers}; s/[A-Z]+// if $flags{uppercase}; s/[a-z]+// if $flags{lowercase}; s/[_\W]+//g if $flags{symbols}; tr/1Il0O//d if $flags{confusable}; }
# If user triggered the --help option flag, display and exit if ($flags{help}) { &DisplayUsage(); exit(); }
Printing a message and exiting amounts to much of what die does, however the latter is specifically aimed at trapping errors and correctly prints to STDERR, while I don't think that issuing --help is one and thus I agree with you that it should go to STDOUT and exit with status 0. However rather than a DisplayUsage() sub, I would adopt a USAGE() one which just returns the usage screen as a single string, then I would
print(USAGE), exit if $flags{help};
But I would also include the usage screen in the error message, in case there are errors.
Much more importantly: the &-form of sub call is obsolete, most likely not to do what you mean and almost never required nowadays.
my $kill=0; # flag to stop the script if input is invalid (or - +-help is used) my @errmsg; # error message descriptions
You don't need the $kill flag: if @errmsg is empty there won't have been errors and if it contains something, then there will have been.
if ($flags{qty} == 0 || $flags{qty} < 0) { $flags{qty}=1; }
That is simply <=, thus
$flags{qty}=1 if $flags{qty} <= 0;
or
$flags{qty}=1 unless $flags{qty} > 0;
or
$flags{qty}=1 unless $flags{qty} >= 1;
if ( length($pwdchars) == 0) {
That could be
if ($pwdchars) {
Because the empty string is a false value.
# Check that user has passed only 1 argument (LENGTH) other than optio +ns flags, warn user, kill script if ($#ARGV > 0 || $#ARGV < 0) {
That could be
unless (@ARGV==1) {
# Check for only numeric input in LENGTH argument, warn user, kill scr +ipt if ($ARGV[0] !~ /^[0-9]+$/) {
If the user doesn't pass any arguments (but options), then the previous error will be issued, but $ARGV[0] will be undef and perl will issue a warning too, since you have correctly enabled them.
BTW: I think that error collection and check could be cleaner if factorized away in suitable subs.
# If any of the above validation tests triggered the $kill flag... if ($kill == 1) { print "\n** GENPASS ERROR ---------------------------------------- +-----------------"; print "\n** ".@errmsg." Error(s) found"; # display number of +errors foreach my $err (@errmsg) { # display error messages print "\n".$err; } print "\n**\n** Type genpass --help for command usage\n"; print "** -------------------------------------------------------- +---------------\n\n"; exit(); # exit script }
Well, those are errors, thus I think they would be better printed to STDERR and the program should not exit cleanly. Thus definitely a job for die. Also, rather than pointing to --help, I would include the usage screen in the error message.
Incidentally, you may take advantage of the repetition (x) operator...
for ( 0..$pwdlen-1 ) { $pwd .= substr( $pwdchars, rand( $limit ), 1 ); }
Since $pwdlen is only a counter, how 'bout 1..$pwdlen? Also, you may avoid using $pwd with map and join, and basically you have a substr-based "reimplementation" of Perl's idiomatic $array[rand @array], so perhaps you should use a @pwdchars array, although I admit that a single string is handy for excluding charachters as you did.
Putting all of the above (and a few minor cosmetic touches) together:
#!/usr/bin/perl use strict; use warnings; use Getopt::Long; sub collecterr; sub chkerr (); sub USAGE (); Getopt::Long::Configure('bundling'); my %flags=map {$_ => 0 } qw/symbols numbers uppercase lowercase confus +able help/; $flags{qty}=1; GetOptions( 's|S|symbols' => \$flags{symbols}, 'n|N|numbers' => \$flags{numbers}, 'u|U|uppercase' => \$flags{uppercase}, 'l|L|lowercase' => \$flags{lowercase}, 'c|C|confusable' => \$flags{confusable}, 'q|Q:i' => \$flags{qty}, 'help' => \$flags{help} ); print(USAGE), exit if $flags{help}; $flags{qty}=1 if $flags{qty} <= 0; my @pwdchars=do { local $_=join '', map chr, 0x21..0x7e; s/\d+// if $flags{numbers}; s/[A-Z]+// if $flags{uppercase}; s/[a-z]+// if $flags{lowercase}; s/[_\W]+//g if $flags{symbols}; tr/1Il0O//d if $flags{confusable}; no warnings 'uninitialized'; /./g; }; collecterr "0x1: At least 1 character-type must be included" unless @p +wdchars; collecterr "0x2: Incorrect number of arguments passed" unless @ARGV==1 +; my $len=shift; if (defined $len) { collecterr "0x3: Invalid input. LENGTH argument must be a numeric +value" unless $len =~ /^[0-9]+$/; } chkerr; print +(map $pwdchars[rand @pwdchars], 1..$len), "\n" for 1..$flags{qty}; # -- subs -- { my @errmsg; sub collecterr { push @errmsg, @_ } sub chkerr () { return unless @errmsg; my $pre="\n**"; die "$pre GENPASS ERROR ", "-" x 56, "$pre " . @errmsg . " Error(s) found", (map "$pre $_", @errmsg), "$pre ", "-" x 71, USAGE; } } sub USAGE () { <<" USAGE"; Usage: genpass [-OPTIONS] LENGTH Generate secure passwords LENGTH characters long. -s, --symbols\t\tExclude symbols. -n, --numbers\t\tExclude numbers. -u, --uppercase\t\tExclude uppercase letters. -l, --lowercase\t\tExclude lowercase letters. -c, --confusable\tExclude confusable characters like: l,I,1,0, +O -q(X)\t\t\tCreate X number of passwords. --help\t\t\tDisplay this usage screen. Report bugs, comments, and questions to jbrown_home\@yahoo.ca USAGE } __END__
Some tests:
All the possible error combinations:
genpass -snul 10 genpass genpass abc genpass -snul genpass abc def genpass -snul abc genpass -snul abc def
A successful run:
C:\temp>genpass -scq10 10 v36Bhe2mE4 GULShqLdFi XeduriA83S h77kYA6Qdf qLkXNB8fqo wBrFaSr7cZ EfcZNgtvmD 2ZqYMq6NS4 jRUhKdfs46 mV2qZJVnWb
|
|---|