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


person Tom    schedule 30.08.2008    source источник
comment
Осторожно, проблема безопасности! Например, если out_filename — это foo.txt; rm -rf / Злоумышленник может добавить дополнительные команды, интерпретируемые оболочкой напрямую.   -  person Steve Gury    schedule 30.08.2008
comment
Это также полезно без os.system, в ситуациях, когда подпроцесс даже не вариант; например создание сценариев оболочки.   -  person    schedule 04.10.2010
comment
Идеальная функция sh_escape избавится от ; и пробелов и устранит проблему безопасности, просто создав файл с именем вроде foo.txt\;\ rm\ -rf\ /.   -  person Tom    schedule 07.10.2010
comment
Почти во всех случаях следует использовать subprocess, а не os.system. Вызов os.system — это просто запрос на инъекционную атаку.   -  person allyourcode    schedule 27.01.2016


Ответы (8)


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

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

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

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

person Greg Hewgill    schedule 30.08.2008
comment
экранированные одинарные кавычки недопустимы в одинарных кавычках. - person pixelbeat; 11.05.2009
comment
@pixelbeat: именно поэтому он закрывает свои одинарные кавычки, добавляет экранированную буквальную одинарную кавычку, а затем снова открывает свои одинарные кавычки. - person lhunath; 11.05.2009
comment
Хотя вряд ли это входит в обязанности функции shellquote, может быть интересно отметить, что это все равно не удастся, если прямо перед возвращаемым значением этой функции появится обратная косая черта без кавычек. Мораль: убедитесь, что вы используете это в коде, которому вы можете доверять как безопасному (например, в части жестко закодированных команд) - не добавляйте его к другому пользовательскому вводу без кавычек. - person lhunath; 11.05.2009
comment
Обратите внимание, что если вам абсолютно не нужны функции оболочки, вам, вероятно, следует использовать вместо этого предложение Джейми. - person lhunath; 11.05.2009
comment
Нечто подобное теперь официально доступно как shlex.quote. . - person Janus Troelsen; 17.06.2012
comment
@lhunath совершенно неправ по обоим пунктам. Это идеальный и правильный способ избежать слова для оболочки POSIX (обратите внимание на формулировку «слово», структура команды зависит от пользователя, но это тривиально). Передача в виде списка не помогает, например. после команды ssh, так как вам нужно выйти из нескольких уровней, тогда… - person mirabilos; 27.02.2015
comment
Функция, представленная в этом ответе, лучше справляется с цитированием оболочки, чем shlex или pipes. Эти модули Python ошибочно предполагают, что специальные символы — это единственное, что нужно заключать в кавычки, а это означает, что ключевые слова оболочки (например, time, case или while) будут проанализированы, когда такое поведение не ожидается. По этой причине я бы рекомендовал использовать процедуру одинарных кавычек в этом ответе, потому что она не пытается быть умной, поэтому в ней нет этих глупых пограничных случаев. - person user3035772; 15.01.2016
comment
Что-то, что это решение не решает, это если в имя файла встроено «/». Оболочка будет блевать на это, потому что она не допускает этот символ в компонентах имени пути. - person Tom Barron; 16.02.2016
comment
Также присутствует в python 2.7 — docs.python.org/2.7/library/shlex.html< /а> - person Aditya; 19.12.2017
comment
Я считаю, что "'" + s.replace("'", "'\"'\"'") + "'" работает даже лучше, потому что он не требует, чтобы обратная косая черта интерпретировалась как особенная перед двойной кавычкой в ​​оболочке. - person Mikko Rantalainen; 17.04.2019

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

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

