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

124

При использовании 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 (), а источник строк либо не создается пользователем, либо, по крайней мере, вводится доверенный пользователь (я).

Том
источник
1
Остерегайтесь проблем с безопасностью! Например, если out_filename - foo.txt; rm -rf / Злоумышленник может добавить больше команд, напрямую интерпретируемых оболочкой.
Стив Гури
6
Это также полезно без os.system в ситуациях, когда подпроцесс даже не вариант; например, создание сценариев оболочки.
Идеальная sh_escapeфункция могла бы избежать ;пробелов и и устранить проблему безопасности, просто создав файл с именем вроде foo.txt\;\ rm\ -rf\ /.
Том
Практически во всех случаях следует использовать подпроцесс, а не os.system. Вызов os.system просто запрашивает инъекционную атаку.
allyourcode

Ответы:

85

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

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

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

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

Грег Хьюгилл
источник
7
@pixelbeat: именно поэтому он закрывает свои одинарные кавычки, добавляет экранированную буквальную одинарную кавычку, а затем снова открывает свои одинарные кавычки.
lhunath
4
Хотя вряд ли за это отвечает функция shellquote, было бы интересно отметить, что это все равно не сработает, если перед возвращаемым значением этой функции появится обратная косая черта без кавычек. Мораль: убедитесь, что вы используете это в коде, которому вы можете доверять как безопасный (например, как часть жестко запрограммированных команд) - не добавляйте его к другим некотируемым пользовательским вводам.
lhunath
10
Обратите внимание, что если вам абсолютно не нужны функции оболочки, вам, вероятно, следует использовать вместо этого предложение Джейми.
lhunath
6
Нечто подобное теперь официально доступно как shlex.quote .
Janus Troelsen
3
Функция, представленная в этом ответе, лучше справляется с цитированием оболочки, чем shlexили pipes. Эти модули python ошибочно предполагают, что специальные символы - это единственное, что необходимо заключить в кавычки, что означает, что ключевые слова оболочки (например time, caseили while) будут анализироваться, когда такое поведение не ожидается. По этой причине я бы рекомендовал использовать в этом ответе процедуру одиночных кавычек, потому что она не пытается быть «умной», поэтому не имеет этих глупых крайних случаев.
user3035772
157

shlex.quote() делает то, что вы хотите, так как python 3.

(Используйте pipes.quoteдля поддержки как python 2, так и python 3)

pixelbeat
источник
Также есть commands.mkarg. Он также добавляет начальный пробел (за пределами кавычек), который может быть или не быть желательным. Интересно, насколько их реализации сильно отличаются друг от друга, а также намного сложнее, чем ответ Грега Хьюгилла.
Лоуренс Гонсалвес,
3
По какой-то причине pipes.quoteне упоминается в документации стандартной библиотеки для модуля pipe
День
1
Оба не имеют документов; command.mkargустарел и удален в 3.x, в то время как pipe.quote остался.
Бени Чернявский-Паскин
9
Исправление: официально задокументировано, как shlex.quote()в 3.3, pipes.quote()сохранено для совместимости. [ bugs.python.org/issue9723]
Бени Чернявский-Паскин
7
pipe НЕ работает в Windows - добавляет одинарные кавычки вместо двойных кавычек.
Nux
58

Возможно, у вас есть особая причина для использования 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]
Джейми
источник
6
subprocess(особенно с и check_callт.д.) часто значительно лучше, но есть несколько случаев, когда экранирование оболочки все еще полезно. Основная проблема, с которой я сталкиваюсь, - это когда мне нужно вызывать удаленные команды ssh.
Крейг Рингер
@CraigRinger, да, удаленное взаимодействие ssh - вот что привело меня сюда. : Пи хотел бы, чтобы у ssh было чем-то здесь помочь.
Юрген А. Эрхард
@ JürgenA.Erhard Кажется странным, что у него нет опции --execvp-remote (или работает так по умолчанию). Делать все через оболочку кажется неуклюжим и рискованным. OTOH, ssh полон странных причуд, часто вещи делаются в узком понимании «безопасности», что заставляет людей придумывать более небезопасные обходные пути.
Крейг Рингер,
10

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

Гэри Ши
источник
Выглядит неплохо. Интересно, что это не задокументировано ... ( по крайней мере, в docs.python.org/library/subprocess.html )
Том
4
Он не сбегает должным образом \: subprocess.list2cmdline(["'",'',"\\",'"'])дает' "" \ \"
Тино
Он не экранирует символы расширения оболочки
grep
Subprocess.list2cmdline () предназначен только для Windows?
JS.
@JS Да, list2cmdlineсоответствует синтаксису Windows cmd.exe ( см. Строку документации функции в исходном коде Python ). shlex.quoteсоответствует синтаксису bourne shell Unix, однако обычно в этом нет необходимости, поскольку Unix имеет хорошую поддержку прямой передачи аргументов. Windows в значительной степени требует, чтобы вы передавали одну строку со всеми вашими аргументами (следовательно, необходимо правильное экранирование).
eestrada
7

Обратите внимание, что 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 и новее.

Джон Уайзман
источник
4
Какую версию Python вы используете? Версия 2.6, похоже, дает правильный результат: mycommand arg1 '' arg3 (это две одинарные кавычки вместе, хотя шрифт в Stack Overflow затрудняет определение этого!)
Брэндон Родс,
4

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

Согласно источнику , pipes.quote()это способ « надежно указать строку как единственный аргумент для / bin / sh ». (Хотя она устарела с версии 2.7 и, наконец, публично представлена ​​в Python 3.3 как shlex.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)
Rockallite
источник
3

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

pauldoo
источник
2
Есть основания ожидать, что оболочка будет в основном или полностью совместима с POSIX (по крайней мере, везде, кроме Windows, и в любом случае вы знаете, какая у вас «оболочка»). os.system не использует $ SHELL, по крайней мере, здесь.
2

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

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

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

tzot
источник
Обратите внимание, что вы должны использовать '\\ "', '\\ $' и '\`', иначе экранирование не произойдет.
ЯнКанис,
1
Кроме того, есть проблемы с использованием двойных кавычек в некоторых (странных) локали ; предлагаемые исправления, на pipes.quoteкоторые указал @JohnWiseman, также не работают. Таким образом, следует использовать ответ Грега Хьюджилла. (Это также тот, который оболочки используют для внутренних целей в обычных случаях.)
mirabilos
-3

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

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

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

DBR
источник
-3

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

Scarabeetle
источник
6
Вопрос содержит пример, когда подпроцесс не работает. Если вы можете использовать подпроцесс, вы должны обязательно. Но если вы не можете ... подпроцесс - это не решение для всего . О, и ваш ответ вообще не отвечает на вопрос.
Юрген А. Эрхард
@ JürgenA.Erhard, разве пример ОП не терпит неудачу, потому что он хочет использовать трубы оболочки? Вы всегда должны использовать подпроцесс, потому что он не использует оболочку. Это немного неуклюжий пример , но вы можете создавать каналы в собственных подпроцессах, есть несколько пакетов pypi, которые пытаются упростить это. Я стараюсь как можно больше выполнять нужную мне постобработку в Python. Вы всегда можете создать свои собственные буферы StringIO и полностью контролировать вещи с помощью подпроцессов.
ThorSummoner