Вопрос

A simple perl script:

#!/usr/bin/perl
$num = `sed "s/\([^ ]*\).*/\1/"`;
print "$num";

$total = `sed "s/[^ ]*\(.*\)/\1/"`;
print "$total";

$div = $num / $total;
print "div = $div \n";

When I run it:

$ echo 4 10 | ./test.pl 
4 10
Illegal division by zero at ./test.pl line 11.
Это было полезно?

Решение 3

Change the code like this and the answer will be obvious:

#!/usr/bin/perl

$num = `sed "s/\([^ ]*\).*/\1/"`;

print "num = $num\n";

$total = `sed "s/[^ ]*\(.*\)/\1/"`;

print "total = $total\n";

$div = $num / $total;

print "div = $div\n";

Другие советы

Looking at your code, I am somewhat confused. First by how one would come up with such a strange idea, and second how it works.

I assume that what you want is to parse a string sent to Perl via STDIN. There is a very simple and idiomatic way to do that, that does not involve the strange usage of sed inside backticks: Using the diamond operator <>, which is a file handle that reads from STDIN or ARGV (file name argument list), depending on which is given.

use strict;
use warnings;               # always use these two pragmas

my $input = <>;                         # input is the string "4 10"
my ($num, $total) = split ' ', $input;  # splitting on whitespace
my $div = $num / $total;                # ... etc

Normally, one would chomp input from STDIN to remove the trailing newline, but since we split on whitespace here, the newline is removed anyway.

As to why you get the illegal division by zero error, that is because your $total variable is undefined, which translates to the number zero 0 when used in a numerical context, such as with the division operator /. If you had used use warnings, you would have gotten the error

Use of uninitialized value $total in division (/) at script.pl line 10.

Which would no doubt have told you something about what the error was.

Your code doesn't work as expected, because you don't understand the escaping rules of strings in Perl, and because you are confused how STDIN is inherited.

In double quoted strings, unknown escapes like \( are ignored by dropping the backslash. Therefore: "\(" eq "(". The \1 is usually not a replacement operator, but a octal escape sequence: "\1" eq "\01" && "\1" eq "\x01" – it is the character 0x01 “Start of Heading”. So your first line is identical to

$num = `sed "s/([^ ]*).*/\x01/"`;  # this won't change your input

As you can see, the regex is actually buggy, and won't even match your input.

The backtick operator threats its contents exactly like a double-quoted string, then passes the contents to the shell. The shell also has escapes, so quite often double escaping has to be done.

The shell is launched by forking from your script. The child process inherits all file descriptors including STDIN. The child execs the shell which then forks off a sed process, which reads all input from STDIN, which is still the same as the STDIN of your original script.

Now when you execute the 2nd command, STDIN is empty, and sed prints nothing. At this point, $total will contain the empty string. When used as a number, that string is interpreted as zero, leading to your error.

The solution

The solution is to stop treating Perl as a glorified shell, because Perl isn't as good for stringing commands together as actual sh. To parse your input, use the built-in regexes of Perl. To read input, use the builtins, not shell idioms.

First, start every script with:

use strict;
use warnings;

For example, this forces us to properly declare all variables. After we do that, running your script emits the following warnings and errors:

Argument "" isn't numeric in division (/) at script.pl line 12.
Argument "4 10\n" isn't numeric in division (/) at script.pl line 12.
Illegal division by zero at script.pl line 12.

So you are actually calculating "4 10 \n" / "", not what you intended.

To read input, we use the <> diamond operator. Each line then lands in the $_ default variable. We could use a regex like /([0-9]+)/ to extract the numbers, but we'll rather split each line at whitespace:

while (<>) {
  my ($num, $total) = split;
  print $num / $total, "\n";
}

We then perform the division, and print out the result along with a newline. If you are using any non-ancient perl, you can also use feature 'say', which enables the say function: Exactly like print, but automatically adds a newline.

Instead of giving the arguments through STDIN etc. you could also specify them on the command line. The command line arguments are accessible through the @ARGV array. Now:

my ($num, $total) = @ARGV;
say $num / $total;

Invocation:

$ perl script.pl 4 10

For some tasks, Perl isn't the fitting tool. If you want to write a shell script, write an actual shell script:

#!/bin/bash
input="4 10"
num=`echo $input | sed 's/\([^ ]*\).*/\1/'`
total=`echo $input | sed 's/[^ ]*\(.*\)/\1/'`
echo `bc <<<"scale = 5; $num / $total"`
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top