¿Por qué utilizar los métodos del módulo os de Python en lugar de ejecutar comandos de shell directamente?

157

Estoy tratando de entender cuál es la motivación detrás del uso de las funciones de biblioteca de Python para ejecutar tareas específicas del sistema operativo, como crear archivos / directorios, cambiar los atributos de los archivos, etc., en lugar de simplemente ejecutar esos comandos a través de os.system()o subprocess.call().

Por ejemplo, ¿por qué querría usar en os.chmodlugar de hacerlo os.system("chmod...")?

Entiendo que es más "pitónico" usar los métodos de biblioteca disponibles de Python tanto como sea posible en lugar de simplemente ejecutar comandos de shell directamente. Pero, ¿hay alguna otra motivación detrás de hacer esto desde un punto de vista funcional?

Solo estoy hablando de ejecutar comandos simples de shell de una línea aquí. Cuando necesitamos más control sobre la ejecución de la tarea, entiendo que usar el subprocessmódulo tiene más sentido, por ejemplo.

Koderok
fuente
66
Básicamente golpeaste el clavo en la cabeza. Las tareas a nivel de sistema operativo a las que se refiere son lo suficientemente comunes como para garantizar su propia función en lugar de ser relegadas a ser llamadas a través del sistema os.system.
deweyredman
77
Por cierto, ¿trató de cronometrar el tiempo de ejecución: os.chmod vs os.system ("chmod ...") . Me arriesgaría a adivinar que responderá parte de su pregunta.
volcán
61
¿Por qué tener printcuando pudiste os.system("echo Hello world!")?
user253751
25
Por la misma razón que debe usar os.pathpara manejar rutas en lugar de manejarlas manualmente: funciona en todos los sistemas operativos donde se ejecuta.
Bakuriu
51
"Ejecutar comandos de shell directamente" es en realidad menos directo. El shell no es una interfaz de bajo nivel para el sistema, y os.chmodno va a llamar al chmodprograma como lo haría el shell. El uso de os.system('chmod ...')lanzamientos de una cáscara de interpretar una cadena para llamar a otro ejecutable para hacer una llamada a la C chmodfunción, mientras que os.chmod(...)va mucho más directamente a la carpeta C chmod.
user2357112 es compatible con Monica

Respuestas:

325
  1. Es más rápido , os.systemy subprocess.callcrear nuevos procesos que es innecesario para algo tan simple. De hecho, os.systemy subprocess.callcon el shellargumento usualmente se crean al menos dos procesos nuevos: el primero es el shell y el segundo el comando que estás ejecutando (si no es un shell incorporado test).

  2. Algunos comandos son inútiles en un proceso separado . Por ejemplo, si ejecuta os.spawn("cd dir/"), cambiará el directorio de trabajo actual del proceso secundario, pero no del proceso de Python. Necesitas usar os.chdirpara eso.

  3. No tiene que preocuparse por los caracteres especiales interpretados por el shell. os.chmod(path, mode)funcionará sin importar cuál sea el nombre de archivo, mientras os.spawn("chmod 777 " + path)que fallará horriblemente si el nombre de archivo es similar ; rm -rf ~. (Tenga en cuenta que puede solucionar esto si lo usa subprocess.callsin el shellargumento).

  4. No tiene que preocuparse por los nombres de archivo que comienzan con un guión . os.chmod("--quiet", mode)cambiará los permisos del archivo nombrado --quiet, pero os.spawn("chmod 777 --quiet")fallará, como --quietse interpreta como un argumento. Esto es cierto incluso para subprocess.call(["chmod", "777", "--quiet"]).

  5. Tiene menos preocupaciones entre plataformas y conchas cruzadas, ya que se supone que la biblioteca estándar de Python se ocupará de eso por usted. ¿Su sistema tiene chmodcomando? ¿Está instalado? ¿Admite los parámetros que espera que admita? El osmódulo intentará ser lo más multiplataforma posible y documentará cuando eso no sea posible.

  6. Si el comando que está ejecutando tiene un resultado que le interesa, debe analizarlo, lo que es más complicado de lo que parece, ya que puede olvidarse de las mayúsculas y minúsculas (nombres de archivos con espacios, pestañas y nuevas líneas), incluso cuando no me importa la portabilidad.

