Как избежать вызовов os.system()?

При использовании os.system() часто необходимо избегать имен файлов и других аргументов, передаваемых в качестве параметров командам. Как я могу это сделать? Желательно что-то, что будет работать на нескольких операционных системах / оболочках, но в особенности на bash.

В настоящее время я делаю следующее, но я уверен, что для этого должна быть библиотечная функция или, по крайней мере, более элегантный / надежный / эффективный вариант:

def sh_escape(s):
   return s.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")

os.system("cat %s | grep something | sort > %s" 
          % (sh_escape(in_filename), 
             sh_escape(out_filename)))

Изменить: я принял простой ответ с использованием кавычек, не знаю, почему я не думал об этом; Я думаю, потому что я пришел из Windows, где "и" ведут себя немного по-другому.

Что касается безопасности, я понимаю эту проблему, но в этом случае меня интересует быстрое и простое решение, которое предоставляет os.system(), а источник строк либо не генерируется пользователем, либо, по крайней мере, вводится пользователем. доверенный пользователь (я).

12 ответов

Решение

Это то, что я использую:

def shellquote(s):
    return "'" + s.replace("'", "'\\''") + "'"

Оболочка всегда будет принимать заключенное в кавычки имя файла и удалять окружающие кавычки перед передачей его рассматриваемой программе. В частности, это позволяет избежать проблем с именами файлов, которые содержат пробелы или любой другой тип неприятного метасимвола оболочки.

Обновление: если вы используете Python 3.3 или более позднюю версию, используйте shlex.quote вместо собственного.

shlex.quote() делает то, что вы хотите, начиная с Python 3.

(Использование pipes.quote поддерживать как Python 2, так и Python 3)

Возможно, у вас есть конкретная причина использования os.system(), Но если нет, то вы, вероятно, должны использовать subprocess модуль. Вы можете указать трубы напрямую и избегать использования оболочки.

Следующее от PEP324:

Replacing shell pipe line
-------------------------

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

Может быть subprocess.list2cmdline лучше выстрел?

Обратите внимание, что pipe.quote на самом деле не работает в Python 2.5 и Python 3.1 и небезопасен в использовании - он не обрабатывает аргументы нулевой длины.

>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1  arg3

См. Выпуск Python 7476; это было исправлено в Python 2.6 и 3.2 и новее.

Я считаю, что os.system просто вызывает любую командную оболочку, настроенную для пользователя, поэтому я не думаю, что вы можете сделать это независимо от платформы. Моя командная оболочка может быть любой из bash, emacs, ruby ​​или даже quake3. Некоторые из этих программ не ожидают того типа аргументов, которые вы им передаете, и даже если они это сделали, нет никакой гарантии, что они сбегут таким же образом.

Примечание: это ответ для Python 2.7.x.

По словам источника, pipes.quote() это способ "Надежно заключить строку в строку как один аргумент для / bin / sh". (Хотя он устарел с версии 2.7 и, наконец, публично представлен в Python 3.3 как shelx.quote() функция).

С другой стороны, subprocess.list2cmdline() способ "Перевести последовательность аргументов в строку командной строки, используя те же правила, что и во время выполнения MS C".

Здесь мы, независимый от платформы способ цитирования строк для командной строки.

import sys
mswindows = (sys.platform == "win32")

if mswindows:
    from subprocess import list2cmdline
    quote_args = list2cmdline
else:
    # POSIX
    from pipes import quote

    def quote_args(seq):
        return ' '.join(quote(arg) for arg in seq)

Использование:

# Quote a single argument
print quote_args(['my argument'])

# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)

Я использую функцию:

def quote_argument(argument):
    return '"%s"' % (
        argument
        .replace('\\', '\\\\')
        .replace('"', '\\"')
        .replace('$', '\\$')
        .replace('`', '\\`')
    )

то есть: я всегда заключаю аргумент в двойные кавычки, а затем заключаю в кавычки только специальные символы внутри двойных кавычек.

В оболочках UNIX, таких как Bash, вы можете использовать shlex.quoteв Python 3, чтобы экранировать специальные символы, которые может интерпретировать оболочка, такие как пробелы и *персонаж:

      import os
import shlex

os.system("rm " + shlex.quote(filename))

Однако этого недостаточно для целей безопасности! Вы по-прежнему должны быть осторожны, чтобы аргумент команды не был интерпретирован непреднамеренно. Например, что, если имя файла на самом деле является путем, например ../../etc/passwd? Бег os.system("rm " + shlex.quote(filename))может удалить /etc/passwdкогда вы только ожидали, что он удалит имена файлов, найденные в текущем каталоге! Проблема здесь не в том, что оболочка интерпретирует специальные символы, а в том, что аргумент имени файла интерпретируется не как простое имя файла, а фактически интерпретируется как путь.

Или что, если действительное имя файла начинается с тире, например, -f? Недостаточно просто передать экранированное имя файла, вам нужно отключить параметры, используя --или вам нужно пройти путь, который не начинается с тире, например ./-f. Проблема здесь не в том, что оболочка интерпретирует специальные символы, а в том, что rmкоманда интерпретирует аргумент как имя файла , путь или параметр, если он начинается с дефиса.

Вот более безопасная реализация:

      if os.sep in filename:
     raise Exception("Did not expect to find file path separator in file name")

os.system("rm -- " + shlex.quote(filename))

Я думаю, что эти ответы - плохая идея для экранирования аргументов командной строки в Windows. Основываясь на результатах: люди пытаются применить подход черного списка для фильтрации «плохих» персонажей, предполагая (и надеясь), что они получили их всех. Windows очень сложна, и в будущем могут быть найдены всевозможные символы, которые могут позволить злоумышленнику перехватить аргументы командной строки.

Я уже видел, что некоторые ответы пренебрегают фильтрацией основных метасимволов в Windows (например, точки с запятой). Подход, который я использую, намного проще:

  1. Составьте список разрешенных символов ASCII.
  2. Удалите все символы, которых нет в этом списке.
  3. Экранируйте косые черты и двойные кавычки.
  4. Заключите всю команду в двойные кавычки, чтобы аргумент команды нельзя было злонамеренно разорвать и использовать пробелы.

Основной пример:

      
def win_arg_escape(arg, allow_vars=0):
    allowed_list = """'"/\\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-. """
    if allow_vars:
        allowed_list += "~%$"

    # Filter out anything that isn't a
    # standard character.
    buf = ""
    for ch in arg:
        if ch in allowed_list:
            buf += ch

    # Escape all slashes.
    buf = buf.replace("\\", "\\\\")

    # Escape double quotes.
    buf = buf.replace('"', '""')

    # Surround entire arg with quotes.
    # This avoids spaces breaking a command.
    buf = '"%s"' % (buf)

    return buf

Функция имеет возможность включить использование переменных окружения и других переменных оболочки. Включение этого представляет больший риск, поэтому по умолчанию оно отключено.

Реальный ответ: не используйте os.system() на первом месте. использование subprocess.call вместо этого и предоставьте неэкранированные аргументы.

Если вы используете системную команду, я бы попытался внести в белый список то, что входит в вызов os.system(). Например..

clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))

Модуль подпроцесса - лучший вариант, и я бы порекомендовал стараться по возможности избегать использования чего-либо вроде os.system/subprocess.

Другие вопросы по тегам