Perl Page #5: Tips and Tricks


There's strictly no warranty for the correctness of this text. You use any of the information provided here at your own risk.


Contents:

  1. Page Description
  2. Generating random numbers
  3. Check, if the user is 'root'
  4. Date and Time
  5. Conversions Between Decimal, Hexadecimal, Binary and Octal Numbers
  6. Parsing HTML
  7. Getting User-Input on the Linux-Console with Default Value and History-Function
  8. Encrypting and Decrypting Text Using a Password
  9. Installing Perl-Modules with the cpan-Command
  10. Sending Keystrokes to Other Applications on Windows
  11. Perl One-liners
  12. Further Reading


1. Page Description

On this page, I collect further Perl topics. Things, you can do with Perl.


2. Generating random numbers

Just like that:

perl -e 'print int(rand(10)) . "\n";'


3. Check, if the user is 'root'

On Linux, the administrator, who has full access to the system, is called 'root'. This checks, if the user, who is executing the Perl script, is 'root':

#!/usr/bin/perl

use warnings;
use strict;

if ( $> != 0 ) {
    print "\nError: This script must be run as root\n\n"; 
    exit 1;
}

print "User is 'root'.\n";

In the 'English'-module ("use English;"), the system variable "$>" is also called "$EFFECTIVE_USER_ID".


4. Date and Time

This script demonstrates some often-used operations concerning date and time:

#!/usr/bin/perl

use warnings;
use strict;

# Without the brackets, too many functions would be exported
# into the main::-namespace:
use POSIX (); 

# Print current time in central European format including seconds:
print POSIX::strftime("%H:%M:%S", localtime) . "\n";

# On Linux, you can read more about format-strings like "%H:%M:%S"
# by executing the shell-command "info date".

# Print today's date in central European format; the numbers of days and months
# below 10 are zero-padded (like in 09.08.2007):
print POSIX::strftime("%d.%m.%Y", localtime) . "\n";

# Print seconds since epoch (on Linux, epoch is 1.1.1970):
print time . "\n";

# Generate a time-list from seconds since epoch and print it:
my @tml = localtime;
print join(", ", @tml) . "\n";

# Print today's date in central European format using the time-list generated above;
# the numbers of days and months below 10 are blank-padded (like in 9.8.2007):

print $tml[3] . "." . ($tml[4] + 1) . "." . ($tml[5] + 1900) . "\n";

# Convert the time-tuple back to seconds since epoch:

print POSIX::mktime(@tml) . "\n";

There is also a module called "Time::Local".


5. Conversions Between Decimal, Hexadecimal, Binary and Octal Numbers

This script shows, how these number-conversions can be done:

#!/usr/bin/perl

use warnings;
use strict;

my $hexnum = "FF";
my $decnum = 255;
my $binnum = "11111111";
my $octnum = "377";

# Hexadecimal to decimal:

# Hexadecimal numbers start with "0x":
print 0xFF . "\n";

print hex($hexnum) . "\n";
print oct("0x". $hexnum) . "\n";

# Decimal to hexadecimal:
printf("%X\n", $decnum);

# Binary to decimal:

# Binary numbers start with "0b":
print 0b11111111 . "\n";

print oct("0b". $binnum) . "\n";

# Decimal to binary:
printf("%b\n", $decnum);

# Octal to decimal:

# Octal numbers start with "0".
# They are used for example in functions like chmod().
print 0377 . "\n";

print oct($octnum) . "\n";

# Decimal to octal:
printf("%o\n", $decnum);


6. Parsing HTML

To parse HTML, it's better to use a module than trying to figure out a RegEx yourself. "HTML::Parser" is a rather low-level module. "HTML::TokeParser", which is built on top of it, is more convenient for the user. This extracts the texts between all "a href="-links in a HTML file called "test.html":

#!/usr/bin/perl

use warnings;
use strict;

use HTML::TokeParser;

my $filename = "test.html";

my $p = HTML::TokeParser->new($filename);
 
