Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 120 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
120
Dung lượng
1,29 MB
Nội dung
Terminal Input and Output 573 This produces a list resembling (but possibly not exactly the same, depending on where and on what we run the program) the following: > perl getcontrolchars.pl DISCARD => 15 EOF => 4 EOL => 0 EOL2 => 0 ERASE => 127 ERASEWORD => 23 INTERRUPT => 3 KILL => 21 MIN => 1 QUIT => 28 QUOTENEXT => 22 REPRINT => 18 START => 17 STOP => 19 SUSPEND => 26 SWITCH => 0 TIME => 0 This list of operations and characters comes from the terminal, and represents the internal mapping that it is using to process control characters. Many of the operations returned in the list may not be assigned and so have a character value of zero (or 255, depending on the platform) in the returned array – these characters are discarded by the terminal. Note that if the terminal is not a real terminal, which is usually the case, what it receives may already have been processed by something else first. For instance, the X-Window system defines its own character mapping (the xrdb utility can do this), which takes effect before our terminal even sees the character. Likewise, PC keyboards actually generate 16 bit values, which are translated by the operating system into characters before we see them. On UNIX platforms only, we can also alter which control characters trigger which operations, using SetControlChars. This takes a list of key-value pairs as arguments and applies them to the terminal's built-in list. Each pair consists of a name, as returned by GetControlChars, followed by the character or character value. A value of zero disables the operation. For example, we can redefine or disable the delete key by setting the ERASE operation: SetControlChars ERASE => 0; # disables delete SetControlChars ERASE => 2; # sets delete to control-B In the following program we extract and print the list of control characters, alter some of them, and then print it out again. Note that the attempted alterations will not produce any effect on Windows systems (and will, in fact, generate an error): #!/usr/bin/perl # setcontrolchars.pl use warnings; use strict; use Term::ReadKey; TEAMFLY Team-Fly ® Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Chapter 15 574 my %oldcontrolchars = GetControlChars; sub dump_list { my %controlchars = GetControlChars; foreach my $key (sort keys %controlchars) { print "$key\t => ",ord($controlchars{$key}),"\n"; } print "\n"; } dump_list; # disable interrupt, suspend and erase (delete) # change eof to whatever suspend is (i.e. ctrl-D to ctrl-Z) SetControlChars INTERRUPT => 0, EOF => $oldcontrolchars{SUSPEND}, SUSPEND => 0, ERASE => 0; dump_list; # reset control characters to their old values SetControlChars %oldcontrolchars; dump_list; In this program we have disabled the interrupt operation, normally bound to Ctrl-C, and changed the end-of-file (or more accurately end-of-transmission, EOT) character, Ctrl-D under UNIX, to Ctrl-Z, which is more like Windows. Advanced Line Input with 'Term::ReadLine' Perl provides a module called Term::ReadLine as part of the standard library. Although it does little by itself, it provides an interface to a system readline library, if one is installed. Two libraries are currently available (at least on UNIX systems), the standard Perl readline library and the much more capable third party GNU Readline library. GNU Readline is also available under Windows as a Cygwin package. Whichever library we have installed we can use Term::ReadLine to access it – if the GNU library is installed Term::ReadLine will automatically use it, so we do not have to cater for different libraries in our own code. The module Term::ReadLine will work with no underlying readline library, but few of the advanced features supported by a real readline library, like editable command-lines or command- line history traversal will be available. By writing our programs to use Term::ReadLine, however, we can transparently and automatically make use of these features if our program is run on a system where they are installed. Also supported by Term::ReadLine are several standard methods, which we can call on terminal objects created with the module. They are: Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Terminal Input and Output 575 Name Function ReadLine Return the name of the underlying library module new Create a new terminal object readline Read a line. This is the central method of the module addhistory Add a new line to the input line history IN/OUT Return the input and output file handles of the terminal, respectively. See also findConsole MinLine Set a limit on the shortest length of an input line before it is allowed in the input line history findConsole Return an array of two strings containing the appropriate filename strings for opening the input and output respectively. See also IN and OUT Attribs Return a reference to a hash of internal configuration details Features Return a reference to a hash of supported features In addition Term::ReadLine supplies some stub methods. Without an underlying library these have no useful effect, but if one is present they will perform the relevant function. The stubs ensure that we can call these methods even if the library we are using doesn't actually support them. If we want to check if a given method is supported (as we probably should) we can use the Features method to find out. Name Function tkRunning Enable or disable the tk event loop while waiting for input. Perl-Tk only ornaments Enable, disable, or change the decoration of the prompt and input text when using readline newTTY Switch the terminal to a new pair of input and output file handles In addition to the standard methods, the underlying library may define other methods that are unique to it. The GNU library in particular defines a very extensive set of calls, in fact more than we have time to touch on here. For full details, see perldoc Term::ReadLine::Gnu. Each library adds its own set of features to the list returned by Features so we can also test for them before trying to use the corresponding methods. Before calling any methods, however, we first have to create a terminal object. Creating a Terminal Object The Term::ReadLine module is object-oriented, so to use it we first instantiate a terminal object. We give this object a name, presumably a descriptive name for the program, and then optionally typeglobs for the filehandles that we wish to use for input and output: use Term::ReadLine; # use STDIN and STDOUT by default $term = new Term::ReadLine "Demo"; Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Chapter 15 576 # use file handles IN and OUT explicitly $term = new Term::ReadLine "Demo", *IN, *OUT; # use a serial connection (same file handle for both input and output) $serialterm = new Term::ReadLine "Remote", *SERIAL, *SERIAL; Once we have created a terminal object we can use it to both read and write to the terminal. The following script shows the general idea: #!/usr/bin/perl # termobject.pl use warnings; use strict; use Term::ReadLine; my $term = new Term::ReadLine "My Demo Application"; print "This program uses ", $term->ReadLine, "\n"; my $input = $term->readline("Enter some text: "); print "You entered: $input\n"; First we load in the Term::ReadLine module. Then, just for curiosity, we use the ReadLine method to find out the name of the underlying package, if any. Then we use the readline method (note the difference in case) to read a line from the terminal, optionally supplying a prompt. When this program is run it causes Term::ReadLine to look for and load an underlying readline library if it can find one. If it can, it passes control to it for all other functions. The library in turn provides editing functionality for the actual input of text. If we are using the GNU library, we can take advantage of its more advanced features like editable command-lines automatically, since they are provided automatically by the library. In our own code we don't have to raise a finger, which is the point, of course. If we happen to be using a terminal that isn't connected to standard input and standard output, we need to direct output to the right filehandle, which means passing print the right filehandle. The above happens to work because the terminal is connected to standard out, so a simple print works. We really ought to direct output to the terminal's output, irrespective of whether it is standard output our not. Fortunately we can find both the input and output filehandles from the terminal object using the IN and OUT methods: $input_fh = $term->IN; $output_fh = $term->OUT; print $term->OUT "This writes to the terminal"; Once created, the filehandles used by the terminal can (usually) be changed with the newTTY method. This takes two typeglobs as arguments and redirects the terminal to them: $term->newTTY *NEWIN *NEWOUT; Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Terminal Input and Output 577 It is possible, though unlikely, that this will not work if the library does not support the switch. To be sure of success we can interrogate Term::ReadLine to find out whether the newTTY feature (or indeed any feature), is actually supported. Supported Features Due to the fact that different readline implementations support different features of Term::ReadLine, we can interrogate the module to find out what features are actually supported using the Features method. This returns a reference to a hash with the keys being the features supported: #!/usr/bin/perl # features.pl use warnings; use strict; use Term::ReadLine; my $term = new Term::ReadLine "Find Features"; my %features = %{$term->Features}; print "Features supported by ", $term->ReadLine, "\n"; foreach (sort keys %features) { print "\t$_ => \t$features{$_}\n"; } When run with Term::ReadLine::Gnu (available from CPAN) installed, this produces the following output: > perl xxx_features.pl Features supported by Term::ReadLine::Gnu addHistory => 1 appname => 1 attribs => 1 autohistory => 1 getHistory => 1 minline => 1 newTTY => 1 ornaments => 1 preput => 1 readHistory => 1 setHistory => 1 stiflehistory => 1 tkRunning => 1 writeHistory => 1 We should not confuse these features with callable methods. Some, though not all, of these features correspond to methods supported by the underlying library. Unfortunately, the feature names do not always match the names of the methods that implement them. For example the method to add a line of history is addhistory not addHistory. We can use the feature list to check that a feature exists before trying to use it. For example, to change a terminal to use new filehandles we could check for the newTTY feature, using it if present, and resort to creating a new object otherwise: Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Chapter 15 578 sub switch_tty { ($term, *IN, *OUT) = @_; $features = $term->Features; if ($features->{newTTY}) { $term->newTTY(*IN, *OUT); } else { $name = $term->appname; $term = new Term::ReadLine $name, *IN, *OUT; } return $term; } Regardless of which features are actually supported, Term::ReadLine defines stub methods for a selected subset so that calling them will not cause an error in our program, even if they don't have any useful effect. Setting the Prompt Style and Supplying Default Input The readline method takes a prompt string as its argument. This string is printed to the screen using ornaments (if any have been defined) that alter the look of the prompt and the entered text. For example, GNU Readline underlines the prompt text by default. Ornamentation can be enabled, disabled, or redefined with the ornaments method. Enabling and disabling the currently defined ornaments is achieved by passing 1 or 0 (a True or False value) to ornaments: #!/usr/bin/perl # ornament.pl use warnings; use strict; use Term::ReadLine; my $term = new Term::ReadLine "Ornamentation"; # disable ornaments $term->ornaments(0); my $plain = $term->readline("A plain prompt: "); print "You entered: $plain\n"; # enable default ornaments $term->ornaments(1); my $fancy = $term->readline("A fancy prompt: "); print "You entered: $fancy\n"; Alternatively the current ornamentation can be redefined by passing four parameters containing terminal capabilities (as deduced by the Term::Cap module – see later) as a string. The first two are applied before and after the prompt, and the second two before and after the input text. For example: # define ornaments (md = bold, me = normal) $term->ornaments('md, me, ,'); $userd = $term->readline("A user-defined prompt: "); print "You entered: $userd\n"; Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Terminal Input and Output 579 In this example we have used md, which is the terminal capability code for bold, and me, which is the terminal capability code to return to normal. We don't want to change the input line, so we have left those two entries blank. Note that if we have no termcap library this will fail since Term::Cap is used to determine how to handle ornaments. To enable the ornaments subroutine to work without generating a warning, add: # disable warnings for platforms with no 'termcap' database $Term::ReadLine::termcap_nowarn = 1; The GNU version of readline supports a second optional parameter that contains the default input text. This is known as the preput text and we can test to see if it is supported by checking for the preput feature. Since passing extra parameters is not an error, not checking is fine – we simply won't see the default text. Here is a short example of supplying some default text to the readline method: #!/usr/bin/perl # defaulttext.pl use warnings; use strict; use Term::ReadLine; my $term = new Term::ReadLine "Default Input"; my $input = $term->readline("Enter some text: ", "Default Text"); print "You entered: $input\n"; If the preput text is supplied and the library supports it, the input line is automatically filled with the default text. The user can then either delete and replace or edit (because GNU Readline supports in-line editing) the default text, or just press return to accept it. Command-Line History The Term::ReadLine module provides support for command-line history – that is, a record of what has been typed beforehand. This can be used to allow the user to step backward or forward through previous commands, typically using the up and down cursor keys. This functionality is provided automatically (assuming a library is present which supports it) so again we do not have to do anything ourselves to provide it. We can control the history several ways, however. First, we can lie about previously entered commands by using the addhistory method: $term->addhistory("pretend this was previously entered text"); If we have the GNU Readline library we can also remove a line from the history by giving its line number to remove_history: $term->remove_history(1); # remove line 1 from history, GNU only Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Chapter 15 580 We can also, using the GNU library, retrieve the whole history as an array with GetHistory: @history = $term->GetHistory; We can then step through this array and pass the index numbers to remove_history if desired. Of course to prevent the line numbers changing as we proceed, traversing the array in reverse order is recommended. For example, this loop traverses the history array removing all lines that have less than three characters: @history = $term->GetHistory; # traverse in reverse order, to preserve indices in loop foreach my $item (reverse 0 $#history) { $term->remove_history($item) if length($history[$item])<3); } We can actually do this automatically with the standard MinLine method, which should work regardless of the underlying library. Additionally, we can disable the history entirely by passing an undefined value to it: $term->MinLine(3); # only record lines of three plus characters $term->MinLine(undef); # disable history The GNU Readline library goes far beyond these features however. It also provides support for editing, moving around, saving, loading and searching the history. For a complete, if terse, list of available features see the Term::ReadLine::Gnu documentation. Word Completion Some shell programs support the concept of 'completion', where the shell attempts to deduce the rest of a partially entered word from the first few letters. We can provide the same feature within our own Perl programs with either the GNU Readline library or the somewhat simpler (but less able) Term::Complete module. This module supplies the Complete function, which takes a prompt and a list of words for matching against. This is an example of how we might use it: #!/usr/bin/perl # complete.pl use warnings; use strict; use Term::Complete; my @terms = qw(one two three four five six seven eight nine ten); my $input = Complete("Enter some number words: ",@terms); print "You entered: $input\n"; Completion is triggered if we press the Tab key. When this occurs, Term::Complete attempts to match the text entered so far against one of the words in the completion list. If there is a unique match, it fills in the rest of the word. For example, if we were to enter e and then press Tab, Term::Complete would automatically fill in ight for eight, since that is the only word that begins with e. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Terminal Input and Output 581 If there is not a unique match then Tab will produce no useful effect. Instead, we can type Ctrl-D to have Term::Complete return a list of valid matches. If we were to enter t then Ctrl-D, the list two, three, ten would be returned, for example. The Term::Complete module supplies several functions that allow various keys to be redefined, with the curious exception of Tab for completion. None of these functions are exported by the module so we must access them via their full names. They are as follows: Function Keystroke Action Term::Complete::complete Ctrl-D List matching completions, if any Term::Complete::kill Ctrl-U Erase whole line Term::Complete::erase1 del Delete last character Term::Complete::erase2 backspace Delete last character The Term::ReadLine::Gnu package provides a more comprehensive completion mechanism, but depends on the GNU Readline library being installed on the system. This may be more trouble than we want to go to, especially on a non-UNIX or non-Windows system. It is also a much more involved library to program, and is beyond the scope of this book – consult the documentation and the manual page for the bash shell (which makes extensive use of GNU Readline) if available. Writing to the Screen Perl provides a lot of different approaches to writing to a terminal screen, from simply printing to standard output, through low-level terminal control modules like Term::Cap and the POSIX::Termios interface through to very high-level modules like the third-party Curses module. Somewhere in the middle we can find modules like Term::ANSIColor and the third-party Term::Screen, which provide a slightly simpler interface to the features of the low-level modules. The Term::ANSIColor module handles the specific problem of using colors and other text attributes like blinking, bold, or underline. Other commands can be sent to the screen by interrogating the terminal capabilities with Term::Cap. However, since Term::Cap is a rather low-level module, the third-party Term::Screen module provides a few of these facilities in a more convenient form. If we plan to do a lot of screen output, however, such as writing a complete text-based GUI application, then we might want to look at the Curses module. Terminal Capabilities However we want to talk to a terminal, it ultimately boils down to a case of terminal capabilities. There are many different kinds of terminal, both real and simulated, each with its own particular range of features. Even if a terminal supports all the usual features, it may not do it in the same way as another. In order to make sense of the huge range of possible terminals and different terminal features and options UNIX machines make use of a terminal capability or 'termcap' database. A given terminal has a terminal type associated with it. Interested applications can look up in the database to find out how to tell the terminal to do things like move the cursor or change the color of text. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Chapter 15 582 We send commands to terminals in the form of ANSI escape sequences, a series of characters starting with an escape (character 27, or \e). To switch on blue text, for example, we could use: print "\e[34m this is blue text \e[0m\n"; Of course, this relies on the terminal supporting ANSI escape sequences, which is likely on a UNIX system but is not the case for a DOS shell – if characters like e[32m appear on the screen, it's a safe bet that ANSI isn't supported so the rest of this discussion is likely to be academic. Remembering that \e[ m is the sequence for change screen colors and that 34 is the number for blue is hardly convenient, however. Worse, while things like the color blue are standard across all terminals (all color terminals, that is), many other terminal capabilities vary widely in the precise escape sequences that control them. For that reason, rather than write escape sequences explicitly we use Term::Cap to find them out for us. The Term::Cap module is an interface to the terminal capability or 'termcap' database commonly found on UNIX systems that allows us to issue commands to terminals based on what kind of terminal we are using. To use it we create a terminal capability object using Term::Cap, then pass that object information about what we want to do, along with the filehandle of the terminal we want to do it on. In order to use the module therefore, we first have to create a terminal capability object (or termcap object for short) that points to the entry in the termcap database that we want to use. We also need a termcap database for Term::Cap to work with, so again, this is academic for platforms that do not possess one. Creating a Termcap Object Using the Tgetent method, Term::Cap creates terminal capability objects. In order to use it we must pass it a hash reference, which it blesses, populates with capability strings, and returns to us. In order to work out which entry to look up Tgetent needs to know the terminal name, for example ansi for a standard ANSI terminal, vt100 for a terminal that adheres to the VT100 standard and so on. UNIX shell tools like xterm use their own terminal mode, for example xterm, which is a superset of the ANSI terminal that also knows a few things particular to living inside a window, such as resizing the screen. In general we want Term::Cap to look up the entry for whatever terminal it is our program happens to be running in, which it can normally deduce from the environment. To tell Tgetent to look at the environment we pass it an anonymous hash containing a key-value pair of TERM and undef: #!/usr/bin/perl # anonhash.pl use warnings; use strict; use Term::Cap; # create a terminal capability object - warns of unknown output speed my $termcap = Term::Cap->Tgetent({ TERM => undef }); print "Capabilities found: ", join(',', sort(keys %{$termcap})), "\n"; Just to illustrate what this actually does we have looked at the hash that the termcap object actually is and printed out its keys. That's usually a rather rude way to treat an object, but it serves our purpose for illustrative purposes. Run from a UNIX xterm window, this program produces the following output: Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... $termios_stdin->getlflag; printf "\tLocal flag is %b\n", $lflag; # Perlsetlflag($lflag & ~(ECHO | ECHOK)); printf "\tLocal flag is %b\n", $termios_stdin->getlflag; # Perlgetlflag)), "\n"; 595 Chapter 15... verbatim, without embellishment If however no trailing newline is present then Perl will add details of the file and line number to the end of the message (plus a newline) For example: > perl -e 'warn "Eek! A Mouse\n"' # On Windows: perl -e "warn \"Eek! A Mouse\n\"" Eek! A Mouse! > perl -e 'warn "Eek! A Mouse"' # On Windows: perl -e "warn \"Eek A Mouse\"" Eek! A Mouse! at -e line 1 If no message at all... overkill for most applications 60 6 Warnings and Errors Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 'Errno' and The 'POSIX' Module The POSIX module provides a direct interface from Perl to the underlying C standard library Many of the functions in this library set errno and therefore the value of $! in Perl, and can be handled in the same way as standard Perl functions like open... reason for not doing this, but Perl still gives us the choice In this chapter we will explore various options that Perl gives us to deal with warnings and errors Enabling Warnings To enable warnings from the command line we can use the -w switch: > perl -w myscript.pl We can force warnings on, even if the application tries to switch them off by using the -W option instead: > perl -W myscript.pl Conversely,... evaluated in a scalar context, the comma is treated like C's comma operator, which throws away the left argument, which is not what you want See perlref for more on this This information comes from the perldiag.pod file, which is otherwise available as perldoc perldiag The (W void) prefix indicates that this is a warning (as opposed to a fatal error, for instance) in the void category By default use diagnostics... object-oriented interface for programming windows, and secondly by wrapping all of the variants into one Perl subroutine In order to work out which one we actually want, the Curses module merely inspects the number of arguments and their type This makes the whole business of programming Curses applications much simpler Since Curses is a huge library (and not strictly Perl either, for that matter) we... works by making the separator 0 and using bold cyan as the attribute: 5 86 Terminal Input and Output Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com #!/usr/bin /perl # boldbin.pl use warnings; use strict; use Term::ANSIColor; my $number = rand 10_000_000; # my $bintext = sprintf '%b', $number; # if Perl >=5 .6 my $bintext = unpack 'B32', pack('d', $number); $Term::ANSIColor::EACHLINE... Merge and Split Unregistered Version - http://www.simpopdf.com > perl anonhash.pl OSPEED was not set, defaulting to 960 0 at /termcap1.pl line 7 Capabilities found: OSPEED,PADDING,TERM,TERMCAP,_AL,_DC,_DL,_DO,_IC,_LE,_RI,_UP,_ae,_al,_am,_as,_bc,_b l,_cd,_ce,_cl,_cm,_co,_cr,_cs,_ct,_dc,_dl,_do,_ei,_ho,_ic,_im,_is,_it,_k1,_k2,_k3,_k4,_k5,_k6,_k7,_k 8,_k9,_kI,_kN,_kP,_kb,_kd,_ke,_kh,_kl,_km,_kr,_ks,_ku,_le,_li,_md,_me,_mi,_mr,_ms,_nd,_pc,_rc,_... $^W = 0; } Chapter 16 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com This only works for run-time warnings however, since at compile time Perl is clearly not actually assigning variables or running code From Perl 5 .6, use of $^W is deprecated Instead, we can enable (and disable) warnings from inside the application with the warnings pragma, as we have been doing in our previous... these cases we can use the diagnostics pragma to provide a complete (and frequently very long) description of what Perl is trying to tell us To enable it we just write: use diagnostics; As an example, the following program generates a void context warning: #!/usr/bin /perl use diagnostics; 2**2; 60 0 Warnings and Errors Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Note that use . Output 587 #!/usr/bin /perl # boldbin.pl use warnings; use strict; use Term::ANSIColor; my $number = rand 10_000_000; # my $bintext = sprintf '%b', $number; # if Perl >=5 .6 my $bintext =. capability information anyway based on an assumed speed of 960 0bps. In order to silence this, we can feed it a speed from the POSIX module: #!/usr/bin /perl # speed.pl use warnings; use strict; use POSIX; use. line speed explicitly - but 'POSIX::B 960 0' may not be defined my $termcap1 = Term::Cap->Tgetent({ TERM => undef, OSPEED => POSIX::B 960 0 }); Better, we can use the POSIX::Termios