Perché usare i metodi del modulo os di Python invece di eseguire direttamente i comandi di shell?


157

Sto cercando di capire qual è la motivazione dietro l'utilizzo delle funzioni di libreria di Python per l'esecuzione di attività specifiche del sistema operativo come la creazione di file / directory, la modifica degli attributi di file, ecc. Invece di eseguire tali comandi tramite os.system()o subprocess.call()?

Ad esempio, perché dovrei voler usare os.chmodinvece di farlo os.system("chmod...")?

Capisco che è più "pythonic" usare il più possibile i metodi di libreria disponibili di Python invece di eseguire direttamente i comandi di shell. Ma c'è qualche altra motivazione dietro a ciò dal punto di vista della funzionalità?

Sto solo parlando di eseguire semplici comandi di shell a una riga qui. Quando abbiamo bisogno di un maggiore controllo sull'esecuzione dell'attività, capisco che l'uso del subprocessmodulo ha più senso, ad esempio.


6
Fondamentalmente hai colpito l'unghia sulla testa. Le attività a livello di sistema operativo a cui si fa riferimento sono abbastanza comuni da giustificare la propria funzione invece di essere relegate ad essere chiamate tramite os.system.
Deweyredman,

7
A proposito, hai provato a calcolare i tempi di esecuzione - os.chmod vs. os.system ("chmod ...") . Immagino che possa rispondere a una parte della tua domanda.
vulcano,

61
Perché avere printquando potresti os.system("echo Hello world!")?
user253751

25
Per lo stesso motivo è necessario utilizzare os.pathper gestire i percorsi anziché gestirli manualmente: funziona su tutti i sistemi operativi in ​​cui viene eseguito.
Bakuriu,

51
"L'esecuzione diretta dei comandi di shell" è in realtà meno diretta. La shell non è un'interfaccia di basso livello per il sistema e os.chmodnon chiamerà il chmodprogramma che la shell farebbe. Utilizzando os.system('chmod ...')lancia una shell di interpretare una stringa di chiamare un altro eseguibile per effettuare una chiamata al C chmodfunzione, mentre os.chmod(...)va molto più direttamente alla C chmod.
user2357112 supporta Monica il

Risposte:


325
  1. E ' più veloce , os.systeme subprocess.callcreare nuovi processi di cui non è necessaria per qualcosa di così semplice. In effetti, os.systeme subprocess.callcon l' shellargomento di solito creano almeno due nuovi processi: il primo è la shell e il secondo è il comando che stai eseguendo (se non è una shell incorporata come test).

  2. Alcuni comandi sono inutili in un processo separato . Ad esempio, se si esegue os.spawn("cd dir/"), cambierà la directory di lavoro corrente del processo figlio, ma non del processo Python. È necessario utilizzare os.chdirper questo.

  3. Non devi preoccuparti di caratteri speciali interpretati dalla shell. os.chmod(path, mode)funzionerà indipendentemente dal nome del file, mentre os.spawn("chmod 777 " + path)fallirà in modo orribile se il nome del file è simile ; rm -rf ~. (Nota che puoi aggirare questo problema se lo usi subprocess.callsenza l' shellargomento.)

  4. Non devi preoccuparti di nomi di file che iniziano con un trattino . os.chmod("--quiet", mode)cambierà le autorizzazioni del file indicato --quiet, ma os.spawn("chmod 777 --quiet")fallirà, come --quietviene interpretato come argomento. Questo è vero anche per subprocess.call(["chmod", "777", "--quiet"]).

  5. Hai meno preoccupazioni multipiattaforma e cross-shell, in quanto la libreria standard di Python dovrebbe occuparsene per te. Il tuo sistema ha un chmodcomando? È installato? Supporta i parametri che ti aspetti che supporti? Il osmodulo cercherà di essere il più multipiattaforma possibile e di documentare quando ciò non è possibile.

  6. Se il comando che stai eseguendo ha un output che ti interessa, devi analizzarlo, il che è più complicato di quanto sembri, poiché potresti dimenticare i casi angolari (nomi di file con spazi, tabulazioni e nuove righe), anche quando non importa della portabilità.


38
Per aggiungere al punto "multipiattaforma", elencare una directory è "ls" su linux, "dir" su windows. Ottenere il contenuto di una directory è un'attività di basso livello molto comune.
Cort Ammon,

1
@CortAmmon: "basso livello" è relativo, lso dirsono piuttosto alto livello per alcuni tipi di sviluppatori, proprio come basho cmdo ksho qualunque sborsare preferite sono.
Sebastian Mach,

1
@phresnel: non ci ho mai pensato in quel modo. Per me, "la chiamata diretta all'API del kernel del tuo sistema operativo" era di livello molto basso. Suppongo che ci sia una prospettiva diversa su ciò che mi sfugge perché mi sto (naturalmente) avvicinando con i miei pregiudizi.
Cort Ammon,

5
@CortAmmon: giusto, ed lsè di livello superiore, dato che non è una chiamata diretta all'API del kernel del tuo sistema operativo. È un'applicazione (piccola).
Steve Jessop,

1
@SteveJessop. Ho chiamato "ottenere il contenuto di una directory" di basso livello. Non sto pensando lso dirma opendir()/readdir()(linux api) o FindFirstFile()/FindNextFile()(windows api) o File.listFiles(java API) o Directory.GetFiles()(C #). Tutti questi sono strettamente legati a una chiamata diretta al sistema operativo. Alcuni possono essere semplici come inserire un numero in un registro e chiamare int 13hper attivare la modalità kernel.
Cort Ammon,

133

È più sicuro. Per darti un'idea ecco uno script di esempio

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

Se l'input dell'utente fosse test; rm -rf ~questo, eliminerebbe la home directory.

Ecco perché è più sicuro utilizzare la funzione integrata.

Ecco perché dovresti usare anche i sottoprocessi invece del sistema.


26
O un altro modo di vederlo, cosa è più facile da ottenere, scrivendo programmi Python o scrivendo programmi Python che scrivono script di shell? :-)
Steve Jessop,

3
@SteveJessop, un mio collega è rimasto sorpreso dal fatto che una piccola sceneggiatura in Python che l'ho aiutato a scrivere abbia funzionato 20 (!) Volte più velocemente della shell tan shell. Ho spiegato che il reindirizzamento dell'output può sembrare sexy, ma comporta l'apertura e la chiusura del file su ogni iterazione. Ma alcuni amano fare le cose nel modo più duro - :)
vulcano,

1
@SteveJessop, questa è una domanda trabocchetto - non lo sapresti fino al runtime! :)

