Unwanted double-interpolation in string passed to backticks

H

Henry Law

I think I've done my homework on this, including "How can I call
backticks without shell processing?" in perlfaq8, and a Groups search
on "perl shell escape characters backticks" but I'm none the wiser;
pointers would be welcome.

Here's a test program:
-----------------------
#! /usr/bin/perl

use strict;
use warnings;

print "Enter filename:";
my $filename = <STDIN>;
chomp $filename;

my $ret = `ls -m $filename`;

print "Returned value:$ret\n";
-----------------------

The problem comes when the file name that is entered contains the "$"
character, which in my case it often does. In the test directory
there is a file called "test" and another called "$test". If I run
the program above and enter "test" the result is as expected, thus:

$ ./test
Enter filename:test
Returned value:test

But if I enter the name of the "$test" file, the variable $filename is
interpolated a second time, and since there is no "$test" variable
within the program it comes out as null, thus:

$ ./test
Enter filename:$test
Returned value:bashref.html, c, d, (etc.. the whole directory)

Escaping the $ within the backticks won't work, since I do want Perl
to interpolate $filename, so what to do? My current work-round is a
sub called "shell_execute" which takes the string to be executed
(after one level of interpolation) and then escapes the dollar signs
before running backticks. It works, but ITNABWTDI?

Henry Law <>< Manchester, England
 
A

Anno Siegel

Henry Law said:
I think I've done my homework on this, including "How can I call
backticks without shell processing?" in perlfaq8, and a Groups search
on "perl shell escape characters backticks" but I'm none the wiser;
pointers would be welcome.

Here's a test program:
-----------------------
#! /usr/bin/perl

use strict;
use warnings;

print "Enter filename:";
my $filename = <STDIN>;
chomp $filename;

my $ret = `ls -m $filename`;

print "Returned value:$ret\n";
-----------------------

The problem comes when the file name that is entered contains the "$"
character, which in my case it often does. In the test directory
there is a file called "test" and another called "$test". If I run
the program above and enter "test" the result is as expected, thus:

$ ./test
Enter filename:test
Returned value:test

But if I enter the name of the "$test" file, the variable $filename is
interpolated a second time, and since there is no "$test" variable
within the program it comes out as null, thus:

$ ./test
Enter filename:$test
Returned value:bashref.html, c, d, (etc.. the whole directory)

The unwanted interpolation happens in the shell that is called to
execute "ls". To avoid this, escape the dollar:

$filename =~ s'\$'\\$'g;

Anno
 
P

Paul Lalli

Here's a test program:
-----------------------
#! /usr/bin/perl

use strict;
use warnings;

print "Enter filename:";
my $filename = <STDIN>;
chomp $filename;

my $ret = `ls -m $filename`;

print "Returned value:$ret\n";
-----------------------

The problem comes when the file name that is entered contains the "$"
character, which in my case it often does. In the test directory
there is a file called "test" and another called "$test". If I run
the program above and enter "test" the result is as expected, thus:

$ ./test
Enter filename:test
Returned value:test

But if I enter the name of the "$test" file, the variable $filename is
interpolated a second time, and since there is no "$test" variable
within the program it comes out as null, thus:

$ ./test
Enter filename:$test
Returned value:bashref.html, c, d, (etc.. the whole directory)

Perl is not interpolating your variable a second time. The shell is
interpolating the variable $test. To verify, try entering the command in
your shell, without perl:
ls -m $test

You will see the same results, that is, an entire directory listing. In
the shell, you'd have to do:
ls -m \$test

which should give you the clue of how to solve this. You could search and
replace all 'special' characters to have a backslash precede them.
However, Perl gives you a way to do this:

$return = `ls -m \Q$filename\E`;
or
$filename = quotemeta $filename
$return = `ls -m $filename`;

Read about quotemeta in perldoc -f quotemeta, and \Q in perldoc perlop
under "Quote and Quote-like Operators"

Paul Lalli
 
A

Anno Siegel

Paul Lalli said:
Here's a test program:
-----------------------
#! /usr/bin/perl

use strict;
use warnings;

print "Enter filename:";
my $filename = <STDIN>;
chomp $filename;

my $ret = `ls -m $filename`;

print "Returned value:$ret\n";
-----------------------
[...]

However, Perl gives you a way to do this:

$return = `ls -m \Q$filename\E`;
or
$filename = quotemeta $filename
$return = `ls -m $filename`;

