Como escapar de chamadas de os.system ()?

123

Ao usar os.system (), muitas vezes é necessário escapar nomes de arquivos e outros argumentos passados ​​como parâmetros para comandos. Como posso fazer isso? De preferência algo que funcionaria em vários sistemas operacionais / shells, mas em particular no bash.

Atualmente, estou fazendo o seguinte, mas tenho certeza de que deve haver uma função de biblioteca para isso, ou pelo menos uma opção mais elegante / robusta / eficiente:

def sh_escape(s):
   return s.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")

os.system("cat %s | grep something | sort > %s" 
          % (sh_escape(in_filename), 
             sh_escape(out_filename)))

Edit: Aceitei a resposta simples de usar aspas, não sei por que não pensei nisso; Eu acho que porque eu vim do Windows, onde 'e "se comportam de maneira um pouco diferente.

Em relação à segurança, entendo a preocupação, mas, neste caso, estou interessado em uma solução rápida e fácil fornecida pelo os.system (), e a origem das strings não é gerada pelo usuário ou, pelo menos, inserida por um usuário confiável (eu).

Tom
fonte
1
Cuidado com o problema de segurança! Por exemplo, se out_filename for foo.txt; rm -rf / O usuário mal-intencionado pode adicionar mais comandos diretamente interpretados pelo shell.
Steve Gury
6
Isso também é útil sem o os.system, em situações em que o subprocesso nem é uma opção; por exemplo, gerar scripts de shell.
Uma sh_escapefunção ideal escaparia dos ;espaços e e removeria o problema de segurança simplesmente criando um arquivo chamado algo parecido foo.txt\;\ rm\ -rf\ /.
Tom
Em quase todos os casos, você deve usar o subprocesso, não o os.system. Ligar para o os.system está apenas pedindo um ataque de injeção.
Allyourcode

Respostas:

84

Isto é o que eu uso:

def shellquote(s):
    return "'" + s.replace("'", "'\\''") + "'"

O shell sempre aceita um nome de arquivo citado e remove as aspas circundantes antes de passá-lo para o programa em questão. Notavelmente, isso evita problemas com nomes de arquivos que contêm espaços ou qualquer outro tipo de metacaractere desagradável do shell.

Atualização : se você estiver usando o Python 3.3 ou posterior, use shlex.quote em vez de usar o seu próprio.

Greg Hewgill
fonte
7
@pixelbeat: é exatamente por isso que ele fecha suas aspas simples, adiciona uma aspas simples literal de escape e reabre suas aspas simples novamente.
11119 lhunath
4
Embora isso dificilmente seja responsabilidade da função shellquote, pode ser interessante observar que isso ainda falhará se uma barra invertida não citada aparecer logo antes do valor de retorno dessa função. Moral: certifique-se de usar isso em código no qual possa confiar como seguro - (como parte de comandos codificados) - não o anexe a outras entradas não citadas do usuário.
11239 lhunath
10
Observe que, a menos que você precise absolutamente de recursos de shell, provavelmente deverá usar a sugestão de Jamie.
Lhunath
6
Algo semelhante a isso agora está oficialmente disponível como shlex.quote .
Janus Troelsen
3
A função fornecida nesta resposta faz um trabalho melhor de citação de shell do que shlexou pipes. Esses módulos python erroneamente supor que os caracteres especiais são a única coisa que precisa ser citado, o que significa que a Shell palavras-chave (como time, caseou while) será analisado quando esse comportamento não é esperado. Por esse motivo, eu recomendaria usar a rotina de aspas simples nesta resposta, porque ela não tenta ser "inteligente" e, portanto, não tem esses casos tolos.
precisa saber é o seguinte
157

shlex.quote() faz o que você quer desde o python 3.

(Use pipes.quotepara suportar python 2 e python 3)

pixelbeat
fonte
Há também commands.mkarg. Também adiciona um espaço inicial (fora das aspas) que pode ou não ser desejável. É interessante como suas implementações são bem diferentes umas das outras e também muito mais complicado do que a resposta de Greg Hewgill.
Laurence Gonsalves
3
Por alguma razão, pipes.quotenão é mencionado na documentação padrão da biblioteca para o módulo pipes
dia
1
Ambos não são documentados; command.mkargfoi descontinuado e removido no 3.x, enquanto o pipes.quote permaneceu.
Beni Cherniavsky-Paskin
9
Correção: oficialmente documentado como shlex.quote()no 3.3, pipes.quote()mantido por compatibilidade. [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin
7
pipes não funciona no Windows - adiciona aspas simples em vez de aspas duplas.
Nux
58

Talvez você tenha uma razão específica para usar os.system(). Mas se não, você provavelmente deveria estar usando o subprocessmódulo . Você pode especificar os tubos diretamente e evitar o uso da concha.

O seguinte é do PEP324 :

Replacing shell pipe line
-------------------------

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]
Jamie
fonte
6
subprocess(especialmente com check_calletc) geralmente é dramaticamente superior, mas há alguns casos em que o escape de shell ainda é útil. O principal que eu encontro é quando tenho que chamar comandos remotos ssh.
Craig Ringer
@ CraigRinger, sim, o remsh ssh foi o que me trouxe aqui. : PI desejo ssh tinha algo para ajudar aqui.
Jürgen A. Erhard
@ JürgenA.Erhard Parece estranho que não tenha uma opção --execvp-remote (ou funcione dessa maneira por padrão). Fazer tudo através da concha parece desajeitado e arriscado. OTOH, ssh é cheio de peculiaridades estranhas, geralmente feitas em uma visão restrita da "segurança" que leva as pessoas a encontrar soluções alternativas muito mais inseguras.
Craig Ringer
10

