Зачем использовать методы модуля Python os вместо непосредственного выполнения команд оболочки?

157

Я пытаюсь понять, что является мотивацией использования библиотечных функций Python для выполнения специфических для ОС задач, таких как создание файлов / каталогов, изменение атрибутов файлов и т. Д., Вместо простого выполнения этих команд с помощью os.system()или subprocess.call()?

Например, почему я хотел бы использовать os.chmodвместо того, чтобы делать os.system("chmod...")?

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

Я говорю только о выполнении простых однострочных команд оболочки здесь. Когда нам нужно больше контроля над выполнением задачи, я понимаю, что с помощьюsubprocess модуля имеет больше смысла, например.

Koderok
источник
6
Вы в основном ударили ноготь по голове. Задания на уровне ОС, на которые вы ссылаетесь, достаточно распространены, так что они оправдывают свои собственные функции, а не просто вызывают их через os.system.
Девейредман
7
Кстати, вы пытались рассчитать время выполнения - os.chmod vs. os.system ("chmod ...") . Я рискнул бы предположить, что это ответит на часть вашего вопроса.
вулкан
61
Почему, printкогда ты мог os.system("echo Hello world!")?
user253751
25
По той же причине вы должны использовать os.pathдля обработки путей вместо ручной обработки их: он работает на каждой ОС, где он работает.
Бакуриу
51
«Выполнение команд оболочки напрямую» на самом деле менее прямое. Оболочка не является низкоуровневым интерфейсом системы и os.chmodне будет вызывать chmodпрограмму, которую будет делать оболочка. Использование os.system('chmod ...')запускает оболочку для интерпретации строки для вызова другого исполняемого файла , чтобы сделать вызов на C chmodфункцию, в то время как os.chmod(...)идет гораздо более непосредственно к C chmod.
user2357112 поддерживает Monica

Ответы:

325
  1. Это быстрее , os.systemи subprocess.callсоздавать новые процессы, которые не нужны для чего-то такого простого. Фактически, os.systemи subprocess.callс shellаргументом обычно создают как минимум два новых процесса: первый - это оболочка, а второй - команда, которую вы запускаете (если это не встроенная оболочка, как test).

  2. Некоторые команды бесполезны в отдельном процессе . Например, если вы запустите os.spawn("cd dir/"), он изменит текущий рабочий каталог дочернего процесса, но не процесса Python. Вы должны использовать os.chdirдля этого.

  3. Вам не нужно беспокоиться о специальных символах, интерпретируемых оболочкой. os.chmod(path, mode)будет работать независимо от того, какое имя файла, а os.spawn("chmod 777 " + path)ужасно потерпит неудачу, если имя файла будет чем-то вроде ; rm -rf ~. (Обратите внимание, что вы можете обойти это, если вы используете subprocess.callбез shellаргумента.)

  4. Вам не нужно беспокоиться об именах файлов, начинающихся с тире . os.chmod("--quiet", mode)изменит права доступа к названному файлу --quiet, но os.spawn("chmod 777 --quiet")потерпит неудачу, так как --quietинтерпретируется как аргумент. Это верно даже для subprocess.call(["chmod", "777", "--quiet"]).

  5. У вас меньше проблем с кроссплатформенностью и кросс-оболочкой, так как стандартная библиотека Python должна решить эту проблему для вас. У вашей системы есть chmodкоманда? Это установлено? Поддерживает ли он параметры, которые вы ожидаете поддерживать? osМодуль будет пытаться быть кросс-платформенным , как это возможно и документов , когда , что это не возможно.

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

