Por que usar os métodos do módulo OS do Python em vez de executar comandos shell diretamente?


157

Estou tentando entender qual é a motivação por trás do uso das funções de biblioteca do Python para executar tarefas específicas do SO, como criar arquivos / diretórios, alterar atributos de arquivo etc. em vez de apenas executar esses comandos via os.system()ou subprocess.call()?

Por exemplo, por que eu gostaria de usar em os.chmodvez de usar os.system("chmod...")?

Entendo que é mais "pitônico" usar os métodos de biblioteca disponíveis do Python, tanto quanto possível, em vez de apenas executar comandos shell diretamente. Mas, existe alguma outra motivação por trás disso, do ponto de vista da funcionalidade?

Estou apenas falando sobre a execução de comandos simples de uma linha aqui. Quando precisamos de mais controle sobre a execução da tarefa, entendo que usarsubprocess módulo faz mais sentido, por exemplo.


6
Você basicamente acertou a unha na cabeça. As tarefas no nível do SO a que você se refere são comuns o suficiente para garantirem sua própria função, em vez de serem relegadas a serem chamadas pelo os.system.
deweyredman

7
BTW, você tentou cronometrar o tempo de execução - os.chmod vs. os.system ("chmod ...") . Eu arriscaria um palpite de que ele responderá parte de sua pergunta.
vulcão

61
Por que printquando você pode os.system("echo Hello world!")?
user253751

25
Pelo mesmo motivo, você deve usar os.pathpara manipular caminhos em vez de manipulá-los manualmente: ele funciona em todos os sistemas operacionais em que é executado.
Bakuriu

51
"Executar comandos do shell diretamente" é na verdade menos direto. O shell não é uma interface de baixo nível para o sistema e os.chmodnão chama o chmodprograma que o shell chamaria. Usando os.system('chmod ...')lança um shell de interpretar uma string para chamar outro executável para fazer uma chamada para o C chmodfunção, enquanto os.chmod(...)vai muito mais diretamente ao C chmod.
User2357112 suporta Monica

Respostas:


325
  1. É mais rápido , os.systeme subprocess.callcriar novos processos que é desnecessário para algo tão simples. De fato, os.systeme subprocess.callcom o shellargumento geralmente crie pelo menos dois novos processos: o primeiro sendo o shell e o segundo o comando que você está executando (se não for um shell embutido test).

  2. Alguns comandos são inúteis em um processo separado . Por exemplo, se você executar os.spawn("cd dir/"), ele mudará o diretório de trabalho atual do processo filho, mas não do processo Python. Você precisa usar os.chdirpara isso.

  3. Você não precisa se preocupar com caracteres especiais interpretados pelo shell. os.chmod(path, mode)funcionará independentemente do nome do arquivo, enquanto os.spawn("chmod 777 " + path)falhará horrivelmente se o nome do arquivo for algo parecido ; rm -rf ~. (Observe que você pode solucionar isso se usar subprocess.callsem o shellargumento.)

  4. Você não precisa se preocupar com nomes de arquivos que começam com um traço . os.chmod("--quiet", mode)alterará as permissões do arquivo nomeado --quiet, mas os.spawn("chmod 777 --quiet")falhará, como --quieté interpretado como um argumento. Isso é verdade mesmo para subprocess.call(["chmod", "777", "--quiet"]).

  5. Você tem menos preocupações entre plataformas e shell, pois a biblioteca padrão do Python deve lidar com isso para você. Seu sistema possui chmodcomando? Está instalado? Ele suporta os parâmetros que você espera? O osmódulo tentará ser o mais multiplataforma possível e documentará quando isso não for possível.

  6. Se o comando que você está executando tiver uma saída com a qual você se preocupa, é necessário analisá-lo, o que é mais complicado do que parece, pois você pode esquecer as maiúsculas (nomes de arquivos com espaços, guias e novas linhas), mesmo quando você não se importa com portabilidade.


38
Para adicionar ao ponto "multiplataforma", listar um diretório é "ls" no linux, "dir" no windows. Obter o conteúdo de um diretório é uma tarefa de baixo nível muito comum.
Cort Ammon

1
@CortAmmon: "Low-Level" é relativo, lsou diré bastante alto para certos tipos de desenvolvedores, exatamente como bashou cmdou kshou qualquer outro shell que você preferir.
Sebastian Mach

