Як уникнути дзвінків 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. Виклик ОС.системи просто вимагає ін'єкційної атаки.
allyourcode

Відповіді:


85

Це те, що я використовую:

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

Оболонка завжди прийме назване ім'я файлу та видалить навколишні лапки, перш ніж передати його до відповідної програми. Зокрема, це дозволяє уникнути проблем з іменами файлів, які містять пробіли, або будь-який інший тип неприємних метахарактерів оболонки.

Оновлення : Якщо ви використовуєте Python 3.3 або новішої версії, використовуйте shlex.quote замість того, щоб прокрутити свій власний.


7
@pixelbeat: саме тому він закриває свої одиничні котирування, додає уникнуту буквальну єдину цитату, а потім знову відкриває свої єдині цитати.
lhunath

4
Хоча це навряд чи є обов'язком функції shellquote, можливо, буде цікаво відзначити, що це все-таки вийде з ладу, якщо невведений косий рядок з’явиться перед початковим значенням цієї функції. Мораль: переконайтеся, що ви використовуєте це в коді, якому ви можете довіряти як безпечному - (наприклад, частині твердо кодованих команд) - не додавати його до інших введених користувачем даних.
lhunath

10
Зауважте, що якщо вам абсолютно не потрібні функції оболонки, ви, ймовірно, замість цього використовуєте пропозицію Джеймі.
lhunath

6
Щось подібне до цього зараз офіційно доступно як shlex.quote .
Янус Троельсен

3
Функція, надана у цій відповіді, виконує кращу роботу цитування оболонок, ніж shlexабо pipes. Ці модулі python помилково припускають, що спеціальні символи - це єдине, що потрібно цитувати, а це означає, що ключові слова оболонки (наприклад time, caseабо while) будуть розібрані, коли така поведінка не очікується. З цієї причини я б рекомендував використовувати у цій відповіді рутину одноцитування, тому що вона не намагається бути "розумною", тому немає цих дурних крайових випадків.
користувач3035772

157

shlex.quote() робить те, що ви хочете з python 3.

(Використовуйте pipes.quoteдля підтримки як python 2, так і python 3)


Там також commands.mkarg. Він також додає провідний простір (поза цитатами), який може бути або не бути бажаним. Цікаво, наскільки їх реалізація сильно відрізняється одна від одної, а також набагато складніше, ніж відповідь Грега Х'югілла.
Лоранс Гонсалвес


1
Обидва є недокументованими; command.mkargзастаріле та видалено в 3.x, тоді як залишилися цитати.
Бені Чернявський-Паскін

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, yup, ssh видалення - це те, що мене привело сюди. : Я бажаю, щоб ssh тут щось допомогло.
Юрген А. Ерхард

@ JürgenA.Erhard Здається дивним, що у нього немає --execvp-віддаленої опції (або працювати таким чином за замовчуванням). Робити все через оболонку здається незграбно та ризиковано. ОТО, ssh повна дивних химерностей, часто це робиться у вузькому розумінні "безпеки", що змушує людей придумувати більш безпечні способи вирішення.
Крейг Рінгер

10

Може бути subprocess.list2cmdline, кращий постріл?


Це виглядає досить добре. Цікаво , це не документований ... (в docs.python.org/library/subprocess.html по крайней мере)
Том

4
Це не належним чином втече \: subprocess.list2cmdline(["'",'',"\\",'"'])дає' "" \ \"
Тіно

Це не уникає символів розширення оболонки
grep

Чи subprocess.list2cmdline () призначений лише для Windows?
JS.

@JS Так, list2cmdlineвідповідає синтаксису cmd.exe Windows ( див. Функцію docstring у вихідному коді Python ). shlex.quoteвідповідає синтаксису Bourne оболонки Unix, однак це зазвичай не потрібно, оскільки Unix має хорошу підтримку для прямого передачі аргументів. Windows в значній мірі вимагає, щоб ви пройшли одну рядок з усіма своїми аргументами (таким чином, необхідність у правильному уникненні).
eestrada

7

Зауважте, що лагер pipe.quo фактично порушений у 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)

3

Я вважаю, що os.system просто викликає будь-яку командну оболонку, налаштовану для користувача, тому я не думаю, що ви можете це зробити незалежно від платформи. Моєю командною оболонкою може бути що-небудь - від bash, emacs, ruby ​​або навіть землетрусу3. Деякі з цих програм не сподіваються на аргументи, які ви їм передаєте, і навіть якщо вони це зробили, немає жодної гарантії, що вони роблять свої втечі так само.


2
Нерозумно очікувати в основному або повністю сумісних з POSIX оболонок (принаймні скрізь, але з Windows, і ви все одно знаєте, що таке "оболонка"). os.system не використовує $ SHELL, принаймні не тут.

2

Я використовую функцію:

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

тобто: Я завжди додаю аргумент у подвійні лапки, а потім зворотну косу риску цитую лише символами, які є спеціальними всередині подвійних лапок.


Зауважте, що ви повинні використовувати '\\ "', '\\ $' та '\`', інакше втеча не відбудеться.
JanKanis

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))

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


-3

Справжня відповідь: Не використовуйте os.system()в першу чергу. Використовуйте subprocess.callнатомість і надайте аргументи без нагляду.


6
Питання містить приклад, коли підпроцес просто не працює. Якщо ви можете використовувати підпроцес, ви повинні впевнено. Але якщо ви не можете ... підпроцес не є рішенням для всього . О, і ваша відповідь зовсім не відповідає на питання.
Юрген А. Ерхард

@ JürgenA.Erhard не вдається прикладом ОП, оскільки він хоче використовувати корпусні труби? Ви завжди повинні використовувати підпроцес, оскільки він не використовує оболонку. Це трохи незграбний приклад , але ви можете робити труби в нативних підпроцесах, є кілька пакетів pypi, які намагаються зробити це простіше. Я схильний просто максимально виконувати потрібну мені після цього обробку в python. Ви завжди можете створювати власні буфери StringIO і керувати речами повністю повністю з підпроцесами.
ThorSummoner
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.