Флимм
источник
38
Чтобы добавить к кроссплатформенной точке, список каталогов - "ls" в linux, "dir" в windows. Получение содержимого каталога является очень распространенной задачей низкого уровня.
Cort Ammon
1
@CortAmmon: «Низкоуровневый» является относительным lsили dirдовольно высокоуровневым по отношению к определенным типам разработчиков, так же, как bashи cmd/ kshили любая оболочка, которую вы предпочитаете.
Себастьян Мах
1
@phresnel: я никогда не думал об этом таким образом. Для меня «прямой вызов API ядра вашей ОС» был очень низким уровнем. Я предполагаю, что есть другая точка зрения на это, которая ускользает от меня, потому что я (естественно) подхожу к этому со своими собственными предубеждениями.
Cort Ammon
5
@CortAmmon: верно, и lsэто более высокий уровень, поскольку это не прямой вызов API ядра вашей ОС. Это (небольшое) приложение.
Стив Джессоп
1
@SteveJessop. Я назвал «получение содержимого каталога» низким уровнем. Я не думаю , lsили , dirно opendir()/readdir()(Linux API) или FindFirstFile()/FindNextFile()(Windows API) или File.listFiles(Java API) или Directory.GetFiles()(C #). Все они тесно связаны с прямым вызовом ОС. Некоторые могут быть такими же простыми, как вставка числа в регистр и вызов int 13hрежима ядра.
Корт Аммон
133

Это безопаснее. Чтобы дать вам представление, вот пример сценария

import os
file = raw_input("Please enter a file: ")
os.system("chmod 777 " + file)

Если вход от пользователя был test; rm -rf ~ таким, то удалял бы домашний каталог.

Вот почему безопаснее использовать встроенную функцию.

Следовательно, почему вы должны использовать подпроцесс вместо системы тоже.

iProgram
источник
26
Или другой способ взглянуть на это: что легче сделать правильно, писать программы на Python или программы на Python, которые пишут сценарии оболочки? :-)
Стив Джессоп
3
@SteveJessop, мой коллега, был поражен тем, что маленький скрипт на Python, который я ему помогал писать, работал в 20 (!) Раз быстрее сценария tan shell. Я объяснил, что перенаправление вывода может выглядеть сексуально - но это влечет за собой открытие и закрытие файла на каждой итерации. Но некоторые любят делать вещи трудным путем - :)
вулкан
1
@ SteveJessop, это вопрос с подвохом - вы не узнаете до времени выполнения! :)
60

Есть четыре веских аргумента в пользу предпочтения более специфических методов Python в osмодуле, чем при использовании os.systemили subprocessмодуле при выполнении команды:

  • Избыточность - порождение другого процесса является избыточным и тратит время и ресурсы.
  • Переносимость - многие из методов в osмодуле доступны на нескольких платформах, в то время как многие команды оболочки зависят от ОС.
  • Понимание результатов - порождение процесса для выполнения произвольных команд вынуждает вас анализировать результаты из вывода и понимать, если и почему команда сделала что-то не так.
  • Безопасность - процесс может потенциально выполнить любую команду, которую ему дают. Это слабый дизайн, и его можно избежать, используя специальные методы в osмодуле.

Избыточность (см. Избыточный код ):

Вы фактически выполняете избыточного «посредника» на пути к возможным системным вызовам (chmod в вашем примере). Этот средний человек - это новый процесс или суб-оболочка.

От os.system:

Выполнить команду (строку) в подоболочке ...

И subprocess это просто модуль для порождения новых процессов.

Вы можете делать то, что вам нужно, не вызывая эти процессы.

Переносимость (см. Переносимость исходного кода ):

Целью osмодуля является предоставление общих служб операционной системы, и его описание начинается с:

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

Вы можете использовать os.listdirкак на Windows, так и на Unix. Попытка использовать os.system/ subprocessдля этой функции заставит вас выполнить два вызова (для ls/ dir) и проверить, в какой операционной системе вы работаете. Это не так переносимо и позже приведет к еще большему разочарованию (см. Обработка вывода ).

Понимание результатов команды:

Предположим, вы хотите перечислить файлы в каталоге.

Если вы используете os.system("ls")/subprocess.call(['ls']) , вы можете получить только выходные данные процесса, которые в основном представляют собой большую строку с именами файлов.

Как вы можете отличить файл с пробелом в имени от двух файлов?

Что если у вас нет разрешения перечислять файлы?

Как вы должны отобразить данные на объекты Python?

Это только из головы, и, хотя есть решения этих проблем - зачем снова решать проблему, которая была решена для вас?

Это пример того , чтобы следовать не повторяйте себя принцип (часто обозначаемый как «DRY») по не повторять реализацию , которая уже существует и свободно доступны для вас.

Безопасность:

os.systemи subprocessмощные. Это хорошо, когда тебе нужна эта сила, но это опасно, когда тебе это не нужно. Когда вы используете os.listdir, вы знаете, что он не может делать ничего другого, кроме списка файлов или выдавать ошибку. Когда вы используете os.systemилиsubprocess для достижения того же поведения, вы можете в конечном итоге сделать то, что вы не хотели делать.

