Как избежать вызовов 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]
Обратите внимание, что 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 (например, точки с запятой). Подход, который я использую, намного проще:
- Составьте список разрешенных символов ASCII.
- Удалите все символы, которых нет в этом списке.
- Экранируйте косые черты и двойные кавычки.
- Заключите всю команду в двойные кавычки, чтобы аргумент команды нельзя было злонамеренно разорвать и использовать пробелы.
Основной пример:
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.