Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Thank you in advance. I am writing a short program, a game, that tests your knowledge of GNU/Linux commands and programs (with a whatis entry). I want it to put like flash cards: present the name of the command, wait for the user, then print the description, wait for the user, ad infinitum. The code below shows the first attempt (SLATHERED WITH COMMENTS):

#!/usr/bin/perl #bashflash #tests your memory of bash/gnu-linux commands and cli programs #USAGE: #cd to the directory containing the binarys of the programs #you want to test your knowledge of (DO NOT NEED TO BE ROOT) #pass perl bashflash -`whatis --wildcard *` # use strict; use warnings; #The following loop utilizes a shift-and-push loop #to shift the zeroth element of @ARGV out of the array, into a scalar, #then push the scalar back into the array as the final element. #effectively, this process lets one cycle through the output of #`whatis --wildcard *` indefinitely, *while separating the #command names from their descriptions, like flashcards.* #*In it's current implementation, @ARGV stores the output of `whatis` #word by word: such that elements are delineated by spaces. my $currentcard; #this variable holds onto strings shifted off of @ARG +V #, prints them, then pushes it's contents #back onto the end of ARGV while (1) { #infinite loop START $currentcard = shift; #shift first element #of @ARGV into $currentcard print $currentcard; push (@ARGV, $currentcard); #push $currentcard #(previously element 0) back onto #@ARGV as the last element <STDIN>; #Wait for user } #infinite loop END exit; #the script never gets here, #but I always explicitly exit on the end of my Perl. #and then an empty comment line #

This program puts this to STDOUT:

sage@bash:/bin$ perl bashflash -`whatis --wildcard *` bashflash: nothing appropriate. kmod: nothing appropriate. ntfsck: nothing appropriate. ntfsdump_logfile: nothing appropriate. ntfsmftalloc: nothing appropriate. ntfsmove: nothing appropriate. ntfstruncate: nothing appropriate. ntfswipe: nothing appropriate. plymouth: nothing appropriate. plymouth-upstart-bridge: nothing appropriate. running-in-container: nothing appropriate. static-sh: nothing appropriate. -bash (1) - GNU Bourne-Again SHell bunzip2 (1) - a block-sorting file compressor, v1.0.6 busybox (1) - The Swiss Army Knife

Note, each line after "-bash" had to be summoned by the return key, meaning that (as explained in the comments) @ARGV has stored the command line argument into new elements by word. (This is my current understanding, please correct me.)

#1, the negative testing while

my @currentcard; #this variable holds onto strings shifted off of @ARG +V #, prints them, then pushes it's contents #back onto the end of ARGV while (1) { #infinite loop START $_ = shift; while (m#^(?!-\s)$#) { push (@currentcard, $_); $_ = shift; } print @currentcard; push (@ARGV, @currentcard); #push $currentcard #(previously element 0) back onto #@ARGV as the last element <STDIN>; #Wait for user } #infinite loop END

#2, the while controlled by internal positive testing if

my @currentcard; #this variable holds onto strings shifted off of @ARG +V #, prints them, then pushes it's contents #back onto the end of ARGV while (1) { #infinite loop START $_ = shift; my $witch = 1; while ($witch) { push (@currentcard, $_); $_ = shift; if ($_ =~ m#^(-\s)$#) { --$witch; } } print @currentcard; push (@ARGV, @currentcard); #push $currentcard #(previously element 0) back onto #@ARGV as the last element <STDIN>; #Wait for user } #infinite loop END

Above: an attempt, two of many, to get the "flash card" output I desire. My plan is to instead push the shifted elements of @ARGV onto an Array in a regexp-conditional loop, to attempt to match the "- " in the last element of Array, if it does match "- ", then the code should exit the loop and print everything it collected up to and including the "- ", then pushing it's content back onto the end of @ARGV, and waiting for the return key press. Or the logical equivalent, test positive for iteration with the lack of "- ", and keep adding to Array until shift adds a "- ". As above.

I have tried dozens of variations with positive and negative regular expressions, I even tried /.*/, but every time I try to group the output such that iterations of print are delimited by "- ", just I get blank lines.

Am I using push and shift incorrectly?

Prithee, great archons of perl, behelpeth me.

