Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

psh (perl testing shell)

by jettero (Monsignor)
on May 11, 2006 at 18:16 UTC ( #548787=sourcecode: print w/replies, xml ) Need Help??
Category: Utility Scripts
Author/Contact Info jettero
Description: I'm sorry to say, I've written another perl shell. This one is designed to help with the development of perl programs by giving you a place try things out. I've been using it to support my own coding for quite some time now and a friend encouraged me to publish it. I wrote this one to compete with hilfe specifically, though python and ruby have similar devices. UPDATE(2/14/08): This has evolved a bit since my original post May 11, 2006 at 14:16 EDT. UPDATE(8/28/8): More evolution. There is now support for paging in less, shell forks, config editing, and assorted perldoc forks.

use common::sense;
use utf8;
use Encode;
use Data::Dumper; $Data::Dumper::Indent = $Data::Dumper::Sortkeys = 1;
use Term::ReadLine;
use File::Slurp qw(slurp);

binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";

if( $ARGV[0] eq "-h" or $ARGV[0] eq "--help" ) { exec "perldoc", $0; e
+xit 900 }

my $term = new Term::ReadLine 'psh';
my $OUT  = $term->OUT || \*STDOUT;
my @s = ();

if( $ENV{TERM} =~ m/(?:xterm|)/ ) {
    local $| = 1;
    print "\e]0; PSH \x07";

*d = *Dumper;
eval 'use Data::Dump qw(dump)';
if( !$@ ) { *d = *dump; *Dumper = *dump; }

    eval "use List::Util qw(first max maxstr min minstr reduce shuffle
+ sum)";
    eval "use Scalar::Util qw(blessed dualvar isweak readonly refaddr 
+reftype tainted weaken isvstring looks_like_number set_prototype);";
    eval "use Math::Units::PhysicalValue qw(PV)";
    unless( $@ ) {
        *pv = *PV;
        *p = *PV;

$SIG{INT} = sub { exit 0 }; END { print "\n" }
$SIG{HUP} = sub { exit 0 };

print "Welcome to Paul's perl shell (type 'help' for documentation)\n"
for my $file ("$ENV{HOME}/.pshrc") {
    if( -f $file and open my $n, $file ) {
        local $/; my $thefile = <$n>;

        no strict "vars";
        eval $thefile; 
        die "(while evaluating $file): $@" if $@;

    eval { # try it... don't freak out if it doesn't work

my $cmd  = 0;

eval { # try it, but don't freak out if it fails...
    my $file = "$ENV{HOME}/.psh_history";
    $term->history_truncate_file($file, 100);
    print "[loaded ", int ($term->GetHistory), " command(s) from histo
+ry file]\n";

$SIG{INT} = sub { print "\n" };

print "\n";
$term->ornaments('', '', '', '');
our $PS1; $PS1 = "\\# psh> " unless $PS1;
while ( defined ($_ = $term->readline(sub_ps1_vars($PS1))) ) {
    s/^\s*//; s/\s*$//; s/[\r\n]//g;
  # s/\bs(\d+)/\$s[$1]/g;

    print "\r\e[2K"; # move to start of line and erase it

    $cmd ++;

    my $less_next_command = 0;

    if( m/^(?:q|e|quit|exit)\b/ ) {

    } elsif( m/^\/?(?:hist|last)\s*(\d*)/ ) {
        my @hist = reverse $term->GetHistory;
        my $max = ($1>0 ? $1-1 : $#hist);

        for my $i ( reverse 0 .. $max ) {
            print "$i - $hist[$i]\n";

    } elsif( m/^\/?less\s+(.+)/ ) {
        $_ = $1;
        $less_next_command = 1;

    } elsif( m/^\/?(?:help|h)/ ) {
        system("perldoc", $0);

    } elsif( $ENV{EDITOR} and $ENV{HOME} and m/^\/?(?:conf?i?g?u?r?a?t
+?i?o?n?)/ ) {
        system($ENV{EDITOR} => "$ENV{HOME}/.pshrc");

    } elsif( m/^\/?(?:doc|mod|m)\b\s*(.+)/ ) {
        system("perldoc", argparse($1));

    } elsif( m/^\s*'\s*(.+)/ and my $args = balanced_single_quotes($1)
+ ) {

    } elsif( m/^\/?(?:func|f)\b\s*(.+)/ ) {
        system("perldoc", "-f", argparse($1));

    } elsif( m/^\/?(?:s|l|stack|list)\b\s*(\d*)/ ) {
        my $max = ($1>0 ? $1-1 : $#s);

        for my $i ( reverse 0 .. $max ) {
            my $r = "";
            if( my $R = ref $s[$i] ) {
                $r = "\t\t[$R]";
            print "s$i = $s[$i]$r\n";

    } elsif( m/./ ) {

        my $less;
        my $ofh;
        if( $less_next_command ) {
            open $less, '|-', ($ENV{PAGER}||'less') or die "unable to 
+open ENV{PAGER}||less: $!";
            $ofh = select $less;
            $less_next_command = 0;

        my $eval_line;
        my $val;
        my $dt;

                my $__before = time;
                local $SIG{INT} = sub { die "interrupted\n"; };
                no strict "vars";
                $val = eval $_; $eval_line = __LINE__;
                $dt = time - $__before;

        if( $dt > 1 ) {
            my $s = "";
               $s = "s" if $dt != 1;
            print "(dt=$dt second$s)\n";

        if( $@ ) {
            if( $@ eq "interrupted\n" ) {
                warn "(operation interrupted with ^C)\n";

            } else {
                $@ =~ s/at \s*$0\s+line $eval_line//; # rare, but can 
+happen if the error uses caller() stuff
                $@ =~ s/at\s*\(eval\s*\d+\)/in command #$cmd/; # make 
+eval into command
                $@ =~ s/\s*line\s+\d+\.$//; # take the line off, it's 
                warn "ERROR: $@\n";

        } else {
            do_val( $val );

            my $res;
            if( ref $val and not blessed $val ) {
                local $Data::Dumper::Indent = 0;

                $res = Dumper($val);
                $res =~ s/\$VAR\d\s*=\s*//;

            } else {
                $res = $val;

            $res =~ s/([^\n[:print:]])/sprintf('\x%02x', ord($1))/eg;

            print "\$s[0] = $res\n";

        if( $less ) {
            select $ofh;
            close $less;

        # this is actually automatic
        # $term->addhistory($_) if /\S/;

    print "\n";

sub sub_ps1_vars {
    my $p = shift;

    $p =~ s/\\#/$cmd/eg;

    return $p;

sub do_val {
    my $v = shift;
    unshift @s, $v unless "$s[$#s]" eq "$v";
    pop @s while @s > 50;

sub argparse {
    my $args = shift;

    #TODO: handle quotes

    return split /\s+/, $args;

sub balanced_single_quotes {
    my $v = shift;
    my $cnt = () = $v =~ m/\'/g;

    return undef unless ($cnt/2) == int ($cnt/2);
    return $v;


=head1 NAME

psh -- yet another perl shell, complete with fun


I wanted a hilfe (pike shell) or python shell like setup for
perl.  Because I designed the shell through actual use, I ended
up including a few handy shortcuts and commands.

=head1 THE STACK

Everything returned from expressions you type is dumped into the
stack (@s).  The most recent value is s0.  You can type the literal
's0' anywhere in an expression and psh will substitue '$s[0]'
(which also works).  You can similarly type 's15' for '$s[15]'.
The @s never grows bigger than 50.

You can view the stack with: 'list', 'l', 'stack', and 's'.
These commands take an optional number (e.g. 'l 10') argument to
limit the lines printed.


Your expressions are evaluated under 'use strict'; but also under
no strict 'vars'.  Warnings are not enabled, but you can 'use
warnings' in your .pshrc.

=head1 HISTORY

The history is nothing fancy.  I highly recommend installing
Term::ReadLine::Gnu, but that is a personal preference I suppose.

** However, your history will NOT save until you install it **

You can list your history with 'last'.  Presently there is no way
to actually execute something from history other than the obvious
arrow keys and/or vim keys (iff applicable).


You can lead each command with a '/' if you desire.  Why would
you want to? IRC and TinyFugue habits?  The '/' is optional.

    last, hist        - show the history
    s, l, stack, list - show the stack
    config, conf      - edit your config (iff $ENV{EDITOR} set)
    less              - pipe the results to less, if found in path
    doc, mod, m       - fork of perldoc <something>
    func, f           - fork of perldoc -f <something>
    '                 - arbitrary fork (checks for unbalanced 's)
    help              - this document
    q, e, quit, exit  - exit


    *p  = *PV     -- from Math::Units::PhysicalValue (if available)
    *pv = *PV     -- from Math::Units::PhysicalValue (if available)
    *d  = *Dumper -- from Data::Dumper               (if available)


    (Disclaimer: This may be a plug for PV. Meh.)
    psh> p "3,000 ft"
    psh> p "2 minutes"
    psh> ($s[1]/$s[2]) + "0 miles/hour"

Violla, $s[0] is now set to 17.05 miles/hour!

Lastly, because you probably do not even have PV installed, but most l
+ikely do have Data::Dumper (since it's required):

    psh> [qw(lol dude!)]
    psh> d $s[0]
    psh> d [1, 2, 3]

Oh and one more because strange attractors are neat.

    psh> 7
    psh> sqrt $s[0]
    psh> sqrt $s[0]
    psh> sqrt $s[0]

=head1 FILES

    $ENV{HOME}/.psh_history - contains your command history
    $ENV{HOME}/.pshrc       - evaled at starttime if it exists

=head1 PS1

I intend to add many bash substitutions, but for now only \# (cmd
number) actually works.  You can (and possibly should) set your
PS1 in your .pshrc. I choose this because I like blue:

    $PS1 = "\e[1;34m\\# psh>\e[0;37m ";

=head1 AUTHOR

Paul Miller <>


Public Domain!  

I relinquish all my rights to anything written in this
document/program.  However, I politely request that you leave my
name on the project unless you rewrite, add, or alter the project
in such a way that the diff -u is bigger than the original source

=head1 SEE ALSO

NOTE: In a few ways this is a reduplication of the perl debugger.
In many other ways, it is most definitely not.

perl(1), perldebug(1), perldebtut(1)


Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: sourcecode [id://548787]
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (6)
As of 2018-02-24 20:57 GMT
Find Nodes?
    Voting Booth?
    When it is dark outside I am happiest to see ...

    Results (311 votes). Check out past polls.