Perl Plumber.

Goal: A script that continuously show date and time, with Dzen2, and Conky.

Before you dip your toe to scripting, you might desire to know the reason by reading this overview.

Reading


Piping and Forking in Many Languages

This article is one part of a collection. All integrated, on related to another. So we can compare each other quickly.

Tutorial/ Guidance/ Article: [ Pipe Overview ] [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]

Source Code Directory: [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]


Start Simple

Welcome to n00berland. Begin with simple script. We will use this loop as a source feed to pipe. This step won’t introduce Pipe nor Fork.

This script only show an infinite loop showing local time. Each updated in one second interval. We manage this interval by delaying, using sleep code.

Source:

#!/usr/bin/perl

use warnings;
use strict;
use Time::Piece;
use IO::Pipe;

my $timeformat = '%a %b %d %H:%M:%S';

my $datestr;
while(1) {
    $datestr = localtime->strftime($timeformat);
    print "$datestr \n";
      
    sleep 1;
}

Call to this simple code would produce time marching, one after another, below the command line prompt.

Pipe: Basic

Similar Code: [ BASH basic ] [ Perl basic ] [ Python basic datetime ] [ Python basic time ] [ Ruby basic ] [ PHP basic ] [ Lua basic ] [ Haskell basic ]


External Command as Source Feed

Beside previous simple loop that is used as Internal Command, this tutorial also provide Conky as External Command in asset directory. I made it as simple as possible.

Source:

conky.config = {
    out_to_x = false,
    out_to_console = true,
    short_units = true,
    update_interval = 1
}

conky.text = [[\
${time %a %b %d %H:%M:%S}\
]]

Spawning Using System Shell

Using system shell is simple and straightforward. But it does not have any ability, to stream internal function process, that required later on this article.

Source:

#!/usr/bin/perl
# Using system shell

use warnings;
use strict;
use File::Basename;

# reset the terminal, for use with less
system('reset');

my $dirname = dirname(__FILE__);
my $path    = "$dirname/../assets";
my $cmdin   = "conky -c $path/conky.lua";
my $cmdout  = "less"; # or dzen2
my $cmd     = "$cmdin | $cmdout";

# execute pipe
system($cmd);

Similar Code: [ Perl system ] [ Python system ] [ Python popen ] [ Ruby system ] [ Ruby spawn ] [ Ruby shell ] [ Haskell system ]


A Unidirectional Pipe Between External Command

Unidirectional means one way communication.

A pipe has a read end and a write end.

This step is overview of Pipe between two external command. This short script is using conky as pipe source feed and less as pipe target. Showing time and date forever in the console.

This infinite pipe run in time-less fashioned.

I add $dirname, relative to the Perl source, to locate the conky script assets.

At least, there are four mechanism to pipe in Perl. First three using native open, IPC::Open2 and IPC::Open3, and the last, using object oriented IO::Pipe</code.

Source:

Using open:

#!/usr/bin/perl

use warnings;
use strict;
use File::Basename;

my $dirname = dirname(__FILE__);
my $path    = "$dirname/../assets";
my $cmdin   = "conky -c $path/conky.lua";
my $cmdout  = "less"; # or dzen2

open my $pipein, "-|", $cmdin
    or die "Could not open filehandle: $!";

open my $pipeout, "|-", $cmdout
    or die "Could not open filehandle: $!";
    
while(<$pipein>) {
    print $pipeout $_;
    flush $pipeout;
}

close $pipein;
close $pipeout;

Using IO::Pipe:

#!/usr/bin/perl

use warnings;
use strict;
use File::Basename;
use IO::Pipe;

my $dirname = dirname(__FILE__);
my $path    = "$dirname/../assets";
my $cmdin   = "conky -c $path/conky.lua";
my $cmdout  = "less"; # or dzen2

my $pipein  = IO::Pipe->new();
my $hnd_in  = $pipein->reader($cmdin);

my $pipeout = IO::Pipe->new();
my $hnd_ou  = $pipeout->writer($cmdout);

while(<$pipein>) {
    print $pipeout $_;
    flush $pipeout;
}
    
$pipein->close();
$pipeout->close();

Using IPC::Open2:

#!/usr/bin/perl

use warnings;
use strict;

use File::Basename;
use IPC::Open2;

my $dirname = dirname(__FILE__);
my $path    = "$dirname/../assets";
my $cmdin   = "conky -c $path/conky.lua";
my $cmdout  = "dzen2";

my ($rhin, $whin);
my $pidin  = open2 ($rhin,  $whin,  $cmdin)  
    or die "can't pipein: $!";