person pixelbeat    schedule 11.05.2009
comment
Также есть commands.mkarg. Он также добавляет начальный пробел (вне кавычек), который может быть или не быть желательным. Интересно, как их реализации сильно отличаются друг от друга, а также намного сложнее, чем ответ Грега Хьюгилла. - person Laurence Gonsalves; 04.10.2010
comment
По какой-то причине pipes.quote не упоминается в документации стандартной библиотеки для модуля pipe - person Day; 19.08.2011
comment
Оба недокументированы; command.mkarg устарел и удален в 3.x, а pipe.quote остался. - person Beni Cherniavsky-Paskin; 18.09.2011
comment
Исправление: официально задокументировано как shlex.quote() в 3.3 , pipes.quote() сохранено для совместимости. [bugs.python.org/issue9723] - person Beni Cherniavsky-Paskin; 18.09.2011
comment
pipe НЕ работает в Windows — добавляет одинарные кавычки вместо двойных кавычек. - person Nux; 30.05.2014
comment
shlex кажется довольно ограниченным и не делает то, что хочет Op. Например, в строке «hellothere» должны быть экранированы двойные кавычки, так как это специальный символ в sh. Но shlex.quote() не ускользает от него, что приводит к искаженному вызову оболочки. - person Cerin; 18.12.2020

Возможно, у вас есть конкретная причина для использования 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]
person Jamie    schedule 30.08.2008
comment
subprocess (особенно с check_call и т. д.) часто значительно лучше, но есть несколько случаев, когда экранирование оболочки все еще полезно. Основное, с чем я сталкиваюсь, - это когда мне приходится вызывать удаленные команды ssh. - person Craig Ringer; 23.05.2013
comment
@CraigRinger, да, удаленное взаимодействие по ssh привело меня сюда. :P Хотел бы я, чтобы ssh чем-нибудь помог. - person Jürgen A. Erhard; 27.05.2013
comment
@ JürgenA.Erhard Кажется странным, что у него нет параметра --execvp-remote (или он работает так по умолчанию). Делать все через оболочку кажется неуклюжим и рискованным. OTOH, ssh полон странных причуд, часто это делается с узким представлением о безопасности, что заставляет людей придумывать гораздо более небезопасные обходные пути. - person Craig Ringer; 28.05.2013

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

person Gary Shi    schedule 25.05.2012
comment
Это выглядит довольно хорошо. Интересно, что это не задокументировано... (в docs.python.org/library/subprocess.html по крайней мере) - person Tom; 05.06.2012
comment
Он неправильно экранирует \: subprocess.list2cmdline(["'",'',"\\",'"']) дает ' "" \ \" - person Tino; 28.10.2012
comment
Он не экранирует символы расширения оболочки - person grep; 08.01.2013
comment
Предназначен ли subprocess.list2cmdline() только для Windows? - person JS.; 25.02.2016
comment
@JS Да, list2cmdline соответствует синтаксису Windows cmd.exe (см. строку документации функции в исходном коде Python). shlex.quote соответствует синтаксису оболочки Unix bourne, однако обычно в этом нет необходимости, поскольку Unix имеет хорошую поддержку прямой передачи аргументов. Windows в значительной степени требует, чтобы вы передавали одну строку со всеми своими аргументами (поэтому необходимо правильное экранирование). - person eestrada; 25.09.2019

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

person John Wiseman    schedule 10.12.2009
comment
Какую версию Python вы используете? Версия 2.6, кажется, выдает правильный вывод: mycommand arg1 '' arg3 (это две одинарные кавычки вместе, хотя шрифт в Stack Overflow затрудняет определение!) - person Brandon Rhodes; 31.08.2010

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

person pauldoo    schedule 30.08.2008
comment
Вполне разумно ожидать, что оболочка в основном или полностью совместима с POSIX (по крайней мере, везде, кроме Windows, и вы все равно знаете, какая у вас оболочка). os.system не использует $SHELL, по крайней мере здесь. - person ; 04.10.2010

Примечание. Это ответ для 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)
person Rockallite    schedule 13.04.2015

Функция, которую я использую:

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

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

person tzot    schedule 03.10.2010
comment
Обратите внимание, что вы должны использовать '\\', '\\$' и '\`', иначе экранирование не произойдет. - person JanKanis; 08.07.2014
comment
Кроме того, существуют проблемы с использованием двойных кавычек в некоторых (странных) локалях; предлагаемое исправление использует pipes.quote, который, как указал @JohnWiseman, также не работает. Таким образом, следует использовать ответ Грега Хьюгилла. (Это также тот, который оболочки используют внутри для обычных случаев.) - person mirabilos; 27.02.2015