Reputation: 840
Yesterday I asked a similar question about escaping double quotes in env variables, although It didn't solve my problem (Probably because I didn't explain good enough) so I would like to specify more.
I'm trying to run a script (Which I know is written in Perl), although I have to use it as a black box because of permissions issue (so I don't know how the script works). Lets call this script script_A
.
I'm trying to run a basic command in Shell: script_A -arg "date time"
.
If I run from the command line, it's works fine, but If I try to use it from a bash script or perl scrip (for example using the system
operator), it will take only the first part of the string which was sent as an argument. In other words, it will fail with the following error: '"date' is not valid.
.
Example to specify a little bit more:
If I run from the command line (works fine):
> script_A -arg "date time"
If I run from (for example) a Perl script (fails):
my $args = $ENV{SOME_ENV}; # Assume that SOME_ENV has '-arg "date time"'
my $cmd = "script_A $args";
system($cmd");
I think that the problem comes from the environment variable, but I can't use the one quote while defining the env variable. For example, I can't use the following method:
setenv SOME_ENV '-arg "date time"'
Because it fails with the following error: '"date' is not valid."
.
Also, I tried to use the following method:
setenv SOME_ENV "-arg '"'date time'"'"
Although now the env variable will containe:
echo $SOME_ENV
> -arg 'date time' # should be -arg "date time"
Another note, using \"
fails on Shell (tried it).
Any suggestions on how to locate the reason for the error and how to solve it?
Upvotes: 2
Views: 240
Reputation: 66881
The $args
, obtained from %ENV
as you show, is a string.
The problem is in what happens to that string as it is manipulated before arguments are passed to the program, which needs to receive strings -arg
and date time
If the program is executed in a way that bypasses the shell, as your example is, then the whole -arg "date time"
is passed to it as its first argument. This is clearly wrong as the program expects -arg
and then another string for its value (date time
)
If the program were executed via the shell, what happens when there are shell metacharacters in the command line (not in your example), then the shell would break the string into words, except for the quoted part; this is how it works from the command line. That can be enforced with
system('/bin/tcsh', '-c', $cmd);
This is the most straightforward fix but I can't honestly recommend to involve the shell just for arguments parsing. Also, you are then in the game of layered quoting and escaping, what can get rather involved and tricky. For one, if things aren't right the shell may end up breaking the command into words -arg
, "date
, time"
How you set the environment variable works
> setenv SOME_ENV '-arg "date time"'
> perl -wE'say $ENV{SOME_ENV}' #--> -arg "date time" (so it works)
what I believe has always worked this way in [t]csh
.
Then, in a Perl script: parse this string into -arg
and date time
strings, and have the program is executed in a way that bypasses the shell (if shell isn't used by the command)
my @args = $ENV{SOME_ENV} =~ /(\S+)\s+"([^"]+)"/; #"
my @cmd = ('script_A', @args);
system(@cmd) == 0 or die "Error with system(@cmd): $?";
This assumes that SOME_ENV
's first word is always the option's name (-arg
) and that all the rest is always the option's value, under quotes. The regex extracts the first word, as consecutive non-space characters, and after spaces everything in quotes.† These are program's arguments.
In the system LIST
form the program that is the first element of the list is executed without using a shell, and the remaining elements are passed to it as arguments. Please see system for more on this, and also for basics of how to investigate failure by looking into $?
variable.
It is in principle advisable to run external commands without the shell. However, if your command needs the shell then make sure that the string is escaped just right to to preserve quotes.
Note that there are modules that make it easier to use external commands. A few, from simple to complex: IPC::System::Simple, Capture::Tiny, IPC::Run3, and IPC::Run.
I must note that that's an awkward environment variable; any way to ogranize things otherwise?
† To make this work for non-quoted arguments as well (-arg date
) make the quote optional
my @args = $ENV{SOME_ENV} =~ /(\S+)\s+"?([^"]+)/;
where I now left out the closing (unnecessary) quote for simplicity
Upvotes: 2