60

Esistono quattro validi casi per preferire i metodi più specifici di Python nel osmodulo rispetto all'uso os.systemo al subprocessmodulo durante l'esecuzione di un comando:

  • Ridondanza : la generazione di un altro processo è ridondante e fa perdere tempo e risorse.
  • Portabilità - Molti dei metodi nel osmodulo sono disponibili in più piattaforme mentre molti comandi della shell sono specifici del sistema operativo.
  • Comprensione dei risultati : la generazione di un processo per eseguire comandi arbitrari ti costringe ad analizzare i risultati dall'output e a capire se e perché un comando ha fatto qualcosa di sbagliato.
  • Sicurezza : un processo può potenzialmente eseguire qualsiasi comando impartito. Questo è un design debole e può essere evitato usando metodi specifici nel osmodulo.

Ridondanza (vedi codice ridondante ):

Stai effettivamente eseguendo un "intermediario" ridondante sulla strada per le eventuali chiamate di sistema ( chmodnel tuo esempio). Questo uomo di mezzo è un nuovo processo o sotto-shell.

Da os.system:

Esegui il comando (una stringa) in una subshell ...

Ed subprocessè solo un modulo per generare nuovi processi.

Puoi fare ciò di cui hai bisogno senza generare questi processi.

Portabilità (vedi portabilità del codice sorgente ):

L' osobiettivo del modulo è fornire servizi generici del sistema operativo e la sua descrizione inizia con:

Questo modulo offre un modo portatile di utilizzare la funzionalità dipendente dal sistema operativo.

È possibile utilizzare os.listdirsia su Windows sia su Unix. Cercare di utilizzare os.system/ subprocessper questa funzionalità ti costringerà a mantenere due chiamate (per ls/ dir) e a controllare il sistema operativo in uso. Questo non è così portatile e sarà causare ancora di più la frustrazione in seguito (vedi uscita Handling ).

Comprensione dei risultati del comando:

Supponiamo di voler elencare i file in una directory.

Se stai usando os.system("ls")/ subprocess.call(['ls']), puoi solo recuperare l'output del processo, che è fondamentalmente una grande stringa con i nomi dei file.

Come puoi distinguere un file con uno spazio nel nome da due file?

Cosa succede se non si dispone dell'autorizzazione per elencare i file?

Come dovresti mappare i dati su oggetti Python?

Questi sono solo in cima alla mia testa, e mentre ci sono soluzioni a questi problemi - perché risolvere di nuovo un problema che è stato risolto per te?

Questo è un esempio del seguire il principio Don't Repeat Yourself (spesso definito come "DRY") non ripetendo un'implementazione che esiste già ed è liberamente disponibile per te.

Sicurezza:

os.systeme subprocesssono potenti. È buono quando hai bisogno di questo potere, ma è pericoloso quando non lo fai. Quando lo usi os.listdir, sai che non può fare nient'altro che elencare i file o generare un errore. Quando usi os.systemo subprocessper ottenere lo stesso comportamento puoi potenzialmente finire per fare qualcosa che non volevi fare.

Sicurezza dell'iniezione (vedi esempi di iniezione della shell ) :

Se usi l'input dell'utente come nuovo comando, in pratica gli hai dato una shell. Questo è molto simile all'iniezione SQL che fornisce all'utente una shell nel DB.

Un esempio potrebbe essere un comando del modulo:

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

Questo può essere facilmente sfruttato per eseguire qualsiasi codice arbitrario utilizzando l'input: NASTY COMMAND;#per creare l'eventuale:

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

Esistono molti di questi comandi che possono mettere a rischio il tuo sistema.


3
Direi 2. è il motivo principale.
jaredad7,

23

Per una semplice ragione - quando chiami una funzione di shell, crea una sotto-shell che viene distrutta dopo che il tuo comando esiste, quindi se cambi directory in una shell - non influenza il tuo ambiente in Python.

Inoltre, la creazione di una sotto-shell richiede molto tempo, quindi l'utilizzo diretto dei comandi del sistema operativo influirà sulle prestazioni

MODIFICARE

Ho eseguito alcuni test di cronometraggio:

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 funzione interna funziona più di 10 volte più velocemente

EDIT2

Ci possono essere casi in cui invocare un eseguibile esterno può produrre risultati migliori rispetto ai pacchetti Python - ho appena ricordato una mail inviata da un mio collega che le prestazioni di gzip chiamate tramite sottoprocesso erano molto più elevate delle prestazioni di un pacchetto Python che usava. Ma certamente non quando parliamo di pacchetti OS standard che emulano comandi OS standard


Per caso è stato fatto con iPython? Non pensavo che potessi usare funzioni speciali a partire %dall'uso dell'interprete normale.
iProgramma il

@aPyDeveloper, sì, era iPython - su Ubuntu. "Magical" % timeit è una benedizione - anche se ci sono alcuni casi - principalmente con la formattazione delle stringhe - che non può elaborare
vulcano

1
Oppure puoi anche creare uno script Python e quindi digitare il time <path to script> terminale e ti dirà il tempo reale, dell'utente e del processo impiegato. Cioè se non hai iPython e hai accesso alla riga di comando di Unix.
iProgramma

1
@aPyDeveloper, non vedo alcun motivo per lavorare sodo - quando ho iPython sulla mia macchina
vulcano,

Vero! Ho detto che se non avessi iPython. :)
iProgram

16

Le chiamate shell sono specifiche del sistema operativo mentre le funzioni del modulo os Python non lo sono, nella maggior parte dei casi. Ed evita di generare un sottoprocesso.


1
Le funzioni del modulo Python generano anche nuovi sottoprocessi per invocare una nuova subshell.
Koderok,

7
@Koderok senza senso, le funzioni del modulo sono chiamate in-process
dwurf

3
@Koderok: il modulo os utilizza le chiamate di sistema sottostanti utilizzate dal comando shell, non utilizza i comandi shell. Ciò significa che le chiamate di sistema os sono in genere più sicure e veloci (nessuna analisi delle stringhe, boo fork, no exec, invece è solo una chiamata del kernel) rispetto ai comandi della shell. Si noti che nella maggior parte dei casi, la chiamata shell e la chiamata di sistema hanno spesso un nome simile o uguale, ma sono documentate separatamente; la chiamata shell è nella sezione man 1 (la sezione man di default) mentre la chiamata di sistema con nome equivalente è nella sezione man 2 (es. man 2 chmod).
Sdraiati Ryan il

1
@ dwurf, LieRyan: Mio cattivo! Ho avuto un'idea sbagliata, a quanto pare. Grazie!
Koderok,

11

È molto più efficiente. La "shell" è solo un altro binario del sistema operativo che contiene molte chiamate di sistema. Perché sostenere il sovraccarico di creare l'intero processo della shell solo per quella singola chiamata di sistema?

La situazione è ancora peggiore quando si utilizza os.systemqualcosa che non è una shell integrata. Si avvia un processo di shell che a sua volta avvia un eseguibile che quindi (a due processi di distanza) effettua la chiamata di sistema. Almeno subprocessavrebbe rimosso la necessità di un processo intermedio di shell.

Non è specifico per Python, questo. systemdè un tale miglioramento dei tempi di avvio di Linux per lo stesso motivo: fa il sistema necessario a chiamare se stesso invece di generare un migliaio di shell.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.