Read about quotemeta in perldoc -f quotemeta, and \Q in perldoc perlop
under "Quote and Quote-like Operators"

With quotemeta() you'll also escape "/", which is unwanted in file
names.

Anno
 
P

Paul Lalli

With quotemeta() you'll also escape "/", which is unwanted in file
names.

Unneeded, perhaps. But it has no ill effect, at least not with the shell
I'm using (I believe it's bash).

ls -al foo\/bar
has the same effect as
ls -al foo/bar

Are there shells out there that would throw an error at this?

Paul Lalli
 
B

Ben Morrow

Quoth "Henry Law said:
I think I've done my homework on this, including "How can I call
backticks without shell processing?" in perlfaq8, and a Groups search
on "perl shell escape characters backticks" but I'm none the wiser;
pointers would be welcome.

Here's a test program:
-----------------------
#! /usr/bin/perl

use strict;
use warnings;

print "Enter filename:";
my $filename = <STDIN>;
chomp $filename;

my $ret = `ls -m $filename`;

print "Returned value:$ret\n";
-----------------------

The problem comes when the file name that is entered contains the "$"
character, which in my case it often does. In the test directory
there is a file called "test" and another called "$test". If I run
the program above and enter "test" the result is as expected, thus:

$ ./test
Enter filename:test
Returned value:test

But if I enter the name of the "$test" file, the variable $filename is
interpolated a second time,

By the shell, not Perl.
and since there is no "$test" variable
within the program it comes out as null, thus:

You say you've read the faq answer; why didn't you try it?

my $ret = do {
open my $LS, '-|', ls => -m => $filename
or die "can't fork ls: $!";
local $/;
<$LS>;
};

As a separate issue, is possible to define some sort of DESTROY method
to call die automatically if the implicit close at end of scope fails?
It would make this sort of code both safe and clean.

Ben
 
H

Henry Law

You say you've read the faq answer; why didn't you try it?

I did; and of course it worked. But forking sounded like too much
heavy-duty workload for something so trivial, and which I have to do
many times within this particular program, so I ended up with my
current solution, which is the sub which excapes $'s and then executes
the command within backticks.

Henry Law <>< Manchester, England
 
B

Ben Morrow

Quoth "Henry Law said:
I did; and of course it worked. But forking sounded like too much
heavy-duty workload for something so trivial, and which I have to do
many times within this particular program, so I ended up with my
current solution, which is the sub which excapes $'s and then executes
the command within backticks.

Backticks perform a fork. If you want to execute an external command,
you *have* to fork.

In fact, the open '-|' answer is lighter, as backticks will fork twice:
once for the shell and again for ls. Avoiding the shell will remove a
completely extraneous process.

Always benchmark before deciding something is 'too heavy-duty'.

Ben
 
C

ctcgag

Henry Law said:
I did; and of course it worked. But forking sounded like too much
heavy-duty workload for something so trivial,

Do you have any idea how much work qx{} does behind the scenes?
and which I have to do
many times within this particular program, so I ended up with my
current solution, which is the sub which excapes $'s and then executes
the command within backticks.

Why not use glob? That is probably lighter (or at least as light) than any
shell-based method, and much easier to figure out the escaping for.

Xho
 
A

Anno Siegel

Paul Lalli said:
Unneeded, perhaps. But it has no ill effect, at least not with the shell
I'm using (I believe it's bash).

ls -al foo\/bar
has the same effect as
ls -al foo/bar

Are there shells out there that would throw an error at this?

I don't know, but the fact that the question even arises makes a case
against using quotemeta to quote strings for a shell. It may work,
but it's not the right tool for the task.

Anno
 
A

Anno Siegel

[...]
my $ret = do {
open my $LS, '-|', ls => -m => $filename
or die "can't fork ls: $!";
local $/;
<$LS>;
};

As a separate issue, is possible to define some sort of DESTROY method
to call die automatically if the implicit close at end of scope fails?
It would make this sort of code both safe and clean.

You can bless the globref that is the filehandle into a class, so yes.
DESTROY is called (immediately) before the handle is closed, so you'd
have to close it yourself to warn on errors.

There is, of course, no good way to give the user the name under which
the handle was originally opened. That could be handled if the blessing
happened at open() time, when the file name is known.

Another problem with this approach turns up when the filehandle already is
a blessed object.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,983
Messages
2,570,187
Members
46,747
Latest member
jojoBizaroo

Latest Threads

Top