Replies are listed 'Best First'.
Re: Seeking regexp @ARGV arrays Wisdom
by Laurent_R (Canon) on Dec 21, 2014 at 16:49 UTC
    I don't really understand what you are trying to do, but the way you use shift and push on @ARGV seems correct to me. Just a simple example (without a mountain of comments making your code much more complicated to decipher):
    use strict; use warnings; use v5.14; while (1) { my $current = shift @ARGV; say $current; push @ARGV, $current; say "Hit return or enter 0 to exit"; chomp (my $input = <STDIN>); last if $input eq "0"; } exit 0;
    This works fine:
Re: Seeking regexp @ARGV arrays Wisdom
by Anonymous Monk on Dec 21, 2014 at 16:50 UTC
    I want it to ... present the name of the command, wait for the user, then print the description, wait for the user, ad infinitum.

    That makes sense, but I think that passing the output of whatis to your program via @ARGV is not the right way to go, one reason being that command-line arguments are generally split on whitespace, whereas you would have a much easier time parsing the output of whatis on a line-by-line basis, and then parse each line with a regex (or split).

    There are other methods to get the output of external programs into your Perl script, practically all of which can automatically split the input into lines. The simplest is backticks (``), aka qx, and it's built in to Perl, but that can have issues with shell quoting (and thereby security, depending on usage) and error handling. So I'd recommend using a module, such as: capture from IPC::System::Simple, run3 from IPC::Run3, or capture from Capture::Tiny (there are a couple more but I like these the best).

    Instead, I might use @ARGV to allow the user to pick which commands they want to be quizzed on, e.g. perl bashflash dd echo cp or perl bashflash /bin/* - note that for the latter, fileparse from File::Basename is useful to get only the name of the command.

Re: Seeking regexp @ARGV arrays Wisdom
by Anonymous Monk on Dec 21, 2014 at 16:41 UTC
    I haven't quite understood what you're trying to do but
    m#^(?!-\s)$#
    Will only match an empty string (which you don't have in your @ARGV as far as I can tell). And
    m#^(-\s)$#
    won't match anything either because Bash removes all unquoted whitespace.

    I also have no idea why you're pushing and shifting, so I don't know whether you're using them incorrectly, but my intuition says 'yes'

    Anyway, Perl can do backticks and can change directories, so your users don't need to bother:

    use strict; use warnings; sub usage { die <<END Usage: $0 <directory of binaries you want to test your knowledge of> For example: $0 /usr/bin END } my $path = shift or usage(); -r -x -d $path or usage(); chdir $path or die $!; my @whatis; print "wait a little...\n"; for (`whatis --wildcard * 2>/dev/null`) { chomp; push @whatis, [ split /\s+-\s+/, $_ ]; } print "(control-d to exit)\n\n"; MAINLOOP: while (1) { for (@whatis) { print $_->[0]; defined <STDIN> or last MAINLOOP; print "\t", $_->[1], "\n\n"; } } print "\n";
      `whatis --wildcard *`

      The * will be expanded by the shell before whatis is called. If the intent is to have * be expanded to the files in the current directory, then the --wildcard switch can be dropped, but if the intent is to have the * be used in whatis's database search (which goes beyond the files in the current directory!), the * needs to be quoted, e.g. `whatis --wildcard "*"` - or use a module that can bypass the shell, such as capturex from IPC::System::Simple.

        The * will be expanded by the shell before whatis is called.
        Yes, I think that was what the OP intended. I'm just too lazy to read the manpage for 'whatis'. This thing doesn't seem very useful to me.
Re: Seeking regexp @ARGV arrays Wisdom
by Anonymous Monk on Dec 21, 2014 at 16:45 UTC
    OP here, I SWEAR TO REFRAIN FROM POSTING IN THE FUTURE. I solved my own problem. I formated the output of the shell command into a text file, then wrote this instead:
    #!/usr/bin/perl use strict; use warnings; open ( my $WIIB, '<', "WhatIsInBin"); our @binstrings = <$WIIB>; close ($WIIB); my @cards; for my $line (@binstrings) { push ( @cards, ( substr ($line, 0, 22) ) ); push ( @cards, ( substr ($line, 22) ) ); } print "Bash Flash!\n"; while (1) { for my $currentcard (@cards) { print "$currentcard \n"; <STDIN>; } } exit; #
    I hope this helps someone in the future. Note to self: Keep it simple, Keep trying.