comparison mercurial/dispatch.py @ 22158:bc2132dfc0a4

alias: expand "$@" as list of parameters quoted individually (BC) (issue4200) Before this patch, there was no way to pass in all the positional parameters as separate words down to another command. (1) $@ (without quotes) would expand to all the parameters separated by a space. This would work fine for arguments without spaces, but arguments with spaces in them would be split up by POSIX shells into separate words. (2) '$@' (in single quotes) would expand to all the parameters within a pair of single quotes. POSIX shells would then treat the entire list of arguments as one word. (3) "$@" (in double quotes) would expand similarly to (2). With this patch, we expand "$@" (in double quotes) as all positional parameters, quoted individually with util.shellquote, and separated by spaces. Under standard field-splitting conditions, POSIX shells will tokenize each argument into exactly one word. This is a backwards-incompatible change, but the old behavior was arguably a bug: Bourne-derived shells have expanded "$@" as a tokenized list of positional parameters for a very long time. I could find this behavior specified in IEEE Std 1003.1-2001, and this probably goes back to much further before that.
author Siddharth Agarwal <sid0@fb.com>
date Wed, 13 Aug 2014 23:21:52 -0700
parents af15de6775c7
children 645457f73aa6
comparison
equal deleted inserted replaced
22157:bd45d92883f9 22158:bc2132dfc0a4
329 givenargs = [x for i, x in enumerate(givenargs) 329 givenargs = [x for i, x in enumerate(givenargs)
330 if i not in nums] 330 if i not in nums]
331 args = shlex.split(cmd) 331 args = shlex.split(cmd)
332 return args + givenargs 332 return args + givenargs
333 333
334 def aliasinterpolate(name, args, cmd):
335 '''interpolate args into cmd for shell aliases
336
337 This also handles $0, $@ and "$@".
338 '''
339 # util.interpolate can't deal with "$@" (with quotes) because it's only
340 # built to match prefix + patterns.
341 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
342 replacemap['$0'] = name
343 replacemap['$$'] = '$'
344 replacemap['$@'] = ' '.join(args)
345 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
346 # parameters, separated out into words. Emulate the same behavior here by
347 # quoting the arguments individually. POSIX shells will then typically
348 # tokenize each argument into exactly one word.
349 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
350 # escape '\$' for regex
351 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
352 r = re.compile(regex)
353 return r.sub(lambda x: replacemap[x.group()], cmd)
354
334 class cmdalias(object): 355 class cmdalias(object):
335 def __init__(self, name, definition, cmdtable): 356 def __init__(self, name, definition, cmdtable):
336 self.name = self.cmd = name 357 self.name = self.cmd = name
337 self.cmdname = '' 358 self.cmdname = ''
338 self.definition = definition 359 self.definition = definition
374 ui.debug("No argument found for substitution " 395 ui.debug("No argument found for substitution "
375 "of %i variable in alias '%s' definition." 396 "of %i variable in alias '%s' definition."
376 % (int(m.groups()[0]), self.name)) 397 % (int(m.groups()[0]), self.name))
377 return '' 398 return ''
378 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:]) 399 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
379 replace = dict((str(i + 1), arg) for i, arg in enumerate(args)) 400 cmd = aliasinterpolate(self.name, args, cmd)
380 replace['0'] = self.name
381 replace['@'] = ' '.join(args)
382 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
383 return util.system(cmd, environ=env, out=ui.fout) 401 return util.system(cmd, environ=env, out=ui.fout)
384 self.fn = fn 402 self.fn = fn
385 return 403 return
386 404
387 try: 405 try: