Como escapar das chamadas os.system()?
Pergunta
Ao usar os.system() muitas vezes é necessário escapar de nomes de arquivos e outros argumentos passados como parâmetros para comandos.Como posso fazer isso?De preferência, algo que funcione em vários sistemas operacionais/shells, mas em particular para o 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)))
Editar: Aceitei a resposta simples de usar aspas, não sei por que não pensei nisso;Acho que é porque 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 que os.system() fornece, e a origem das strings não é gerada pelo usuário ou pelo menos inserida por um usuário confiável (eu).
Solução
Isto é o que eu uso:
def shellquote(s):
return "'" + s.replace("'", "'\\''") + "'"
O shell sempre aceitará um nome de arquivo entre aspas e removerá as aspas 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 de shell desagradável.
Atualizar:Se você estiver usando Python 3.3 ou posterior, use shlex.quote em vez de rolar o seu próprio.
Outras dicas
shlex.quote()
faz o que você quer desde python 3.
(Usar pipes.quote
para suportar python 2 e python 3)
Talvez você tenha um motivo específico para usar os.system()
.Mas se não, você provavelmente deveria estar usando o subprocess
módulo.Você pode especificar os tubos diretamente e evitar o uso do shell.
O seguinte é de 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]
Talvez subprocess.list2cmdline
é um tiro melhor?
Observe que pipes.quote está realmente quebrado no Python 2.5 e no Python 3.1 e não é seguro para uso - ele não lida com argumentos de comprimento zero.
>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1 arg3
Ver Problema Python 7476;foi corrigido no Python 2.6 e 3.2 e mais recentes.
Acredito que os.system apenas invoca qualquer shell de comando configurado para o usuário, então não acho que você possa fazer isso de maneira independente de plataforma.Meu shell de comando pode ser qualquer coisa, desde bash, emacs, ruby ou até mesmo quake3.Alguns desses programas não esperam o tipo de argumento que você está passando para eles e, mesmo que esperassem, não há garantia de que escaparão da mesma maneira.
Perceber:Esta é uma resposta para Python 2.7.x.
De acordo com fonte, pipes.quote()
é uma maneira de "Cite uma string de forma confiável como um único argumento para /bin/sh".(Embora seja obsoleto desde a versão 2.7 e finalmente exposto publicamente em Python 3.3 como o shlex.quote()
função.)
Sobre A outra mão, subprocess.list2cmdline()
é uma maneira de "Traduza uma sequência de argumentos em uma string de linha de comando, usando as mesmas regras do Tempo de execução do MS C".
Aqui estamos, a forma independente de plataforma de citar strings 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)
A função que uso é:
def quote_argument(argument):
return '"%s"' % (
argument
.replace('\\', '\\\\')
.replace('"', '\\"')
.replace('$', '\\$')
.replace('`', '\\`')
)
aquilo é:Eu sempre coloco o argumento entre aspas duplas e, em seguida, coloco aspas invertidas nos únicos caracteres especiais entre aspas duplas.
Se você usar o comando do sistema, eu tentaria colocar na lista de permissões o que entra na chamada os.system().Por exemplo..
clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))
O módulo subprocess é uma opção melhor, e eu recomendaria tentar evitar usar algo como os.system/subprocess sempre que possível.
A verdadeira resposta é:Não use os.system()
em primeiro lugar.Usar subprocess.call
em vez disso, forneça os argumentos sem escape.