1
@phresnel: Eu nunca pensei nisso dessa maneira. Para mim, "a chamada direta para a API do kernel do seu sistema operacional" era de nível muito baixo. Estou assumindo que há uma perspectiva diferente sobre isso que está me iludindo porque estou (naturalmente) me aproximando dele com meus próprios preconceitos.
Cort Ammon

5
@CortAmmon: certo, e lsé de nível superior a esse, pois não é uma chamada direta à API do kernel do seu sistema operacional. É uma aplicação (pequena).
Steve Jessop

1
@SteveJessop. Eu chamei "obtendo o conteúdo de um diretório" de baixo nível. Eu não estou pensando lsou dirmas opendir()/readdir()(api linux) ou FindFirstFile()/FindNextFile()(windows api) ou File.listFiles(API Java) ou Directory.GetFiles()(C #). Tudo isso está intimamente ligado a uma chamada direta ao sistema operacional. Alguns podem ser tão simples quanto inserir um número em um registro e chamar int 13hpara ativar o modo kernel.
Cort Ammon

133

É mais seguro. Para lhe dar uma idéia, aqui está um exemplo de script

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

Se a entrada do usuário foi test; rm -rf ~ essa, excluiria o diretório inicial.

É por isso que é mais seguro usar a função incorporada.

Por isso, você deve usar o subprocesso em vez do sistema também.


26
Ou de outra maneira, o que é mais fácil de acertar, escrevendo programas em Python ou programas em Python que escrevem scripts de shell? :-)
Steve Jessop

3
@SteveJessop, um colega meu ficou surpreso que um pequeno script Python que eu o ajudei a escrever funcionou 20 (!) Vezes mais rápido do que o script do shell. Expliquei que o redirecionamento de saída pode parecer sexy - mas implica abrir e fechar arquivos em cada iteração. Mas um pouco de amor para fazer coisas da maneira mais difícil - :)
vulcão

1
@SteveJessop, essa é uma pergunta complicada - você não saberia até o tempo de execução! :)

60

Existem quatro casos fortes para preferir os métodos mais específicos do Python no osmódulo ao invés de usar os.systemou no subprocessmódulo ao executar um comando:

  • Redundância - gerar outro processo é redundante e desperdiça tempo e recursos.
  • Portabilidade - Muitos dos métodos do osmódulo estão disponíveis em várias plataformas, enquanto muitos comandos do shell são específicos do sistema operacional.
  • Entendendo os resultados - Gerar um processo para executar comandos arbitrários obriga a analisar os resultados da saída e entender se e por que um comando fez algo errado.
  • Segurança - Um processo pode potencialmente executar qualquer comando fornecido. Esse é um design fraco e pode ser evitado usando métodos específicos no osmódulo.

Redundância (consulte código redundante ):

Na verdade, você está executando um "intermediário" redundante no caminho para as eventuais chamadas do sistema ( chmodno seu exemplo). Esse intermediário é um novo processo ou sub-shell.

De os.system:

Execute o comando (uma string) em um subshell ...

E subprocessé apenas um módulo para gerar novos processos.

Você pode fazer o que precisa sem gerar esses processos.

Portabilidade (consulte portabilidade do código fonte ):

O osobjetivo do módulo é fornecer serviços genéricos de sistema operacional e sua descrição começa com:

Este módulo fornece uma maneira portátil de usar a funcionalidade dependente do sistema operacional.

Você pode usar os.listdirno Windows e no Unix. Tentar usar os.system/ subprocesspara esta funcionalidade forçará você a manter duas chamadas (para ls/ dir) e verificar em qual sistema operacional você está. Isto não é tão portátil e vai causar ainda mais frustração mais tarde (ver saída Handling ).

Compreendendo os resultados do comando:

Suponha que você queira listar os arquivos em um diretório.

Se você estiver usando os.system("ls")/ subprocess.call(['ls']), poderá recuperar a saída do processo, que é basicamente uma grande string com os nomes dos arquivos.

Como você pode distinguir um arquivo com um espaço no nome de dois arquivos?

E se você não tiver permissão para listar os arquivos?

Como você deve mapear os dados para objetos python?

Isso está fora da minha cabeça e, embora haja soluções para esses problemas - por que resolver novamente um problema que foi resolvido para você?

Este é um exemplo de seguir o princípio Não se repita (geralmente chamado de "SECO") por não repetir uma implementação que já existe e está disponível gratuitamente para você.