while (my $token = $p->get_tag("a")) {
    my $url = $token->[1]{href} || "-";
    my $text = $p->get_trimmed_text("/a");
    print "$url\t$text\n";
}


7. Getting User-Input on the Linux-Console with Default Value and History-Function

In general, user-input on the Linux-console can be retrieved with:

print "Please enter something: ";
my $input = <STDIN>;
chomp($input);

But what, if you also wanted to set a default value, that could be edited or adopted as input?
And what, if you wanted to have a list of previously entered values, that could be accessed during input with the "cursor up" key, similar to the behaviour of Linux shells?
There are the C libraries GNU Readline and GNU History which provide these features. They can be used from a Perl script using the module "Term::ReadLine". It seems, a valid installation (which is probably already available in most Linux distributions) also needs the modules "Term::ReadLine::Perl" or "Term::ReadLine::Gnu".
Then it is possible to achieve user input with a default value and an entry history function like this:

#!/usr/bin/perl

use warnings;
use strict;

use Term::ReadLine;

my $term = Term::ReadLine->new();

print "\n";

# Switching off the prompt underlining:
$term->ornaments(0);

for my $i (1 .. 10) {
    $term->addhistory("Entry $i");
}

my $prompt = "Please enter something: ";

my $input = $term->readline($prompt, "Please press up");

print "\nYou entered '$input'.\n\n";


8. Encrypting and Decrypting Text Using a Password

First, download the modules "Crypt::CBC" and "Crypt::Blowfish" from the CPAN ("The Comprehensive Perl Archive Network" - The central archive of Perl-modules on the internet) and install them.

Then you should be able to run the following example-script:

#!/usr/bin/perl

use warnings;
use strict;

use Crypt::CBC;

# Just trying to clean the screen:
if($^O eq "linux") {
    system("clear");
}

my $plaintext = "For your eyes only !";
my $password = "Melina";

my $cipher = Crypt::CBC->new(-key => $password,
                             -cipher => 'Blowfish');

my $ciphertext = $cipher->encrypt($plaintext);

print "\nHello !\n\n";
print "I just encrypted some text. It looks like this now\n";
print "(only some of the characters are printed to avoid printing escape sequences):\n\n";

# Printing just some of the characters:

my $i, $a;

for($i = 0; $i < length($ciphertext); $i++) {

    $a = substr($ciphertext, $i, 1);

    if(ord($a) > 31 && ord($a) < 128) {
        print $a;
    }
}

print "\n\nThe password for decryption is: \n\n";

print $password, "\n\n";

print "Let's decrypt again:\n\n"; # like we did last summer :).

$plaintext = $cipher->decrypt($ciphertext);

print $plaintext, "\n\n";

------------------------

Similar result using shell-commands (not Perl):

# Encrypting to file 'encrypted.txt' using password "Melina":
# echo 'Melina' | gpg --batch --textmode --passphrase-fd 0 -o encrypted.txt -c unencrypted.txt

# Decrypting to file 'decrypted.txt' using password "Melina":
# echo 'Melina' | gpg --batch --textmode --passphrase-fd 0 -d encrypted.txt > decrypted.txt


9. Installing Perl-Modules with the cpan-Command

If you use Linux, and you want to install a certain Perl module, you should first check, if it is provided by your Linux distribution. When using OpenSuSE-Linux, you can run "yast2" as root and select "Software Installation". Look for the module there and select it for installation. YaST2 then installs the module, as provided by your distribution, taking all dependencies into account.

If the module is not part of the distribution, but can be found on the CPAN, you can install it with all dependencies by using the shell command "cpan". For example, you can just do as root:

cpan Text::Unidecode

The module and all dependent modules are then downloaded from the CPAN on the internet and are installed on your system. Althought that feels still a bit risky to me, I have to admit, it is convenient.

Of course, you could also download each module by hand and do the installation yourself (perl Makefile.pl; make; make install). The problem is, this can become rather tedious, if the module depends on too many other modules.


