interprete - ¿Por qué usar los métodos del módulo os de Python en lugar de ejecutar comandos de shell directamente?
ejecutar.py desde consola python (6)
-
Es más rápido ,
os.system
ysubprocess.call
crean nuevos procesos que son innecesarios para algo tan simple. De hecho,os.system
ysubprocess.call
con el argumento deshell
generalmente crean al menos dos procesos nuevos: el primero es el shell y el segundo el comando que está ejecutando (si no es un shell incorporado) comotest
). -
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 usaros.chdir
para eso. -
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 queos.spawn("chmod 777 " + path)
fallará horriblemente si el nombre de archivo es algo así; rm -rf ~
; rm -rf ~
. (Tenga en cuenta que puede solucionar esto si usasubprocess.call
sin el argumento deshell
). -
No tiene que preocuparse por los nombres de archivo que comienzan con un guión .
os.chmod("--quiet", mode)
cambiará los permisos del archivo llamado--quiet
, peroos.spawn("chmod 777 --quiet")
fallará, ya que--quiet
se interpreta como un argumento. Esto es cierto incluso parasubprocess.call(["chmod", "777", "--quiet"])
. -
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 el comando
chmod
? ¿Está instalado? ¿Admite los parámetros que espera que admita? El móduloos
intentará ser lo más multiplataforma posible y documentará cuando eso no sea posible. -
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 archivo con espacios, pestañas y nuevas líneas en ellas), incluso cuando No me importa la portabilidad.
Estoy tratando de entender cuál es la motivación detrás del uso de las funciones de la 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
os.chmod
lugar de hacer
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 módulo de
subprocess
tiene más sentido, por ejemplo.
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 fue de
test; rm -rf ~
test; rm -rf ~
esto 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.
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 usas
os.system
para 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 el
subprocess
habría eliminado la necesidad de un proceso intermedio de shell.
No es específico de Python, esto.
systemd
es 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.
Existen cuatro casos sólidos para preferir los métodos más específicos de Python en el módulo
os
en lugar de usar
os.system
o el módulo de
subprocess
al ejecutar un comando:
- Redundancia : generar otro proceso es redundante y desperdicia tiempo y recursos.
-
Portabilidad
: muchos de los métodos del módulo
os
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 incorrecto y por qué .
-
Seguridad
: un proceso puede ejecutar potencialmente cualquier comando que se le dé.
Este es un diseño débil y puede evitarse utilizando métodos específicos en el módulo
os
.
Redundancia (ver código redundante ):
En realidad, está ejecutando un "intermediario" redundante en su camino a las llamadas eventuales del sistema (
chmod
en su ejemplo).
Este intermediario es un nuevo proceso o sub-shell.
Desde
os.system
:
Ejecute el comando (una cadena) en una subshell ...
Y el
subprocess
es 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 objetivo del módulo
os
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.listdir
en Windows y Unix.
Intentar usar
os.system
/
subprocess
para 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 causará aún más frustración más adelante (ver
Salida de manejo
).
Comprender los resultados del comando:
Suponga que desea enumerar los archivos en un directorio.
Si está usando
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 seguir el principio de No repetirse (a menudo denominado "SECO") al no repetir una implementación que ya existe y está disponible gratuitamente para usted.
La seguridad:
os.system
y
subprocess
son potentes.
Es bueno cuando necesitas este poder, pero es peligroso cuando no lo necesitas.
Cuando usa
os.listdir
,
sabe
que no puede hacer nada más que enumerar archivos o generar un error.
Cuando usa
os.system
o
subprocess
para 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.
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.
Por una simple razón: cuando se llama a una función de shell, crea un sub-shell que se destruye después de que exista su comando, 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