my ($rhout, $whout);
my $pidout = open2 ($rhout, $whout, $cmdout) 
    or die "can't pipeout: $!";

my $line = '';
while ($line = <$rhin>) {
    print $whout $line;
}

waitpid( $pidin,  0 );
waitpid( $pidout, 0 );

You can see, how simple it is. This would have less output similar to this below.

Pipe: to Less

Your wallpaper might be different than mine.

Similar Code: [ BASH native ] [ Perl uni IO ] [ Perl uni open ] [ Perl IPC open2 ] [ Python subProcess] [ Ruby popen ] [ PHP popen ] [ PHP proc open ] [ Lua popen ] [ Haskell createProcess ]


How does it works ?

Perl act as middle man. $pipein act as standar output handle (stdout), while $pipeout act as standar input handle (stdin), It read in $_ handle $pipein, and write to handle $pipeout. If you don’t flush, it will be buffered until the handle closed. It means, you will never see the output until it closed.

while(<$pipein>) {
    print $pipeout $_;
    flush $pipeout;
}

A Unidirectional Pipe from Internal Function

Using internal function as source feed to external command is straight forward. This should be self explanatory.

Do not forget to flush.

As previous example, we are using three mechanism, open, IPC:Open2 and IO::Pipe. Note that IPC:Open2 has bidirectional capability that we need in next guidance. Bidirectional means, a process can read and write at the same time.

Source:

Using open:

#!/usr/bin/perl
# https://docstore.mik.ua/orelly/perl3/prog/ch16_03.htm

use warnings;
use strict;
use Time::Piece;

my $cmdout  = "less"; # or dzen2

open my $pipeout, "|-", $cmdout
    or die "Could not open filehandle: $!";
    
my $datestr;
my $timeformat = '%a %b %d %H:%M:%S';

while(1) {
    $datestr = localtime->strftime($timeformat);
    print $pipeout "$datestr \n";
    
    flush $pipeout;
    sleep 1;
}

close $pipeout

Using IO::Pipe:

#!/usr/bin/perl

use warnings;
use strict;
use Time::Piece;
use IO::Pipe;

my $cmdout  = "less"; # or dzen2

my $pipeout = IO::Pipe->new();
my $handle = $pipeout->writer($cmdout);

my $datestr;
my $timeformat = '%a %b %d %H:%M:%S';

while(1) {
    $datestr = localtime->strftime($timeformat);
    print $pipeout "$datestr \n";
    
    flush $pipeout;    
    sleep 1;
}

$pipeout->close();

Using IPC::Open2:

#!/usr/bin/perl
# https://docstore.mik.ua/orelly/perl3/prog/ch16_03.htm

use warnings;
use strict;

use Time::Piece;
use IPC::Open2;

my $cmdout  = "dzen2";

my ($rhout, $whout);
my $pidout = open2 ($rhout, $whout, $cmdout) 
    or die "can't pipeout: $!";
    
my $datestr;
my $timeformat = '%a %b %d %H:%M:%S';

while(1) {
    $datestr = localtime->strftime($timeformat);
    print $whout "$datestr \n";
    
    sleep 1;
}

waitpid( $pidout, 0 );

Similar Code: [ BASH pipe ] [ Perl pipe open ] [ Perl pipe IO ] [ Perl IPC Open2 ] [ Python subProcess ] [ Ruby pipe IO ] [ Ruby popen ] [ Ruby open3 ] [ Ruby PTY ] [ PHP popen ] [ PHP proc open ] [ Lua popen ] [ Haskell createProcess ]


How does it works ?

The same as previous. But instead of reading from stdout $pipein, it is managed by internal process using print $pipeout.

    $datestr = localtime->strftime($timeformat);
    print $pipeout "$datestr \n";

Fork Overview

This step use internal function as source feed, as continuation of previous step.. To avoid complexity of longer script, most code is written inside function

This step use dzen2, with complete parameters. This dzen2 is forked, running in the background. Detached from the script, no need to wait for dzen2 to finish the script.

Source:

#!/usr/bin/perl

use warnings;
use strict;
use Time::Piece;
use IO::Pipe;

sub get_dzen2_parameters { 
    my $xpos    = 0;
    my $ypos    = 0;
    my $width   = 640;
    my $height  = 24;
    my $fgcolor = "#000000";
    my $bgcolor = "#ffffff";
    my $font    = "-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*";

    my $parameters = "  -x $xpos -y $ypos -w $width -h $height";
    $parameters   .= " -fn '$font'";
    $parameters   .= " -ta c -bg '$bgcolor' -fg '$fgcolor'";
    $parameters   .= " -title-name dzentop";

    # print $parameters . "\n";    
    return $parameters;
}