Безопасность впрыска (см. Примеры инъекций в оболочку ) :

Если вы используете ввод от пользователя в качестве новой команды, вы в основном дали ему оболочку. Это очень похоже на SQL-инъекцию, предоставляющую пользователю оболочку в БД.

Примером может служить команда вида:

# ... read some user input
os.system(user_input + " some continutation")

Это может быть легко использовано для запуска любого произвольного кода, используя input: NASTY COMMAND;#для создания возможного:

os.system("NASTY COMMAND; # some continuation")

Есть много таких команд, которые могут поставить вашу систему под угрозу.

Реут Шарабани
источник
3
Я бы сказал, 2. это главная причина.
jaredad7
23

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

Кроме того, создание суб-оболочки занимает много времени, поэтому использование команд ОС напрямую повлияет на вашу производительность.

РЕДАКТИРОВАТЬ

У меня было несколько временных тестов:

In [379]: %timeit os.chmod('Documents/recipes.txt', 0755)
10000 loops, best of 3: 215 us per loop

In [380]: %timeit os.system('chmod 0755 Documents/recipes.txt')
100 loops, best of 3: 2.47 ms per loop

In [382]: %timeit call(['chmod', '0755', 'Documents/recipes.txt'])
100 loops, best of 3: 2.93 ms per loop

Внутренняя функция работает более чем в 10 раз быстрее

EDIT2

Могут быть случаи, когда вызов внешнего исполняемого файла может дать лучшие результаты, чем пакеты Python - я только что вспомнил письмо, отправленное моим коллегой, в котором производительность gzip, вызванного через подпроцесс, была намного выше, чем производительность пакета Python, который он использовал. Но, конечно, не тогда, когда мы говорим о стандартных пакетах ОС, эмулирующих стандартные команды ОС

вулкан
источник
Случайно ли это сделано с iPython? Не думал, что вы могли бы использовать специальные функции, начиная с %обычного интерпретатора.
iProgram
@aPyDeveloper, да, это был iPython - на Ubuntu. «Волшебный» % timeit - это благословение - хотя есть некоторые случаи - в основном с форматированием строк - которые он не может обработать
вулкан
1
Или вы можете также сделать скрипт на Python, а затем набрать time <path to script> в терминале, и он скажет вам реальное время, затраченное пользователем и процессами. Это если у вас нет iPython и у вас есть доступ к командной строке Unix.
iProgram
1
@aPyDeveloper, я не вижу причин усердно работать - когда у меня на компьютере iPython
вулкан
Правда! Я сказал, если у вас не было iPython. :)
iProgram
16

Вызов оболочки зависит от ОС, тогда как в большинстве случаев функции модуля Python os - нет. И это позволяет избежать порождения подпроцесса.

JoshRomRock
источник
1
Функции модуля Python также порождают новые подпроцессы для вызова новой подоболочки.
Кодерок
7
@ Koderok ерунда, функции модуля вызываются в процессе
dwurf
3
@Koderok: модуль os использует системные вызовы, которые использовала команда оболочки, он не использует команды оболочки. Это означает, что системный вызов os обычно безопаснее и быстрее (без разбора строк, boo fork, без exec, вместо этого это просто вызов ядра), чем команды оболочки. Обратите внимание, что в большинстве случаев системные вызовы и системные вызовы часто имеют одинаковые или одинаковые имена, но они документированы отдельно; вызов оболочки находится в разделе man 1 (раздел man по умолчанию), а системный вызов с эквивалентным именем - в разделе man 2 (например, man 2 chmod).
Ли Райан
1
@ dwurf, LieRyan: плохо! Кажется, у меня было неправильное представление. Спасибо!
Кодерок
11

Это гораздо эффективнее. «Оболочка» - это просто еще один бинарный файл ОС, который содержит множество системных вызовов. Зачем тратить время на создание всего процесса оболочки только для одного системного вызова?

Ситуация еще хуже, когда вы используете os.systemчто-то, что не является встроенной оболочкой. Вы запускаете процесс оболочки, который, в свою очередь, запускает исполняемый файл, который затем (на расстоянии двух процессов) выполняет системный вызов. По крайней мереsubprocess , устранена необходимость в промежуточном процессе оболочки.

Это не относится к Python, это. systemdЭто такое улучшение времени запуска Linux по той же причине: оно делает необходимые системные вызовы сам, а не порождает тысячу оболочек.

MSalters
источник