среда, 12 марта 2014 г.

Небольшая особенность параметра args в subprocess.Popen

Как известно в subprocess.Popen можно передать args в виде строки и списка. В чем разница? Рассмотрим два небольших примера:

args = 'more "c:\\temp files\\1.txt"'
p1 = subprocess.Popen(args)

args = ['more''"c:\\temp files\\1.txt"']
p2 = subprocess.Popen(args)


По идее разница не большая, попробуем запустить
В первом случае отобразится содержимое файла 1.txt
hello world

Однако во втором случае консоле отобразится предупреждение
Не удается получить доступ к файлу C:\c:\temp

Что пошло не так во втором примере? Давайте более детально посмотрим на реализацию subprocess.Popen.

В конструкторе инициализация атрибутов, далее идет вызов функций _execute_child, делающей основную работу по запуску процессов на MS Windows, вот в ней то и кроется загвоздка. Переходим к реализации _execute_child и видим условие:

if not isinstance(args, types.StringTypes):
    args = list2cmdline(args)
    print('after list2cmdline', args)

То есть, если передаваемые аргументы args не типа string, то вызывается list2cmdline. как раз во втором случае Popen вызывается со списком аргументов.
Вот реализация метода:


def list2cmdline(seq):
    result = []
    needquote = False
    for arg in seq:
        bs_buf = []

        # Add a space to separate this argument from the others
        if result:
            result.append(' ')

        needquote = (" " in arg) or ("\t" in arg) or not arg
        if needquote:
            result.append('"')

        for c in arg:
            if c == '\\':
                # Don't know if we need to double yet.
                bs_buf.append(c)
            elif c == '"':
                # Double backslashes.
                result.append('\\' * len(bs_buf)*2)
                bs_buf = []
                result.append('\\"')
            else:
                # Normal char
                if bs_buf:
                    result.extend(bs_buf)
                    bs_buf = []
                result.append(c)

        # Add remaining backslashes, if any.
        if bs_buf:
            result.extend(bs_buf)

        if needquote:
            result.extend(bs_buf)
            result.append('"')
    return ''.join(result)

Код довольно простой. В цикле происходит перебор членов списка аргументов. Если аргумент содержит пробел или горизонтальную табуляцию или пустую строку, то функция автоматически добавит кавычки в начале и конце строки. Так если передать 'file directory', то на выходе получим '"file directory"', тоже самое произойдет если передать именованный параметр '-p file', то на выходе получим '"-p file"'.

При использовании subprocess.Popen в качестве аргументов нужно передать либо полностью сформировнную строку, либо если передается  список аргументов, в котором встречается директория с составным именем, то заранее использовать объединение по средством кавычек не нужно. 

Более подробно в документации http://docs.python.org/2/library/subprocess.html#converting-argument-sequence


Комментариев нет:

Отправить комментарий