sub generated_output {
    my $pipeout = shift;

    my $datestr;
    my $timeformat = '%a %b %d %H:%M:%S';
    
    while(1) {
        $datestr = localtime->strftime($timeformat);
        print $pipeout "$datestr \n";

        flush $pipeout;
        sleep 1;
    }
}

sub run_dzen2 { 

    my $parameters = get_dzen2_parameters();

    my $pipeout = IO::Pipe->new();
    my $childhandle = $pipeout->writer("dzen2 $parameters");

    generated_output($pipeout);
    $pipeout->close();
}

sub detach_dzen2 { 
    my $pid = fork;
    return if $pid;     # in the parent process
    
    run_dzen2();
    exit; 
}

# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----
# main

# remove all dzen2 instance
system('pkill dzen2');

# run process in the background
detach_dzen2();

This step also add system command that kill any previous dzen2 instance. So it will be guaranteed, that the dzen2 shown is coming from the latest script.

Similar Code: [ BASH fork ] [ Perl fork ] [ Python fork ] [ Ruby fork ] [ PHP fork ] [ Lua fork ] [ Haskell fork ]


How does it works ?

Any code after the fork executed in both parent and child. The child process has been detached from parent process. The only different is the $pid.

sub detach_dzen2 { 
    my $pid = fork;
    return if $pid;     # in the parent process
    
    run_dzen2();
    exit; 
}

Polishing The Script

This step, we use conky again, as a source feed. And also parameterized dzen2 as continuation of previous step.

This step add optional transset transparency, detached from script. So we two forks, dzen and transset.

Source:

#!/usr/bin/perl

use warnings;
use strict;
use File::Basename;
use IO::Pipe;

sub get_dzen2_parameters { 
    my $xpos    = 0;
    my $ypos    = 0;
    my $width   = 640;
    my $height  = 24;
    my $fgcolor = "#000000";
    my $bgcolor = "#ffffff";
    my $font    = "-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*";

    my $parameters = "  -x $xpos -y $ypos -w $width -h $height";
    $parameters   .= " -fn '$font'";
    $parameters   .= " -ta c -bg '$bgcolor' -fg '$fgcolor'";
    $parameters   .= " -title-name dzentop";

    # print $parameters . "\n";    
    return $parameters;
}

sub generated_output {
    my $pipeout = shift;
    my $pipein  = IO::Pipe->new();
     
    my $dirname = dirname(__FILE__);
    my $path    = "$dirname/../assets";       
    my $cmd     = "conky -c $path/conky.lua";
    my $handle  = $pipein->reader($cmd);

    while(<$pipein>) {
        print $pipeout $_;
        flush $pipeout;
    }
    
    $pipein->close();
}

sub run_dzen2 { 

    my $pipeout = IO::Pipe->new();
    
    my $cmd = "dzen2 " . get_dzen2_parameters();
    my $handle = $pipeout->writer($cmd);
    
    generated_output($pipeout);
    $pipeout->close();
}

sub detach_dzen2 { 
    my $pid = fork;
    return if $pid;     # in the parent process
    
    run_dzen2();
    exit; 
}

sub detach_transset { 
    my $pid = fork;
    return if $pid;     # in the parent process
    
    sleep 1;
    system('transset .8 -n dzentop >/dev/null');
    
    exit; 
}

# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----
# main

# remove all dzen2 instance
system('pkill dzen2');

# run process in the background
detach_dzen2();

# optional transparency
detach_transset();

This would have dzen2 output similar to this below.

Pipe: to Dzen2

You may use transset-df instead of transset.

Similar Code: [ BASH conky ] [ Perl conky ] [ Python conky ] [ Ruby conky ] [ PHP conky ] [ Lua conky ] [ Haskell conky ]


How does it works ?

Nothing new here.

Lemonbar

I also provide Lemonbar, instead of Dzen2. The code is very similar.

Source:

Similar Code: [ BASH lemon ] [ Perl lemon ] [ Python lemon ] [ Ruby lemon ] [ PHP lemon ] [ Lua lemon ] [ Haskell lemon ]


Coming up Next

There already an advance case of Pipe and Fork. Multitier, and bidirectional.


There above are some simple codes I put together. I’m mostly posting codes so I won’t have any problems finding it in the future.

Thank you for reading.