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

This is from 798213 where I was asking help in detecting arrow keys. Anyway, I found this bash script which is exactly what I want. Can someone help me translate this to Perl?

What baffles me is the line  read -n3 key where it reads 3 characters. I'm not sure if I can use Term::ReadKey for this.
#!/bin/bash # http://www.codelibary.com/Bash/Arrow-key-detect.html arrowup='\[A' SUCCESS=0 OTHER=65 echo -n "Press a key... " # May need to also press ENTER if a key not listed above pressed. read -n3 key # Read 3 characters. echo -n "$key" | grep "$arrowup" #Check if character code detected. if [ "$?" -eq $SUCCESS ] then echo "Up-arrow key pressed." exit $SUCCESS fi exit $OTHER

Replies are listed 'Best First'.
Re: Translating Bash to Perl
by almut (Canon) on Sep 30, 2009 at 20:54 UTC

    You could try something like this:

    #!/usr/bin/perl use Term::ReadKey; my %seqs = ( "\e[A" => "Up", "\e[B" => "Down", "\e[C" => "Right", "\e[D" => "Left", "\e[5~" => "PgUp", "\e[6~" => "PgDn", # ... ); ReadMode 4; # raw mode my $buf = ''; while (my $key = ReadKey()) { last if $key eq 'q'; # hit 'q' to quit loop $buf .= $key; $buf = substr($buf, -4); # keep last 4 bytes my $escseq = substr($buf,rindex($buf,"\e")); if (exists $seqs{$escseq}) { print "$seqs{$escseq}\n"; } } ReadMode 0; # reset tty

    (There are a few subtle issues with this approach, but it should give you an idea how to approach the problem...)

    update: you could also use a dispatch table to execute subs when a certain key is pressed:

    #!/usr/bin/perl use Term::ReadKey; my $n; my %seqs = ( "\e[A" => sub { $n++; }, # Up "\e[B" => sub { $n--; }, # Down "\e[C" => sub { $n += 10; }, # Right "\e[D" => sub { $n -= 10; }, # Left "\e[5~" => sub { $n += 100; }, # PgUp "\e[6~" => sub { $n -= 100; }, # PgDn ); ReadMode 4; # raw mode my $buf = ''; while (my $key = ReadKey()) { last if $key eq 'q'; # hit 'q' to quit loop $buf .= $key; $buf = substr($buf, -4); # keep last 4 bytes my $escseq = substr($buf,rindex($buf,"\e")); if (exists $seqs{$escseq}) { $seqs{$escseq}->(); # call associated subroutine print "$n\n"; } } ReadMode 0; # reset tty
Re: Translating Terminal Input Code from Bash to Perl
by jakobi (Pilgrim) on Sep 30, 2009 at 20:50 UTC

    2 ideas:

    a) the bad one: continue translating the rest into perl and worry later by temporarily using $key=`read -n3 key; echo \$key`;. Advantage: you stick to the task at hand.

    b) the trivial one would be something like $c=""; do{$c.=ReadKey(0); last if $c=~/[\x0a]/} for (1..<small number for sequence length max>);. (0x0a/LF would also resynchronize the user's notion of 'char sequence starts' with the terminal's notion - 'the may need to press enter' bit).

    both quick&dirty approaches should be only used for a very small number of expected keys. At least the user can resynch the tty by pressing enter, so it's not too bad.

    Maybe there's a simple module in between ReadKey and full Curses to get the whole key sequence into a variable. Anyone has seen possible candidates?

    Might be quest-level stuff for the general case:)

    update:

    please also include non-curses / partial terminal handling modules, if they offer abstractions beyond ReadKey.

    Thx to bichonfrise for his pointer Curses::Simple.

    My own hoard of never-used just-in-case-examples lists perlmenu, Curses/Perl::UI, and Curses::Widgets, all of which are rather complete user interfaces on a level far higher than ReadKey. Lower than ReadKey and of historic interest is the approach in Larry Wall's process-killer zap of camelbook fame. Might have appeared already in the 1st Ed as it was pure perl4 and eminently hackable.

      When I try this,
      #!/usr/bin/perl $key=`read -n3 key; echo \$key`; print "$key\n";
      I get this error...
      read: 1: Illegal option -n
        Don't expect portability from shell builtins across multiple versions or multiple shells. Try w/o the -n3, but then you always need to press CR.
      When I try this,
      #!/usr/bin/perl use Term::ReadKey; use strict; my $c=""; do{$c.=ReadKey(0); last if $c=~/[\x0a]/} for 1..3; print "$c\n";
      I still have to press ENTER key for the input to stop after the 3 characters. For example,
      $# 12345 <ENTER KEY> 123
        Hm. Try Almut's post's below: it shows an use of the ReadMode 4 setting. For more info, read perldoc -m Term::ReadKey.
Re: Translating Bash to Perl
by ikegami (Patriarch) on Sep 30, 2009 at 20:32 UTC
    There's nothing new here. It checks for \x1B, "[" and "A" in a row, and you already knew that.
Re: Translating Bash to Perl
by bichonfrise74 (Vicar) on Sep 30, 2009 at 21:41 UTC
Re: Translating Bash to Perl
by Anonymous Monk on Sep 30, 2009 at 20:47 UTC
    But how do I check this?  \x1B, "[" and "A". Please help. I'm really puzzled.

    Also, the bash script will not wait for the Enter key to be pressed.