Flimm
fuente
38
Para agregar al punto "multiplataforma", la lista de un directorio es "ls" en Linux, "dir" en Windows. Obtener el contenido de un directorio es una tarea muy común de bajo nivel.
Cort Ammon
1
@CortAmmon: "de bajo nivel" es relativo, lso dirson bastante alto nivel para ciertos tipos de desarrolladores, al igual que basho cmdo ksho lo que sea shell son prefiere.
Sebastian Mach
1
@phresnel: Nunca lo pensé de esa manera. Para mí, "llamada directa a la API del núcleo de su sistema operativo" era de muy bajo nivel. Supongo que hay una perspectiva diferente sobre esto que me está eludiendo porque (naturalmente) lo estoy abordando con mis propios prejuicios.
Cort Ammon
55
@CortAmmon: correcto, y lstiene un nivel más alto que eso, ya que no es una llamada directa a la API del kernel de su sistema operativo. Es una aplicación (pequeña).
Steve Jessop
1
@SteveJessop. Llamé a "obtener el contenido de un directorio" de bajo nivel. No estoy pensando lso dirpero opendir()/readdir()(api de linux) o FindFirstFile()/FindNextFile()(api de windows) o File.listFiles(API de Java) o Directory.GetFiles()(C #). Todos estos están estrechamente vinculados a una llamada directa al sistema operativo. Algunos pueden ser tan simples como insertar un número en un registro y llamar int 13hpara activar el modo kernel.
Cort Ammon
133

Es mas seguro. Para darle una idea aquí hay un script de ejemplo

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

Si la entrada del usuario fuera test; rm -rf ~así, se eliminaría el directorio de inicio.

Es por eso que es más seguro usar la función integrada.

Por lo tanto, también debería utilizar el subproceso en lugar del sistema.

iProgram
fuente
26
O de otra manera de verlo, ¿qué es más fácil de corregir, escribiendo programas Python o escribiendo programas Python que escriben scripts de shell? :-)
Steve Jessop
3
@SteveJessop, un colega mío se sorprendió de que un pequeño script de Python que le ayudé a escribir funcionó 20 (!) Veces más rápido que el script de shell. Le expliqué que la redirección de salida puede parecer atractiva, pero implica abrir y cerrar el archivo en cada iteración. Pero a algunos les encanta hacer las cosas de la manera difícil - :)
volcán
1
@SteveJessop, esta es una pregunta capciosa: ¡no lo sabrías hasta el tiempo de ejecución! :)
60

Existen cuatro casos sólidos para preferir los métodos más específicos de Python en el osmódulo en lugar de usar os.systemo el subprocessmódulo al ejecutar un comando:

  • Redundancia : generar otro proceso es redundante y desperdicia tiempo y recursos.
  • Portabilidad : muchos de los métodos en el osmódulo están disponibles en varias plataformas, mientras que muchos comandos de shell son específicos de os.
  • Comprender los resultados : generar un proceso para ejecutar comandos arbitrarios te obliga a analizar los resultados de la salida y comprender si un comando ha hecho algo mal y por qué .
  • Seguridad : un proceso puede ejecutar potencialmente cualquier comando que se le dé. Este es un diseño débil y se puede evitar utilizando métodos específicos en el osmódulo.

Redundancia (ver código redundante ):

En realidad, está ejecutando un "intermediario" redundante en su camino a las llamadas eventuales del sistema ( chmoden su ejemplo). Este intermediario es un nuevo proceso o sub-shell.

De os.system:

Ejecute el comando (una cadena) en una subshell ...

Y subprocesses solo un módulo para generar nuevos procesos.

Puede hacer lo que necesita sin generar estos procesos.

Portabilidad (ver portabilidad del código fuente ):

El osobjetivo del módulo es proporcionar servicios genéricos del sistema operativo y su descripción comienza con:

Este módulo proporciona una forma portátil de utilizar la funcionalidad dependiente del sistema operativo.

Puede usar os.listdirtanto en Windows como en Unix. Intentar usar os.system/ subprocesspara esta funcionalidad te obligará a mantener dos llamadas (para ls/ dir) y verificar en qué sistema operativo estás. Esto no es tan portátil y va a causar frustración aún más adelante (véase Salida de gestión ).

Comprender los resultados del comando:

Suponga que desea enumerar los archivos en un directorio.

Si está utilizando os.system("ls")/ subprocess.call(['ls']), solo puede recuperar la salida del proceso, que es básicamente una cadena grande con los nombres de archivo.

¿Cómo puede distinguir un archivo con un espacio en su nombre de dos archivos?

¿Qué sucede si no tiene permiso para enumerar los archivos?

¿Cómo debe asignar los datos a los objetos de Python?

Estos solo están fuera de mi cabeza, y si bien hay soluciones a estos problemas, ¿por qué resolver de nuevo un problema que se resolvió para usted?

Este es un ejemplo de la siguiente No te repitas principio (A menudo aduana mencionados como "seco") por no repetir una implementación que ya existe y está disponible gratuitamente para usted.