Talvez subprocess.list2cmdlineseja uma chance melhor?

Gary Shi
fonte
Isso parece muito bom. Interessante não é documentado ... (em docs.python.org/library/subprocess.html pelo menos)
Tom
4
Não corretamente escapar \: subprocess.list2cmdline(["'",'',"\\",'"'])' "" \ \"
Tino
Ele não escapa símbolos de expansão shell
grep
O subprocess.list2cmdline () é destinado apenas ao Windows?
JS.
@JS Sim, list2cmdlineestá de acordo com a sintaxe do cmd.exe do Windows ( consulte a função docstring no código fonte do Python ). shlex.quoteestá de acordo com a sintaxe do Unix bourne shell, no entanto, geralmente não é necessário, pois o Unix tem um bom suporte para transmitir argumentos diretamente. O Windows exige praticamente que você passe uma única sequência com todos os seus argumentos (portanto, a necessidade de escape apropriado).
eestrada
7

Observe que o pipes.quote é realmente quebrado no Python 2.5 e Python 3.1 e não é seguro de usar - ele não manipula argumentos de comprimento zero.

>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1  arg3

Veja Python edição 7476 ; foi corrigido no Python 2.6 e 3.2 e mais recente.

John Wiseman
fonte
4
Qual versão do Python você está usando? Versão 2.6 parece produzir a saída correta: mycommand arg1 '' arg3 (Essas são duas aspas simples juntos, embora a fonte no Stack Overflow torna tão difícil de dizer!)
Brandon Rhodes
4

Aviso : Esta é uma resposta para o Python 2.7.x.

Segundo a fonte , pipes.quote()é uma maneira de " Citar com segurança uma string como um único argumento para / bin / sh ". (Embora tenha sido descontinuado desde a versão 2.7 e finalmente exposto publicamente no Python 3.3 como a shlex.quote()função.)

Por outro lado , subprocess.list2cmdline()é uma maneira de " Traduzir uma sequência de argumentos em uma sequência de linhas de comando, usando as mesmas regras do tempo de execução do MS C ".

Aqui estamos, a maneira independente da plataforma de citar seqüências de caracteres para linhas de comando.

import sys
mswindows = (sys.platform == "win32")

if mswindows:
    from subprocess import list2cmdline
    quote_args = list2cmdline
else:
    # POSIX
    from pipes import quote

    def quote_args(seq):
        return ' '.join(quote(arg) for arg in seq)

Uso:

# Quote a single argument
print quote_args(['my argument'])

# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)
Rockallite
fonte
3

Acredito que o os.system invoque qualquer shell de comando configurado para o usuário, então não acho que você possa fazê-lo de maneira independente da plataforma. Meu shell de comando pode ser qualquer coisa, desde bash, emacs, ruby ​​ou até quake3. Alguns desses programas não esperam o tipo de argumento que você está passando para eles e, mesmo que o fizessem, não há garantia de que eles escapam da mesma maneira.

pauldoo
fonte
2
Não é irracional esperar um shell compatível com POSIX, na maior parte ou totalmente (pelo menos em qualquer lugar, exceto no Windows, e você sabe qual "shell" você tem então). o os.system não usa $ SHELL, pelo menos não aqui.
2

A função que eu uso é:

def quote_argument(argument):
    return '"%s"' % (
        argument
        .replace('\\', '\\\\')
        .replace('"', '\\"')
        .replace('$', '\\$')
        .replace('`', '\\`')
    )

ou seja: eu sempre coloco o argumento entre aspas duplas e depois aspiro com barra invertida os únicos caracteres especiais entre aspas duplas.

tzot
fonte
Observe que você deve usar '\\ "', '\\ $' e '\`', caso contrário, a fuga não acontecerá.
JanKanis
1
Além disso, há problemas com o uso de aspas duplas em alguns locais (estranhos) ; a correção sugerida pipes.quoteque o @JohnWiseman apontou também está quebrada. A resposta de Greg Hewgill é, portanto, a única a ser usada. (É também a uma das conchas usar internamente para os casos regulares.)
mirabilos
-3

Se você usar o comando system, eu tentaria incluir o que entra na chamada os.system () na lista de permissões. Por exemplo ..

clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))

O módulo de subprocesso é uma opção melhor e eu recomendaria tentar evitar usar algo como os.system / subprocess sempre que possível.

dbr
fonte
-3

A resposta real é: não use os.system()em primeiro lugar. Use em subprocess.callvez disso e forneça os argumentos sem escape.

Scarabeetle
fonte
6
A pergunta contém um exemplo em que o subprocesso apenas falha. Se você pode usar o subprocesso, deve, com certeza. Mas se você não pode ... o subprocesso não é uma solução para tudo . Ah, e sua resposta não responde à pergunta.
Jürgen A. Erhard
@ JürgenA.Erhard não mostra o exemplo do OP porque ele quer usar tubos de proteção? Você sempre deve usar o subprocesso porque ele não usa um shell. Este é um exemplo um pouco desajeitado , mas você pode executar pipes em subprocessos nativos; existem alguns pacotes pypi que tentam facilitar isso. Eu costumo fazer o pós-processamento necessário em python o máximo possível. Você sempre pode criar seus próprios buffers StringIO e controlar as coisas completamente com subprocessos.
ThorSummoner