Segurança:

os.systeme subprocesssão poderosos. É bom quando você precisa desse poder, mas é perigoso quando não precisa. Quando você usa os.listdir, sabe que não pode fazer mais nada além de listar arquivos ou gerar um erro. Quando você usa os.systemou subprocessobtém o mesmo comportamento, pode acabar fazendo algo que não pretendia fazer.

Segurança de injeção (veja exemplos de injeção de casca ) :

Se você usa a entrada do usuário como um novo comando, basicamente dá a ele um shell. É muito parecido com a injeção de SQL, fornecendo um shell no banco de dados para o usuário.

Um exemplo seria um comando do formulário:

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

Isso pode ser facilmente explorado para executar qualquer código arbitrário usando a entrada: NASTY COMMAND;#para criar o eventual:

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

Existem muitos desses comandos que podem colocar seu sistema em risco.


3
Eu diria que 2. é o principal motivo.
jaredad7

23

Por uma razão simples - quando você chama uma função de shell, ela cria uma sub-shell que é destruída após a existência do seu comando; portanto, se você alterar o diretório em um shell - isso não afeta seu ambiente no Python.

Além disso, a criação de sub shell é demorada, portanto, o uso direto de comandos do SO afetará seu desempenho

EDITAR

Eu tive alguns testes de tempo em execução:

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

Função interna é executada mais de 10 vezes mais rápido

EDIT2

Pode haver casos em que invocar executável externo possa produzir melhores resultados do que pacotes Python - acabei de lembrar de um email enviado por um colega meu que o desempenho do gzip chamado através do subprocesso era muito maior do que o desempenho de um pacote Python que ele usava. Mas certamente não quando estamos falando de pacotes padrão de SO que emulam comandos padrão do SO


Por acaso isso é feito com o iPython? Não achou que você poderia usar funções especiais começando com %o intérprete normal.
IProgram

@aPyDeveloper, sim, era o iPython - no Ubuntu. "Mágico" % timeit é uma bênção - embora haja alguns casos - principalmente com a corda formatação - que não pode processar
vulcão

1
Ou você também pode criar um script python e digitar o time <path to script> terminal e ele informará o tempo real, o usuário e o processo gasto. Ou seja, se você não possui o iPython e tem acesso à linha de comando do Unix.
IProgram

1
@aPyDeveloper, não vejo nenhuma razão para trabalhar duro - quando tenho ipython na minha máquina
vulcão

Verdade! Eu disse que se você não tivesse o iPython. :)
iProgram

16

As chamadas de shell são específicas do SO, enquanto as funções do módulo OS do Python não são, na maioria dos casos. E evita gerar um subprocesso.


1
As funções do módulo Python também geram novos subprocessos para invocar um novo subshell.
Kodokok

7
@Koderok nonsense, funções do módulo são chamados em-processo
dwurf

3
@ Koderok: o módulo os usa as chamadas de sistema subjacentes que o comando shell usou, não usa os comandos shell. Isso significa que a chamada do sistema os geralmente é mais segura e rápida (sem análise de string, boo fork, sem exec, ao invés disso, é apenas uma chamada do kernel) do que os comandos do shell. Observe que, na maioria dos casos, a chamada de shell e a chamada de sistema geralmente têm nome semelhante ou mesmo, mas documentadas separadamente; a chamada do shell está na seção man 1 (a seção man padrão) enquanto a chamada do sistema de nome equivalente está na seção man 2 (por exemplo, man 2 chmod).
Lie Ryan

1
@ dwurf, LieRyan: Meu mal! Eu tinha uma noção errada, ao que parece. Obrigado!
Kodokok

11

É muito mais eficiente. O "shell" é apenas outro binário do SO que contém muitas chamadas de sistema. Por que incorrer na sobrecarga de criação de todo o processo de shell apenas para essa chamada de sistema única?

A situação é ainda pior quando você usa os.systemalgo que não é um shell embutido. Você inicia um processo de shell que, por sua vez, inicia um executável que, a dois processos de distância, faz a chamada do sistema. Pelo menos subprocessteria removido a necessidade de um processo intermediário de shell.

Não é específico para Python, isso. systemdé uma melhoria nos tempos de inicialização do Linux pelo mesmo motivo: ele faz o sistema necessário se autodenominar em vez de gerar milhares de conchas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.