Is it possible to clear the terminal with Term::ReadKey? - perl

Is there a way to do this with the Term::ReadKey-module?
#!/usr/bin/env perl
use warnings;
use 5.012;
use Term::Screen;
say( "Hello!\n" x 5 );
sleep 2;
my $scr = Term::Screen->new();
$scr->clrscr();

I don't know why Term::ReadKey would provide such a feature or if it does. But, how about:
#!/usr/bin/env perl
use strict; use warnings;
*clrscr = $^O eq 'MSWin32'
? sub { system('cls') }
: sub { system('clear') };
print "Hello\n" for 1 .. 5;
sleep 2;
clrscr();

Not sure why you want to use Term::Readkey for clearing the screen. It definitely does not have that capability. Are you trying to use something that's part of the standard Perl installation? You can use Term::Caps which is part of the standard Perl installation. Unfortunately, it requires the Termcaps file to be on the system, and Windows doesn't have that.
use Term::Cap;
#
# Use eval to catch the error when TERM isn't defined or their is no
# Termcap file on the system.
#
my $terminal;
eval {$terminal = Term::Cap->Tgetent();};
#
# Use 'cl' to get the Screen Clearing sequence
#
if ($#) { #Most likely a Windows Terminal
system('cls'); #We really should be doing the 2 line below
# my $clear = "\e[2J"; #But, it doesn't seem to work.
# print "$clear"; #Curse You! I'll get you yet Bill Gates!
} else { #A Real Computer
my $clear = $terminal->Tputs('cl');
print "$clear";
}
print "All nice and squeeky clean!\n";
I tried printing the ANSI Escape sequence if it was a Windows Terminal, but it doesn't seem to work.
I hate doing system calls because there is a security risk . What if someone changed the cls command on you?