10. Sending Keystrokes to Other Applications on Windows

First it needs to be said, that in most cases, sending keystrokes to other applications is a bad idea. Whenever you are tempted to do that, think first, if there isn't a better solution to the problem. In most cases there is.

That being said, with the module "Win32::GuiTest" it is possible to send keystrokes from a Perl script to other applications on Windows. I've only tried this on an old 32-bit Windows version (using "Strawberry Perl" on "Windows XP"). I'm not sure, if this also works on newer systems like "Windows 10". Anyway, here's an example, how to send keys to a running window of "notepad.exe", that has the word "Editor" in its window title:

#!perl

use warnings;
use strict;

use Win32::GuiTest qw/:FUNC :VK/;

sub sendKeyDown {
    my $val  = shift;
    SendRawKey($val, 0);
}

sub sendKeyUp {
    my $val  = shift;
    SendRawKey($val, KEYEVENTF_KEYUP);
}

my ($x, $y) = Win32::GuiTest::GetCursorPos();
print "\nCurrently, the mouse is at position $x" . "x" . "$y.\n";

my @found = Win32::GuiTest::FindWindowLike(undef, "Editor");
for my $i (@found) {
    print "\n$i\n";
}
if ($#found != 0) {
    print "\nCan't proceed: Either too few or too many windows found. Nothing done.\n\n";
    exit 1;
}

my $windowid = $found[0];
Win32::GuiTest::SetActiveWindow($windowid);
Win32::GuiTest::SendKeys("Hello\n");
sendKeyDown(VK_NUMPAD1);
sleep(1);
sendKeyUp(VK_NUMPAD1);

So you can find the id number of the wanted window by its title string. You have to make sure, that exactly one window is found. Then the module can set the focus to this window and send its keys there.

When using the "SendRawKey()" function, the codes of virtual keys can be used.
For example, "VK_NUMPAD0" to "VK_NUMPAD9" represent the keys on the numeric keypad. (If you want the second function of these keys, use the "KEYEVENTF_EXTENDEDKEY" flag: See the module's documentation for details). "VK_LCONTROL" represents the left control key.

To send keys in a similar way on Linux, check out a console program called "xdotool". This can then be used with "system()"-calls from a Perl script.


11. Perl One-liners

Without switches, the perl-command expects the file-name of a perl-script to be executed:

perl myscript.pl

Using the "-e"-switch of the perl-command, you can pass a line of Perl-code to it instead and have it executed, for example:

perl -e 'print "Hello World.\n";'

You can use this construction to pass some data from a pipe to Perl. You must tell Perl to read input from stdin then, so you would use "while(<>){}" around your code:

echo "Hello World" | perl -e 'while(<>) {$_ =~ s/World/Earth/; print $_;}'

The "-n"-switch puts the "while(<>){}"-construction around your code automatically:

echo "Hello World" | perl -ne '$_ =~ s/World/Earth/; print $_;'

(If no argument is passed to Perl's "print"-command, the default-variable "$_" is printed, so instead of "print $_", you can just write "print".)

Instead of a pipe you can use the perl-command on files too. If you have a file called "test.txt" with the content

Hello World
Hello World
Hello World

you can run

perl -ne '$_ =~ s/World/Earth/; print;' test.txt

on it, and get an output to stdout of three times of "Hello Earth".

Using the "-i"-switch, you can also edit files "in place" instead of having the output printed to stdout. The command

perl -ne '$_ =~ s/World/Earth/; print;' -i test.txt

doesn't produce output, but the text inside the file is edited to three times "Hello Earth".
This behaviour is somehow similar to the "sed"-command of Unix/Linux.

You can also pass multiple file-names to the perl-command with the "-i"-switch. This changes the content of several files at once.


12. Further Reading

My little series about Perl continues with "Perl Page #6: Creating 2D Video Games in SDL_perl / SDLx".



Email: hlubenow2 {at-symbol} gmx.net

Back to main-page