3838 <https://colab.research.google.com/drive/1RRV1gHbGJs49qQB1q1d5tQEycVRtuhw6>`__
3939
4040"""
41+ import platform
42+ import re
4143import attr
4244import cloudpickle as cp
4345import inspect
4446import typing as ty
47+ import shlex
4548from pathlib import Path
4649import warnings
4750
@@ -447,7 +450,7 @@ def _command_pos_args(self, field):
447450 cmd_el_str = field .metadata ["formatter" ](** call_args_val )
448451 cmd_el_str = cmd_el_str .strip ().replace (" " , " " )
449452 if cmd_el_str != "" :
450- cmd_add += cmd_el_str . split ( " " )
453+ cmd_add += split_cmd ( cmd_el_str )
451454 elif field .type is bool :
452455 # if value is simply True the original argstr is used,
453456 # if False, nothing is added to the command
@@ -483,10 +486,8 @@ def _command_pos_args(self, field):
483486 cmd_el_str = f"{ argstr } { value } "
484487 else :
485488 cmd_el_str = ""
486- # removing double spacing
487- cmd_el_str = cmd_el_str .strip ().replace (" " , " " )
488489 if cmd_el_str :
489- cmd_add += cmd_el_str . split ( " " )
490+ cmd_add += split_cmd ( cmd_el_str )
490491 return pos , cmd_add
491492
492493 @property
@@ -501,9 +502,19 @@ def cmdline(self):
501502 if self .state :
502503 raise NotImplementedError
503504 if isinstance (self , ContainerTask ):
504- cmdline = " " . join ( self .container_args + self .command_args )
505+ command_args = self .container_args + self .command_args
505506 else :
506- cmdline = " " .join (self .command_args )
507+ command_args = self .command_args
508+ # Skip the executable, which can be a multi-part command, e.g. 'docker run'.
509+ cmdline = command_args [0 ]
510+ for arg in command_args [1 :]:
511+ # If there are spaces in the arg and it is not enclosed by matching
512+ # quotes, add quotes to escape the space. Not sure if this should
513+ # be expanded to include other special characters apart from spaces
514+ if " " in arg :
515+ cmdline += " '" + arg + "'"
516+ else :
517+ cmdline += " " + arg
507518 return cmdline
508519
509520 def _run_task (self ):
@@ -519,10 +530,12 @@ def _run_task(self):
519530 values = execute (args , strip = self .strip )
520531 self .output_ = dict (zip (keys , values ))
521532 if self .output_ ["return_code" ]:
533+ msg = f"Error running '{ self .name } ' task with { args } :"
522534 if self .output_ ["stderr" ]:
523- raise RuntimeError (self .output_ ["stderr" ])
524- else :
525- raise RuntimeError (self .output_ ["stdout" ])
535+ msg += "\n \n stderr:\n " + self .output_ ["stderr" ]
536+ if self .output_ ["stdout" ]:
537+ msg += "\n \n stdout:\n " + self .output_ ["stdout" ]
538+ raise RuntimeError (msg )
526539
527540
528541class ContainerTask (ShellCommandTask ):
@@ -825,3 +838,29 @@ def container_args(self):
825838 cargs .extend (["--pwd" , str (self .output_cpath )])
826839 cargs .append (self .inputs .image )
827840 return cargs
841+
842+
843+ def split_cmd (cmd : str ):
844+ """Splits a shell command line into separate arguments respecting quotes
845+
846+ Parameters
847+ ----------
848+ cmd : str
849+ Command line string or part thereof
850+
851+ Returns
852+ -------
853+ str
854+ the command line string split into process args
855+ """
856+ # Check whether running on posix or windows system
857+ on_posix = platform .system () != "Windows"
858+ args = shlex .split (cmd , posix = on_posix )
859+ cmd_args = []
860+ for arg in args :
861+ match = re .match ("('|\" )(.*)\\ 1$" , arg )
862+ if match :
863+ cmd_args .append (match .group (2 ))
864+ else :
865+ cmd_args .append (arg )
866+ return cmd_args
0 commit comments