Term::Readkey does not provide this function directly, but usually the key combination to clear the screen in a Terminal is ^L (Control-L):
Binary
Oct
Dec
Hex
Asc
Sym
Text
00001100
14
12
c
ff
^L
Form Feed (Next Page)
So, if you want to build this in to your application using that module, you can do something like this:
use Term::ReadKey;
# Perform a normal read using getc
my $key = ReadKey( 0 );
# If ^L was pressed, clear the screen
if ( ord $key == 12 ) { print "\e[2J" }
The above example uses the raw escape sequence \e[2J which clears the entire screen. You also have the following alternatives:
Sequence
Function
\e[J
Clears from cursor until end of screen
\e[0J
Clears from cursor until end of screen
\e[1J
Clears from cursor to beginning of screen
\e[2J
Clears entire screen
\e[K
Clears from cursor to end of line
\e[0K
Clears from cursor to end of line
\e[1K
Clears from cursor to start of line
\e[2K
Clears entire line
The escape code \e refers to ASCII character number 27:
Binary
Oct
Dec
Hex
Asc
Sym
Text
00011011
33
27
1b
esc
^[
Escape
See VT100 Escape Sequences for more information.
This should even work if you're using Windows, since apparently this works there nowadays.

Related

Perl - How to output all on one line

I'm new to PERL but have picked it up rather quickly as I work in C.
My question seems simple, but for the life of me I cant find a simple answer. Basically I want to print something on the same line as user input
Example
print "Please Enter Here: ";
my $input = <STDIN>;
chomp $input;
print " - You Entered: $input";
Output
Please Enter Here: 123
- You Entered: 123
This is undesired as I want all of this on one line in the terminal window. At the moment it prints the second string on the line below once the user has pressed the enter key. I guess what i'm going to need to do is something with STDIN like ignore the carriage return or newline but I'm not sure.
Desired Output
Please Enter Here: 123 - You Entered: 123
I don't know why this seems to be a complicated thing to google but I just haven't fathomed it, so any help would be appreciated.
Ta
Well, this is interesting...
First, you'd have to turn off terminal echoing, using something like IO::Stty. Once you do that, you could use getc. Note that the getc perldoc page has a sample program that could be used. You can loop until you get a \n character as input.
You can also try Term::ReadKey.
I have never done this myself. I'll have to give it a try, and if I succeed, I'll post the answer.
The Program
Ended up I already had Term::ReadKey installed:
#! /usr/bin/env perl
#
use warnings;
use strict;
use feature qw(say);
use Term::ReadKey;
ReadMode 5; # Super Raw mode
my $string;
print "Please Enter Here: ";
while ( my $char = getc ) {
last if ord( $char ) < 32; # \r? \n? \l?
$string .= $char;
print "$char";
}
ReadMode 0;
say qq( - You entered "$string".);
I realized that, depending how the terminal is setup, it's hard to know exactly what is returned when you press <RETURN>. Therefore, I punted and look for any ASCII character that's before a space.
One more thing: I didn't handle what happens if the user hit a backspace, or other special characters. Instead, I simply punt which may not be what you want to do.
In the end I did:
"\033[1A\033[45C
It uses the code functions for line up and 45 indent and it works. Thanks for all the help though.

Script is not running or showing errors

I am writing a script that looks at an access_log file to see how many times each search engine was accessed and to see which one is accessed the most. I am sure there are problems with some of my syntax, but I can't even tell since I am not receiving any information back when running it. Any help would be appreciated!
Code:
#!/usr/bin/perl
use 5.010;
$googleCount = 0;
$msnCount = 0;
$yahooCount = 0;
$askCount = 0;
$bingCount = 0;
while (<STDIN>)
{
if (/(google.com)/)
{
$googleCount++;
}
if (/(msn.com)/)
{
$msnCount++;
}
if (/yahoo.com/)
{
$yahooCount++;
}
if (/ask.com/)
{
$askCount++;
}
if (/bing.com/)
{
$bingCount++;
}
}
print "Google.com was accessed $googleCount times in this log.\n";
print "MSN.com was accessed $msnCount times in this log.\n";
print "Yahoo.com was accessed $yahooCount times in this log.\n";
print "Ask.com was accessed $askCount times in this log.\n";
print "Bing.com was accessed $bingCount times in this log.\n";
I am running MacOS. In the terminal I am typing:
perl -w access_scan.pl access_log.1
When I press enter, nothing happens.
Beside the fact that your script didn't work as you expected, there are a few things wrong with your script:
In regexes, the dot . matches any non-newline character. This includes a literal period, but is not restricted to that. Either escape it (/google\.com/) or protect special characters with \Q...\E: /\Qgoogle.com\E/.
There is a programming proverb “Three or more, use a for”. All your conditionals inside your loop are the same, except for the regex. You counts are actually one variable. Your report at the end is the same line multiple times.
You can use a hash to ease the pain:
#!/usr/bin/perl
use strict; use warnings; use feature 'say';
my %count; # a hash is a mapping of strings to scalars (e.g. numbers)
my #sites = qw/google.com msn.com yahoo.com ask.com bing.com/;
# initialize the counts we are interested in:
$count{$_} = 0 foreach #sites;
while (<>) { # accept input from files specified as command line options or STDIN
foreach my $site (#sites) {
$count{$site}++ if /\Q$site\E/i; # /i for case insensitive matching
}
}
foreach my $site (#sites) {
say "\u$site was accessed $count{$site} times in this log";
}
The \u uppercases the next character, this is required to produce identical output.
The say is exactly like print, but appends a newline. It is available in perl5 v10 or later.
The script is trying to read from STDIN, but you are providing the filename to read from as an argument.
"Nothing happens" because the script is waiting for input (which, since you haven't redirected anything to standard input, it expects you to type).
Change <STDIN> to <> or change the command to perl -w access_scan.pl < access_log.1
Your script is reading from stdin, but you're providing your input as a file. You need to redirect thus:
perl -w access_scan.pl < access_log.1
The < file construct provides the contents of your file as the standard input for your script.
The script works fine (I tested it), but you need to feed it with the log in the STDIN:
cat access_log.1 | perl -w access_scan.pl

cygwin seems to be swallowing my carriage returns

I have a perl script that grabs some files from a remote server and I'd like to be able to represent progress. The way I'm trying to do it is like this:
print "\tDownloading comp.reg.binary.sdiff.log...\n";
if(does_file_exist('comp.reg.binary.sdiff.log', #ret)){
$sftp->get("t-gds/log/comp.reg.binary.sdiff.log", $saveDir, sub {
my($sftp, $data, $offset, $size) = #_;
print "\tRead $offset of $size bytes\r";
});
print "\n\tDownloaded.\n";
}else{
print "\tFile not found on server...skipping.\n";
}
However, cygwin seems to swallow carriage returns and doesn't print anything until the last print statement. I doubt it because the script is running too fast, because when I change \r to \n, I can see them print out slowly.
Does anyone have any idea why it's not working the way it should?
Did you remember to set $| to 1 first? Sounds like not.
Your output is probably line-buffered. Try adding $| = 1; to the top of your script. If that works, there are ways to exercise more find-grained control.
Which terminal are you using? If it's not a cygwin-provided pty (such as the rxvt that ships with cygwin) but instead a Windows console, it is possible that perl cannot detect that it's writing to a terminal, and therefore defaults to buffering the output internally until it can write a good-sized chunk out.
Use $| = 1; to suppress this buffering explicitly.

terminal: where am I?

Is there a variable or a function, which can tell me the actual position of the cursor?
#!/usr/bin/env perl
use warnings;
use 5.012;
use Term::ReadKey;
use Term::Cap;
use POSIX;
my( $col, $row ) = GetTerminalSize();
my $termios = new POSIX::Termios;
$termios->getattr;
my $ospeed = $termios->getospeed;
my $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
# some movement ...
# at which position (x/y) is the cursor now?
You could use curses instead. It has getcurx() and getcurx(). There is a CPAN module for it (and the libcurses-perl package in Debian or Ubuntu).
I don't think you can determine the cursor position using termcap.
The termutils manual says:
If you plan to use the relative cursor motion commands in an application program, you must know what the starting cursor position is. To do this, you must keep track of the cursor position and update the records each time anything is output to the terminal, including graphic characters.
Some terminals may support querying the position, as CSI 6 n. If supported, the position will be reported as CSI Pl;Pc R. For example
$ echo -e "\e[6n"; xxd
^[[4;1R
0000000: 1b5b 343b 3152 0a .[4;1R.
This reports the cursor as being at the 1st column of the 4th line (counting from 1).
However, this probably ought not be relied upon, as not very many terminals actually support this.
Printing ESC[6n at ANSI compatible terminals will give you the current cursor position as ESC[n;mR, where n is the row and m is the column
So try reading it with terminal escape characters. Something like that:
perl -e '$/ = "R";' -e 'print "\033[6n";my $x=<STDIN>;my($n, $m)=$x=~m/(\d+)\;(\d+)/;print "Current position: $m, $n\n";'

getting started with vim scripting with perl

I'd like to create a vim function/command to insert an XSD style timestamp. Currently, I use the following in my vimrc file:
nmap <F5> a<C-R>=strftime("%Y-%m-%dT%H:%M:%S-07:00")<CR><Esc>
I'd like to use the Perl code:
use DateTime;
use DateTime::Format::XSD;
print DateTime->now(formatter => 'DateTime::Format::XSD', time_zone => 'America/Phoenix');
But I don't know where to start. I'm aware that I can define a function that uses Perl. Example:
function PerlTest()
perl << EOF
use DateTime;
use DateTime::Format::XSD;
print DateTime->now(formatter => 'DateTime::Format::XSD', time_zone => 'America/Phoenix');
EOF
But when I changed my vimrc to the following, I didn't get what I expected:
nmap <F5> a<C-R>=PerlTest()<CR><Esc>
Could someone point me in the right direction for implementing this? This is the first time I've tried to write functions in vim. Also, I'm using vim 7.2 compiled with perl support.
First off, you'll want to take a look at :help if_perl for the general information about using Perl from within Vim.
For this specific question, I don't think the same approach of entering insert mode and evaluating an expression is the best option. It doesn't look like the language bindings have a way to return a value like that.
What you can do instead is to have the function get the current line, put the time string at the appropriate place, and set the current line again.
fun! PerlTest()
perl << EOF
use DateTime;
use DateTime::Format::XSD;
my ($row, $col) = $curwin->Cursor();
my ($line) = $curbuf->Get($row);
substr($line, $col + 1, 0,
DateTime->now(formatter => 'DateTime::Format::XSD',
time_zone => 'America/Phoenix'));
$curbuf->Set($row, $line);
EOF
endfun
Then your map would simply be nnoremap <F5> :call PerlTest()<CR>.
One issue I've noticed with the above is that it doesn't work well if the line contains characters where 1 byte != 1 column (i.e., tabs, multi-byte characters, etc.). I've played with various ways of trying to fix that, but none of them seem to work very well.
The problem is that there's no easy way to map from Vim's cursor position to a position in the string that represents the cursor's current line.
A different approach, which avoids this problem, is to just use the Perl interface to get the data and then paste the data from Vim.
fun! PerlTest()
let a_reg = getreg('a', '1')
let a_reg_type = getregtype('a')
perl << EOF
use DateTime;
use DateTime::Format::XSD;
my $date = DateTime->now(formatter => 'DateTime::Format::XSD',
time_zone => 'America/Phoenix');
VIM::Eval("setreg('a', '$date', 'c')");
EOF
normal "ap
call setreg('a', a_reg, a_reg_type)
endfun
nnoremap <F5> :call PerlTest()<CR>
If you're happy with running an external Perl script you can try this:
:map <F5> :let #a = system("perl script.pl")<cr>"ap
this runs the command perl script.pl (adjust depending on paths), captures its output in register #a and pastes it at cursor position.