Why does STDIN cause my Perl program to freeze? - perl

I am learning Perl and wrote this script to practice using STDIN. When I run the script, it only shows the first print statement on the console. No matter what I type in, including new lines, the console doesn't show the next print statement. (I'm using ActivePerl on a Windows machine.) It looks like this:
$perl script.pl
What is the exchange rate? 90.45
[Cursor stays here]
This is my script:
#!/user/bin/perl
use warnings; use strict;
print "What is the exchange rate? ";
my #exchangeRate = <STDIN>;
chomp(#exchangeRate);
print "What is the value you would like to convert? ";
chomp(my #otherCurrency = <STDIN>);
my #result = #otherCurrency / #exchangeRate;
print "The result is #{result}.\n";
One potential solution I noticed while researching my problem is that I could include use IO::Handle; and flush STDIN; flush STDOUT; in my script. These lines did not solve my problem, though.
What should I do to have STDIN behave normally? If this is normal behavior, what am I missing?

When you do
my #answer = <STDIN>;
...Perl waits for the EOF character (on Unix and Unix-like it's Ctrl-D). Then, each line you input (separated by linefeeds) go into the list.
If you instead do:
my $answer = <STDIN>;
...Perl waits for a linefeed, then puts the string you entered into $answer.

I found my problem. I was using the wrong type of variable. Instead of writing:
my #exchangeRate = <STDIN>;
I should have used:
my $exchangeRate = <STDIN>;
with a $ instead of a #.

To end multiline input, you can use Control-D on Unix or Control-Z on Windows.
However, you probably just wanted a single line of input, so you should have used a scalar like other people mentioned. Learning Perl walks you through this sort of stuff.

You could try and enable autoflush.
Either
use IO::Handle;
STDOUT->autoflush(1);
or
$| = 1;
That's why you are not seeing the output printed.
Also, you need to change from arrays '#' to scalar variables '$'
$val = <STDIN>;
chomp($val);

Related

Operator <> issue

I create a file named fred:
fred_1
fred_2
fred_3
I write a Perl script named lac.pl:
while(<>){
print reverse <>;
}
Then I execute the below command.
Why did the output miss fred_1, and the program not end, just waiting for my input?
If I update the Perl script like below, the result will be right:
print reverse <>;
You miss the first line, because that was read into $_ with your while(<>).
print reverse <>;
The <> is evaluated in list context. That is, all the remaining input is slurped into a list which is reversed and printed.
Now, the loop goes back to the conditional, waiting for another line on input. Try
print reverse <>;
by itself, not in a loop.
Of course, slurping like this will make the memory footprint of your program proportional to the size of its input which is not practical for large inputs.
If you do need to reverse large files, use File::ReadBackwards:
#!/usr/bin/env perl
use strict;
use warnings;
use File::ReadBackwards;
for my $arg (#ARGV) {
my $reader = File::ReadBackwards->new($arg)
or die "Cannot read '$arg': $!";
while (defined(my $line = $reader->readline)) {
print $line;
}
}
Why was it wrong? Because while(<>) reads a line into $_. And then you ignore the value. Then it waits for your input because you used <> again, after it had finished. So it guesses you want to read from input, because you've used up everything else available.

How to read STDIN into a variable in perl

I want to read from STDIN and have everything in a variable, how can I do this?
I'm know know almost nothing about Perl, and needed to create CGI script to read data from POST request, and was not able to find anything how to do that.
This is probably not the most definitive way:
my $stdin = join("", <STDIN>);
Or you can enable slurp mode and get the whole file in one go:
local $/;
my $stdin = <STDIN>;
[but see man perlvar for caveats about making global changes to special variables]
If instead of a scalar you want an array with one element per line:
my #stdin = <STDIN>;
my $var = do { local $/; <> };
This doesn't quite read Stdin, it also allows files to specify on the command line for processing (like sed or grep).
This does include line feeds.

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.

Perl system + split + array

my name is luis, live in arg.
i have a problem, which can not solve.
**IN BASH**
pwd
/home/labs-perl
ls
file1.pl file2.pl
**IN PERL**
my $ls = exec("ls");
my #lsarray = split(/\s+/, $ls);
print "$lsarray[1]\n"; #how this, i need the solution. >> file.pl
file1.pl file2.pl # but this is see in shell.
The output you see is not from the print statement, it is the console output of ls. To get the ls output into a variable, use backticks:
my $ls = `ls`;
my #lsarray = split(/\s+/, $ls);
print "$lsarray[1]\n";
This is because exec does not return, the statements after it are not executed. From perldoc:
The exec function executes a system command and never returns; use
system instead of exec if you want it to return. It fails and returns
false only if the command does not exist and it is executed directly
instead of via your system's command shell
But using system command will not help you as it does not allow output capturing, hence, the backticks. However, using glob functions is better:
my #arr = glob("*");
print $arr[1], "\n";
Also, perl array indices start at 0, not 1. To get file1.pl you should use print "$lsarray[0]\n".
It is bad practice to use the shell when you can write something within Perl.
This program displays what I think you want.
chdir '/home/labs-perl' or die $!;
my #dir = glob '*';
print "#dir\n";
Edit
I have just understood better what you need from perreal's post.
To display the first file in the current working directory, just write
print((glob '*')[0], "\n");
print <*> // die("No file found\n"), "\n";
(Though using an iterator in scalar context should usually be avoided if the script will be doing anything further.)

Perl substitute with regex

When I run this command over a Perl one liner, it picks up the the regular expression -
so that can't be bad.
more tagcommands | perl -nle 'print /(\d{8}_\d{9})/' | sort
12012011_000005769
12012011_000005772
12162011_000005792
12162011_000005792
But when I run this script over the command invocation below, it does not pick up the
regex.
#!/usr/bin/perl
use strict;
my $switch="12012011_000005777";
open (FILE, "more /home/shortcasper/work/tagcommands|");
my #array_old = (<FILE>) ;
my #array_new = #array_old ;
foreach my $line(#array_new) {
$line =~ s/\d{8}_\d{9}/$switch/g;
print $line;
sleep 1;
}
This is the data that I am feeding into the script
/CASPERBOT/START URL=simplefile:///data/tag/squirrels/squirrels /12012011_000005777N.dart.gz CASPER=SeqRashMessage
/CASPERBOT/ADDSERVER simplefile:///data/tag/squirrels/12012011_0000057770.dart.trans.gz
/CASPERRIP/newApp multistitch CASPER_BIN
/CASPER_BIN/START URLS=simplefile:///data/tag/squirrels /12012011_000005777R.rash.gz?exitOnEOF=false;binaryfile:///data/tag/squirrels/12162011_000005792D.binaryBlob.gz?exitOnEOF=false;simplefile:///data/tag/squirrels/12012011_000005777E.bean.trans.gz?exitOnEOF=false EXTRACTORS=rash;island;rash BINARY=T
You should study your one-liner to see how it works. First check perl -h to learn about the switches used:
-l[octal] enable line ending processing, specifies line terminator
-n assume "while (<>) { ... }" loop around program
The first one is not exactly self-explanatory, but what -l actually does is chomp each line, and then change $\ and $/ to newline. So, your one-liner:
perl -nle 'print /(\d{8}_\d{9})/'
Actually does this:
$\ = "\n";
while (<>) {
chomp;
print /(\d{8}_\d{9})/;
}
A very easy way to see this is to use the Deparse command:
$ perl -MO=Deparse -nle 'print /(\d{8}_\d{9})/'
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
chomp $_;
print /(\d{8}_\d{9})/;
}
-e syntax OK
So, that's how you transform that into a working script.
I have no idea how you went from that to this:
use strict;
my $switch="12012011_000005777";
open (FILE, "more /home/shortcasper/work/tagcommands|");
my #array_old = (<FILE>) ;
my #array_new = #array_old ;
foreach my $line(#array_new) {
$line =~ s/\d{8}_\d{9}/$switch/g;
print $line;
sleep 1;
}
First of all, why are you opening a pipe from the more command to read a text file? That is like calling a tow truck to fetch you a cab. Just open the file. Or better yet, don't. Just use the diamond operator, like you did the first time.
You don't need to first copy the lines of a file to an array, and then use the array. while(<FILE>) is a simple way to do it.
In your one-liner, you print the regex. Well, you print the return value of the regex. In this script, you print $line. I'm not sure how you thought that would do the same thing.
Your regex here will remove all set of numbers and replace it with the ones in your script. Nothing else.
You may also be aware that sleep 1 will not do what you think. Try this one-liner, for example:
perl -we 'for (1 .. 10) { print "line $_\n"; sleep 1; }'
As you will notice, it will simply wait 10 seconds then print everything at once. That's because perl by default prints to the standard output buffer (in the shell!), and that buffer is not printed until it is full or flushed (when the perl execution ends). So, it's a perception problem. Everything works like it should, you just don't see it.
If you absolutely want to have a sleep statement in your script, you'll probably want to autoflush, e.g. STDOUT->autoflush(1);
However, why are you doing that? Is it so you will have time to read the numbers? If so, put that more statement at the end of your one-liner instead:
perl ...... | more
That will pipe the output into the more command, so you can read it at your own pace. Now, for your one-liner:
Always also use -w, unless you specifically want to avoid getting warnings (which basically you never should).
Your one-liner will only print the first match. If you want to print all the matches on a new line:
perl -wnle 'print for /(\d{8}_\d{9})/g'
If you want to print all the matches, but keep the ones from the same line on the same line:
perl -wnle 'print "#a" if #a = /(\d{8}_\d{9})/g'
Well, that should cover it.
Your open call may be failing (you should always check the result of an open to make sure it succeeded if the rest of the program depends on it) but I believe your problem is in complicating things by opening a pipe from a more command instead of simply opening the file itself. Change the open to simply
open FILE, "/home/shortcasper/work/tagcommands" or die $!;
and things should improve.