Commit 10210138 authored by Matthieu Muffato's avatar Matthieu Muffato
Browse files

Utils.pm method to deal with system() arguments

Join the arguments if they are given as an array-ref, and take care of the
quotes !
parent e6daa568
......@@ -62,6 +62,8 @@ package Bio::EnsEMBL::Hive::RunnableDB::SystemCmd;
use strict;
use warnings;
use Bio::EnsEMBL::Hive::Utils qw(join_command_args);
use Capture::Tiny ':all';
use base ('Bio::EnsEMBL::Hive::Process');
......@@ -145,23 +147,22 @@ sub run {
my $self = shift;
my $cmd = $self->param('cmd');
my $flat_cmd = ref($cmd) ? join(' ', map text_to_shell_lit, @$cmd) : $cmd;
# system() can only spawn 1 process. For multiple commands piped
# together or if redirections are used, we need a shell. The system()
# way is to merge everything into a single string
$cmd = $flat_cmd if ref($cmd) and (grep {$shell_characters{$_}} @$cmd);
my ($join_needed, $flat_cmd) = join_command_args($cmd);
# Let's use the array if possible, it saves us from running a shell
my @cmd_to_run = $join_needed ? $flat_cmd : (ref($cmd) ? @$cmd : $cmd);
if($self->debug()) {
warn qq{cmd = "$flat_cmd"\n};
use Data::Dumper;
warn "Command used: ", Dumper($cmd);
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
warn "Command given: ", Dumper($cmd), "\n";
warn "Command to run: ", Dumper(\@cmd_to_run), "\n";
}
$self->dbc and $self->dbc->disconnect_when_inactive(1); # release this connection for the duration of system() call
my $return_value;
my $stderr = tee_stderr {
system(ref($cmd) ? @$cmd : $cmd);
system(@cmd_to_run);
$return_value = $? >> 8;
};
$self->dbc and $self->dbc->disconnect_when_inactive(0); # allow the worker to keep the connection open again
......
......@@ -61,7 +61,7 @@ use Bio::EnsEMBL::Hive::DBSQL::SqlSchemaAdaptor;
#use Bio::EnsEMBL::Hive::DBSQL::DBConnection; # causes warnings that all exported functions have been redefined
use Exporter 'import';
our @EXPORT_OK = qw(stringify destringify dir_revhash parse_cmdline_options find_submodules load_file_or_module script_usage url2dbconn_hash go_figure_dbc report_versions throw dbc_to_cmd);
our @EXPORT_OK = qw(stringify destringify dir_revhash parse_cmdline_options find_submodules load_file_or_module script_usage url2dbconn_hash go_figure_dbc report_versions throw dbc_to_cmd join_command_args);
no warnings ('once'); # otherwise the next line complains about $Carp::Internal being used just once
$Carp::Internal{ (__PACKAGE__) }++;
......@@ -427,6 +427,42 @@ sub dbc_to_cmd {
return \@cmd;
}
=head2 join_command_args
Argument[0]: String or Arrayref of Strings
Description: Prepares the command to be executed by system(). It is needed if the
command is in fact composed of multiple commands.
Returns: Tuple (boolean,string). The boolean indicates whether it was needed to
join the arguments. The string is the new command-line string.
PS: Shamelessly adapted from http://www.perlmonks.org/?node_id=908096
=cut
my %shell_characters = map {$_ => 1} qw(< > |);
sub join_command_args {
my $args = shift;
return (0,$args) unless ref($args);
# system() can only spawn 1 process. For multiple commands piped
# together or if redirections are used, we need a shell to parse
# a joined string representing the command
my $join_needed = (grep {$shell_characters{$_}} @$args) ? 1 : 0;
my @new_args = ();
foreach my $a (@$args) {
if ($shell_characters{$a} or $a =~ /^[a-zA-Z0-9_\-]+\z/) {
push @new_args, $a;
} else {
# Escapes the single-quotes and protects the arguments
$a =~ s/'/'\\''/g;
push @new_args, "'$a'";
}
}
return ($join_needed,join(' ', @new_args));
}
1;
# Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
use warnings;
use Test::More;
use Data::Dumper;
BEGIN {
use_ok( 'Bio::EnsEMBL::Hive::Utils', 'join_command_args' );
}
#########################
# block 1: the command line is given as a string
{
is_deeply([join_command_args("ls")], [0,"ls"], "String: the executable name");
is_deeply([join_command_args("ls cpanfile")], [0,"ls cpanfile"], "String: the executable name and an argument");
is_deeply([join_command_args("ls | cat")], [0,"ls | cat"], "String: two executables piped");
}
# block 2: the command line is given as an arrayref
{
is_deeply([join_command_args(["ls"])], [0,"ls"], "Array with 1 element: the executable name");
is_deeply([join_command_args(["ls", "cpanfile"])], [0,"ls cpanfile"], "Array with 2 elements: the executable and an argument");
is_deeply([join_command_args(["ls", "file space"])], [0,"ls 'file space'"], "Array with 2 elements: the executable and an argument that contains a space");
}
# block 3: the command line is given as an arrayref and contains
# redirections / pipes
{
is_deeply([join_command_args(["ls", ">", "file space"])], [1, "ls > 'file space'"], "Array with a redirection");
is_deeply([join_command_args(["ls", "|", "cat"])], [1, "ls | cat"], "Array with a pipe");
is_deeply([join_command_args(["ls", "|", "cat", ">", "file space"])], [1, "ls | cat > 'file space'"], "Array with a pipe and a redirection");
}
done_testing();
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment