Wie entkomme ich os.system () -Aufrufen?

123

Bei Verwendung von os.system () müssen häufig Dateinamen und andere Argumente maskiert werden, die als Parameter an Befehle übergeben werden. Wie kann ich das machen? Am besten etwas, das auf mehreren Betriebssystemen / Shells funktioniert, aber insbesondere für Bash.

Ich mache derzeit Folgendes, bin mir aber sicher, dass es dafür eine Bibliotheksfunktion oder zumindest eine elegantere / robustere / effizientere Option geben muss:

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)))

Bearbeiten: Ich habe die einfache Antwort der Verwendung von Anführungszeichen akzeptiert, weiß nicht, warum ich nicht daran gedacht habe; Ich denke, weil ich von Windows kam, wo 'und "sich ein wenig anders verhalten.

In Bezug auf die Sicherheit verstehe ich das Problem, aber in diesem Fall bin ich an einer schnellen und einfachen Lösung interessiert, die os.system () bietet, und die Quelle der Zeichenfolgen wird entweder nicht vom Benutzer generiert oder zumindest von a eingegeben vertrauenswürdiger Benutzer (ich).

Tom
quelle
1
Vorsicht vor dem Sicherheitsproblem! Zum Beispiel, wenn out_filename foo.txt ist; rm -rf / Der böswillige Benutzer kann weitere Befehle hinzufügen, die direkt von der Shell interpretiert werden.
Steve Gury
6
Dies ist auch ohne os.system nützlich, wenn der Unterprozess nicht einmal eine Option ist. zB Shell-Skripte generieren.
Eine ideale sh_escapeFunktion würde aus den ;Leerzeichen und entkommen und das Sicherheitsproblem beseitigen, indem einfach eine Datei mit dem Namen "so etwas" erstellt wird foo.txt\;\ rm\ -rf\ /.
Tom
In fast allen Fällen sollten Sie einen Unterprozess verwenden, nicht os.system. Wenn Sie os.system aufrufen, werden Sie nur nach einem Injektionsangriff gefragt.
Allyourcode

Antworten:

84

Das benutze ich:

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

Die Shell akzeptiert immer einen Dateinamen in Anführungszeichen und entfernt die umgebenden Anführungszeichen, bevor sie an das betreffende Programm übergeben wird. Dies vermeidet insbesondere Probleme mit Dateinamen, die Leerzeichen oder andere böse Shell-Metazeichen enthalten.

Update : Wenn Sie Python 3.3 oder höher verwenden, verwenden Sie shlex.quote, anstatt Ihre eigene zu rollen.

Greg Hewgill
quelle
7
@pixelbeat: Genau deshalb schließt er seine einfachen Anführungszeichen, fügt ein maskiertes einfaches Anführungszeichen hinzu und öffnet seine einfachen Anführungszeichen erneut.
lhunath
4
Obwohl dies kaum in der Verantwortung der Shellquote-Funktion liegt, ist es möglicherweise interessant festzustellen, dass dies immer noch fehlschlägt, wenn ein nicht zitierter Backslash unmittelbar vor dem Rückgabewert dieser Funktion angezeigt wird. Moral: Stellen Sie sicher, dass Sie dies in Code verwenden, dem Sie als sicher vertrauen können (z. B. als Teil fest codierter Befehle). Fügen Sie ihn nicht an andere nicht zitierte Benutzereingaben an.
lhunath
10
Beachten Sie, dass Sie wahrscheinlich stattdessen Jamies Vorschlag verwenden sollten, es sei denn, Sie benötigen unbedingt Shell-Funktionen.
lhunath
6
Ähnliches ist jetzt offiziell als shlex.quote verfügbar .
Janus Troelsen
3
Die in dieser Antwort bereitgestellte Funktion erledigt Shell-Zitate besser als shlexoder pipes. Diese Python - Module übernehmen fälschlicherweise , dass Sonderzeichen die einzige Sache sind , die notiert werden müssen, die Mittel , die Schlüsselwörter Shell (wie time, caseoder while) wird analysiert werden , wenn dieses Verhalten nicht zu erwarten ist. Aus diesem Grund würde ich empfehlen, in dieser Antwort die Routine mit einfachen Anführungszeichen zu verwenden, da sie nicht versucht, "klug" zu sein, also keine dummen Randfälle hat.
user3035772
157

shlex.quote() macht was du willst seit python 3.

(Verwenden Sie pipes.quotediese Option , um sowohl Python 2 als auch Python 3 zu unterstützen.)

Pixelbeat
quelle
Es gibt auch commands.mkarg. Es wird auch ein führender Bereich (außerhalb der Anführungszeichen) hinzugefügt, der wünschenswert oder nicht wünschenswert sein kann. Es ist interessant, wie unterschiedlich ihre Implementierungen voneinander sind und auch viel komplizierter als Greg Hewgills Antwort.
Laurence Gonsalves
3
Aus irgendeinem Grund pipes.quotewird in der Standardbibliotheksdokumentation für das Rohrmodul
Tag
1
Beide sind nicht dokumentiert; command.mkargwird in 3.x veraltet und entfernt, während pipes.quote erhalten bleibt.
Beni Cherniavsky-Paskin
9
Korrektur: offiziell dokumentiert wie shlex.quote()in 3.3, pipes.quote()aus Kompatibilitätsgründen beibehalten. [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin
7
pipes funktioniert NICHT unter Windows - fügt einfache Anführungszeichen anstelle von doppelten Anführungszeichen hinzu.
Nux
58

Vielleicht haben Sie einen bestimmten Grund für die Verwendung os.system(). Wenn nicht, sollten Sie das subprocessModul wahrscheinlich verwenden . Sie können die Pipes direkt angeben und die Verwendung der Shell vermeiden.

Folgendes stammt aus 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
quelle
6
subprocess(besonders bei check_callusw.) ist oft dramatisch überlegen, aber es gibt einige Fälle, in denen das Entweichen der Schale immer noch nützlich ist. Das wichtigste, auf das ich stoße, ist, wenn ich ssh-Fernbefehle aufrufen muss.
Craig Ringer
@CraigRinger, yup, SSH-Remoting hat mich hierher gebracht. : PI wünschte, ssh hätte hier etwas zu helfen.
Jürgen A. Erhard
@ JürgenA.Erhard Es scheint seltsam, dass es keine --execvp-remote-Option gibt (oder standardmäßig so funktioniert). Alles durch die Hülle zu tun scheint ungeschickt und riskant. OTOH, ssh ist voll von seltsamen Macken, oft Dinge, die unter dem Gesichtspunkt der "Sicherheit" gemacht werden und die dazu führen, dass die Leute viel unsicherere Problemumgehungen finden.
Craig Ringer
10

Vielleicht subprocess.list2cmdlineist ein besserer Schuss?

Gary Shi
quelle
Das sieht ziemlich gut aus. Interessanterweise ist es nicht dokumentiert ... ( mindestens in docs.python.org/library/subprocess.html )
Tom
4
Es entkommt nicht richtig \: subprocess.list2cmdline(["'",'',"\\",'"'])gibt' "" \ \"
Tino
Es
grep
Ist subprocess.list2cmdline () nur für Windows vorgesehen?
JS.
@JS Ja, list2cmdlineentspricht der Windows-Syntax cmd.exe ( siehe Funktion docstring im Python-Quellcode ). shlex.quoteentspricht der Unix-Bourne-Shell-Syntax, ist jedoch normalerweise nicht erforderlich, da Unix die direkte Übergabe von Argumenten gut unterstützt. Windows erfordert so ziemlich, dass Sie eine einzelne Zeichenfolge mit all Ihren Argumenten übergeben (daher ist ein ordnungsgemäßes Escapezeichen erforderlich).
Eestrada
7

Beachten Sie, dass pipes.quote in Python 2.5 und Python 3.1 tatsächlich fehlerhaft und nicht sicher zu verwenden ist. Es werden keine Argumente mit der Länge Null verarbeitet.

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

Siehe Python-Ausgabe 7476 ; Es wurde in Python 2.6 und 3.2 und höher behoben.

John Wiseman
quelle
4
Welche Version von Python verwenden Sie? Version 2.6 scheint die richtige Ausgabe zu liefern: mycommand arg1 '' arg3 (Das sind zwei einfache Anführungszeichen zusammen, obwohl die Schriftart auf Stack Overflow das schwer zu sagen macht!)
Brandon Rhodes
4

Hinweis : Dies ist eine Antwort für Python 2.7.x.

Laut der Quelle gibt pipes.quote()es eine Möglichkeit, " einen String zuverlässig als einzelnes Argument für / bin / sh zu zitieren ". (Obwohl es seit Version 2.7 veraltet ist und schließlich öffentlich in Python 3.3 als shlex.quote()Funktion verfügbar gemacht wurde .)

Auf der anderen Seite gibt subprocess.list2cmdline()es eine Möglichkeit, " eine Folge von Argumenten in eine Befehlszeilenzeichenfolge zu übersetzen, wobei dieselben Regeln wie für die MS C-Laufzeit verwendet werden ".

Hier sind wir, die plattformunabhängige Methode zum Zitieren von Zeichenfolgen für Befehlszeilen.

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)

Verwendung:

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

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

Ich glaube, dass os.system nur die für den Benutzer konfigurierte Befehlsshell aufruft, daher glaube ich nicht, dass Sie dies plattformunabhängig tun können. Meine Befehlsshell kann alles von Bash, Emacs, Ruby oder sogar Quake3 sein. Einige dieser Programme erwarten nicht die Art von Argumenten, die Sie an sie weitergeben, und selbst wenn dies der Fall ist, gibt es keine Garantie dafür, dass sie auf die gleiche Weise entkommen.

pauldoo
quelle
2
Es ist nicht unangemessen, eine größtenteils oder vollständig POSIX-kompatible Shell zu erwarten (zumindest überall außer unter Windows, und Sie wissen sowieso, welche "Shell" Sie dann haben). os.system verwendet $ SHELL nicht, zumindest nicht hier.
2

Die Funktion, die ich benutze, ist:

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

Das heißt: Ich füge das Argument immer in doppelte Anführungszeichen ein und zitiere dann die einzigen Sonderzeichen in doppelten Anführungszeichen.

tzot
quelle
Beachten Sie, dass Sie '\\ "', '\\ $' und '\`' verwenden sollten, da sonst die Flucht nicht stattfindet.
JanKanis
1
Darüber hinaus gibt es Probleme bei der Verwendung von doppelten Anführungszeichen in einigen (seltsamen) Gebietsschemas . Der vorgeschlagene Fix, auf pipes.quoteden @JohnWiseman hingewiesen hat, ist ebenfalls fehlerhaft. Greg Hewgills Antwort ist daher die zu verwendende. (Es ist auch die, die die Muscheln intern für die regulären Fälle verwenden.)
Mirabilos
-3

Wenn Sie den Systembefehl verwenden, würde ich versuchen, eine Whitelist für den Aufruf von os.system () zu erstellen. Zum Beispiel.

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

Das Unterprozessmodul ist eine bessere Option, und ich würde empfehlen, die Verwendung von os.system / subprocess nach Möglichkeit zu vermeiden.

dbr
quelle
-3

Die wirkliche Antwort lautet: Nicht os.system()in erster Linie verwenden. Verwenden Sie subprocess.callstattdessen und geben Sie die nicht entkoppelten Argumente an.

Scarabeetle
quelle
6
Die Frage enthält ein Beispiel, bei dem der Unterprozess einfach fehlschlägt. Wenn Sie einen Unterprozess verwenden können, sollten Sie dies sicher tun. Aber wenn Sie nicht können ... ist der Unterprozess nicht für alles eine Lösung . Oh, und Ihre Antwort beantwortet die Frage überhaupt nicht.
Jürgen A. Erhard
@ JürgenA.Erhard scheitert das Beispiel des OP nicht daran, dass er Rohrleitungen verwenden möchte? Sie sollten immer einen Unterprozess verwenden, da keine Shell verwendet wird. Dies ist ein etwas ungeschicktes Beispiel , aber Sie können Pipes in nativen Unterprozessen ausführen. Es gibt einige Pypi-Pakete, die versuchen, dies zu vereinfachen. Ich neige dazu, nur die Nachbearbeitung durchzuführen, die ich in Python so oft wie möglich benötige. Sie können jederzeit Ihre eigenen StringIO-Puffer erstellen und die Dinge mit Unterprozessen ziemlich vollständig steuern.
ThorSummoner