La seguridad:

os.systemy subprocessson poderosos Es bueno cuando necesitas este poder, pero es peligroso cuando no lo necesitas. Cuando lo usa os.listdir, sabe que no puede hacer nada más que enumerar archivos o generar un error. Cuando usa os.systemo subprocesspara lograr el mismo comportamiento, puede terminar haciendo algo que no quiso hacer.

Seguridad de inyección (ver ejemplos de inyección de conchas ) :

Si usa la entrada del usuario como un nuevo comando, básicamente le ha dado un shell. Esto es muy parecido a la inyección SQL que proporciona un shell en la base de datos para el usuario.

Un ejemplo sería un comando de la forma:

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

Esto puede explotarse fácilmente para ejecutar cualquier código arbitrario utilizando la entrada: NASTY COMMAND;#para crear el eventual:

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

Hay muchos de estos comandos que pueden poner en riesgo su sistema.

Reut Sharabani
fuente
3
Yo diría que 2. es la razón principal.
jaredad7
23

Por una simple razón: cuando llama a una función de shell, crea un sub-shell que se destruye después de que su comando existe, por lo que si cambia el directorio en un shell, no afecta su entorno en Python.

Además, la creación de sub-shell lleva mucho tiempo, por lo que usar los comandos del sistema operativo directamente afectará su rendimiento

EDITAR

Tuve algunas pruebas de tiempo en ejecución:

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

La función interna se ejecuta más de 10 veces más rápido

EDIT2

Puede haber casos en los que invocar un ejecutable externo puede producir mejores resultados que los paquetes de Python: acabo de recordar un correo enviado por un colega mío que decía que el rendimiento de gzip llamado a través del subproceso fue mucho mayor que el rendimiento de un paquete de Python que utilizó. Pero ciertamente no cuando hablamos de paquetes estándar del sistema operativo que emulan comandos estándar del sistema operativo

volcán
fuente
¿Por casualidad se hace eso con iPython? No pensé que podría usar funciones especiales comenzando con el %uso del intérprete normal.
iProgram
@aPyDeveloper, sí, era iPython, en Ubuntu. "Mágico" % timeit es una bendición - aunque hay algunos casos - sobre todo con el formato de cadena - que no puede procesar
volcán
1
O también puede hacer una secuencia de comandos de Python y luego escribir time <path to script> terminal y le dirá el tiempo real, el usuario y el proceso que tomó. Eso es si no tiene iPython y tiene acceso a la línea de comando de Unix.
iProgram
1
@aPyDeveloper, no veo ninguna razón para trabajar duro - cuando tengo iPython en mi máquina
volcán
¡Cierto! Dije si no tenías iPython. :)
iProgram
16

Las llamadas de shell son específicas del sistema operativo, mientras que las funciones del módulo de Python os no lo son, en la mayoría de los casos. Y evita generar un subproceso.

JoshRomRock
fuente
1
Las funciones del módulo Python también generan nuevos subprocesos para invocar una nueva subshell.
Koderok
77
@Koderok sin sentido, las funciones del módulo se llaman en proceso
dwurf
3
@Koderok: el módulo os usa las llamadas subyacentes del sistema que usó el comando de shell, no usa los comandos de shell. Esto significa que las llamadas al sistema os son generalmente más seguras y rápidas (sin análisis de cadenas, boo fork, sin exec, en cambio es solo una llamada del kernel) que los comandos de shell. Tenga en cuenta que en la mayoría de los casos, la llamada de shell y la llamada del sistema a menudo tienen un nombre similar o el mismo, pero se documentan por separado; la llamada de shell está en la sección man 1 (la sección man predeterminada) mientras que la llamada al sistema con un nombre equivalente está en la sección man 2 (por ejemplo, man 2 chmod).
Mentira Ryan
1
@ dwurf, LieRyan: ¡Qué mal! Tenía una noción equivocada, al parecer. ¡Gracias!
Koderok
11

Es mucho más eficiente. El "shell" es solo otro binario del sistema operativo que contiene muchas llamadas al sistema. ¿Por qué incurrir en la sobrecarga de crear todo el proceso de shell solo para esa única llamada al sistema?

La situación es aún peor cuando se usa os.systempara algo que no es un shell incorporado. Inicia un proceso de shell que a su vez inicia un ejecutable que luego (a dos procesos de distancia) realiza la llamada al sistema. Al menos subprocesshabría eliminado la necesidad de un proceso intermediario de shell.

No es específico de Python, esto. systemdes una mejora para los tiempos de inicio de Linux por la misma razón: hace que el sistema necesario se llame a sí mismo en lugar de generar miles de shells.

MSalters
fuente