Jak uniknąć wywołań os.system ()?

124

Podczas korzystania z os.system () często konieczne jest uniknięcie nazw plików i innych argumentów przekazywanych jako parametry do poleceń. W jaki sposób mogę to zrobić? Najlepiej coś, co działałoby na wielu systemach operacyjnych / powłokach, ale w szczególności na bash.

Obecnie wykonuję następujące czynności, ale jestem pewien, że musi to być funkcja biblioteczna lub przynajmniej bardziej elegancka / solidna / wydajna opcja:

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

Edycja: zaakceptowałem prostą odpowiedź, używając cytatów, nie wiem, dlaczego o tym nie pomyślałem; Chyba dlatego, że pochodzę z systemu Windows, gdzie „i” zachowują się trochę inaczej.

Jeśli chodzi o bezpieczeństwo, rozumiem obawy, ale w tym przypadku interesuje mnie szybkie i łatwe rozwiązanie, które zapewnia os.system (), a źródło ciągów nie jest generowane przez użytkownika lub przynajmniej wprowadzane przez zaufany użytkownik (ja).

Tomek
źródło
1
Uważaj na kwestię bezpieczeństwa! Na przykład, jeśli out_filename to foo.txt; rm -rf / Szkodliwy użytkownik może dodać więcej poleceń bezpośrednio interpretowanych przez powłokę.
Steve Gury
6
Jest to również przydatne bez os.system, w sytuacjach, gdy podproces nie wchodzi w grę; np. generowanie skryptów powłoki.
Idealna sh_escapefunkcja wymknęłaby ;spacje i i usunęłaby problem bezpieczeństwa, po prostu tworząc plik o nazwie coś podobnego foo.txt\;\ rm\ -rf\ /.
Tom
W prawie wszystkich przypadkach powinieneś używać podprocesu, a nie os.system. Wywołanie os.system po prostu prosi o atak typu injection.
allyourcode

Odpowiedzi:

85

Oto, czego używam:

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

Powłoka zawsze akceptuje nazwę pliku w cudzysłowie i usuwa otaczające ją cudzysłowy przed przekazaniem jej do danego programu. Warto zauważyć, że pozwala to uniknąć problemów z nazwami plików, które zawierają spacje lub inne nieprzyjemne metaznaky powłoki.

Aktualizacja : Jeśli korzystasz z Pythona 3.3 lub nowszego, użyj shlex.quote zamiast tworzyć własne.

Greg Hewgill
źródło
7
@pixelbeat: właśnie dlatego zamyka swoje pojedyncze cudzysłowy, dodaje dosłowny pojedynczy cudzysłów, a następnie ponownie otwiera pojedyncze cudzysłowy.
lhunath
4
Chociaż nie jest to zadaniem funkcji shellquote, warto zauważyć, że to się nie powiedzie, jeśli niecytowany ukośnik odwrotny pojawi się tuż przed wartością zwracaną przez tę funkcję. Morale: upewnij się, że używasz tego w kodzie, któremu możesz zaufać jako bezpiecznego - (na przykład jako część zakodowanych poleceń) - nie dołączaj go do innych niecytowanych danych wejściowych użytkownika.
lhunath
10
Zauważ, że jeśli absolutnie nie potrzebujesz funkcji powłoki, prawdopodobnie powinieneś zamiast tego skorzystać z sugestii Jamiego.
lhunath
6
Coś podobnego do tego jest teraz oficjalnie dostępne jako shlex.quote .
Janus Troelsen
3
Funkcja podana w tej odpowiedzi lepiej radzi sobie z cytowaniem powłoki niż shlexlub pipes. Te moduły Pythona błędnie zakładają, że znaki specjalne są jedyną rzeczą, która musi być cytowany, co oznacza, że Shell słów kluczowych (jak time, caselub while) będzie analizowany gdy nie oczekuje się, że zachowanie. Z tego powodu zalecałbym użycie procedury pojedynczego cudzysłowu w tej odpowiedzi, ponieważ nie stara się być „sprytna”, więc nie ma tych głupich skrajnych przypadków.
user3035772
157

shlex.quote() robi, co chcesz, od Pythona 3.

(Służy pipes.quotedo obsługi języka Python 2 i Python 3)

pixelbeat
źródło
Jest też commands.mkarg. Dodaje również wiodącą spację (poza cudzysłowami), która może być pożądana lub nie. Ciekawe, jak ich implementacje różnią się od siebie, a także znacznie bardziej skomplikowane niż odpowiedź Grega Hewgilla.
Laurence Gonsalves
3
Z jakiegoś powodu pipes.quotenie ma o tym wzmianki w standardowej dokumentacji biblioteki dla modułu potoków
Dzień
1
Obie są nieudokumentowane; command.mkargjest przestarzały i usunięty w 3.x, podczas gdy pipe.quote pozostał.
Beni Cherniavsky-Paskin
9
Korekta: oficjalnie udokumentowana jak shlex.quote()w 3.3, pipes.quote()zachowana dla zapewnienia zgodności. [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin
7
potoki NIE działają w systemie Windows - dodaje pojedyncze cudzysłowy zamiast podwójnych cudzysłowów.
Nux
58

Być może masz konkretny powód, dla którego używasz os.system(). Ale jeśli nie, prawdopodobnie powinieneś używać subprocessmodułu . Możesz określić rury bezpośrednio i uniknąć używania powłoki.

Poniższy tekst pochodzi z 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
źródło
6
subprocess(szczególnie z check_callitp.) jest często znacznie lepszy, ale jest kilka przypadków, w których ucieczka pociskiem jest nadal przydatna. Główną rzeczą, z którą się spotykam, jest wywołanie zdalnych poleceń ssh.
Craig Ringer
@CraigRinger, tak, ssh zdalne jest tym, co mnie tu sprowadziło. : PI chciałbym, żeby ssh miał tu coś do pomocy.
Jürgen A. Erhard
@ JürgenA.Erhard Wydaje się dziwne, że nie ma opcji --execvp-remote (lub domyślnie działa w ten sposób). Robienie wszystkiego przez muszlę wydaje się niezdarne i ryzykowne. OTOH, ssh jest pełen dziwnych dziwactw, często wykonywanych z wąskiego punktu widzenia „bezpieczeństwa”, co powoduje, że ludzie wymyślają bardziej niebezpieczne obejścia.
Craig Ringer
10

Może subprocess.list2cmdlinejest lepszy strzał?

Gary Shi
źródło
Wygląda całkiem nieźle. Interesujące, że nie jest udokumentowane ... ( przynajmniej w docs.python.org/library/subprocess.html )
Tom
4
Nie ucieka właściwie \: subprocess.list2cmdline(["'",'',"\\",'"'])daje' "" \ \"
Tino
Nie ucieka przed symbolami rozszerzenia powłoki
grep,
Czy subprocess.list2cmdline () jest przeznaczony tylko dla systemu Windows?
JS.
@JS Tak, list2cmdlinejest zgodny ze składnią cmd.exe systemu Windows ( zobacz opis funkcji w kodzie źródłowym Pythona ). shlex.quotejest zgodny ze składnią powłoki bourne w systemie Unix, jednak zwykle nie jest to konieczne, ponieważ Unix ma dobrą obsługę bezpośredniego przekazywania argumentów. Windows prawie wymaga, abyś przekazał pojedynczy łańcuch ze wszystkimi argumentami (stąd potrzeba odpowiedniego znaku ucieczki).
eestrada
7

Zauważ, że pipe.quote jest faktycznie uszkodzony w Pythonie 2.5 i Pythonie 3.1 i nie jest bezpieczny w użyciu - nie obsługuje argumentów o zerowej długości.

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

Zobacz wydanie Pythona 7476 ; został naprawiony w Pythonie 2.6 i 3.2 i nowszych.

John Wiseman
źródło
4
Jakiej wersji Pythona używasz? Wydaje się, że wersja 2.6 daje prawidłowe dane wyjściowe: mycommand arg1 '' arg3 (Są to dwa pojedyncze cudzysłowy razem, chociaż czcionka w Stack Overflow sprawia, że ​​trudno to stwierdzić!)
Brandon Rhodes
4

Uwaga : to jest odpowiedź dla Pythona 2.7.x.

Według źródła , pipes.quote()jest to sposób na " Rzetelne cytowanie łańcucha jako pojedynczego argumentu dla / bin / sh ". (Chociaż jest przestarzały od wersji 2.7 i ostatecznie ujawniony publicznie w Pythonie 3.3 jakoshlex.quote() funkcja).

Na drugiej strony , subprocess.list2cmdline()jest to sposób na „ Translate sekwencję argumentów na ciąg wiersza poleceń, przy użyciu tych samych zasad jak w czasie wykonywania MS C ”.

Oto jesteśmy, niezależny od platformy sposób cytowania ciągów znaków w wierszach poleceń.

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)

Stosowanie:

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

# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)
Rockallite
źródło
3

Uważam, że os.system po prostu wywołuje dowolną powłokę poleceń skonfigurowaną dla użytkownika, więc nie sądzę, aby można było to zrobić w sposób niezależny od platformy. Moją powłoką poleceń może być wszystko, od bash, emacs, ruby, a nawet quake3. Niektóre z tych programów nie oczekują argumentów, które im przekazujesz, a nawet jeśli to zrobiły, nie ma gwarancji, że uciekną w ten sam sposób.

pauldoo
źródło
2
Nie jest nierozsądne oczekiwać powłoki w większości lub w pełni zgodnej z POSIX (przynajmniej wszędzie oprócz Windows, a wiesz, jaką masz wtedy powłokę). os.system nie używa $ SHELL, przynajmniej nie tutaj.
2

Funkcja, której używam, to:

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

to znaczy: zawsze umieszczam argument w cudzysłowach, a jedyne znaki specjalne w cudzysłowach umieszczam w odwrotnym ukośniku.

tzot
źródło
Zauważ, że powinieneś użyć '\\ "', '\\ $' i '\`', w przeciwnym razie ucieczka nie nastąpi.
JanKanis,
1
Ponadto występują problemy z używaniem podwójnych cudzysłowów w niektórych (dziwnych) lokalizacjach ; sugerowana poprawka, pipes.quotektórą wskazał @JohnWiseman, również jest zepsuta. Zatem odpowiedź Grega Hewgilla jest tą, której należy użyć. (Jest to również ten, którego muszle używają wewnętrznie w zwykłych przypadkach.)
mirabilos,
-3

Jeśli użyjesz polecenia systemowego, spróbuję umieścić na białej liście to, co wchodzi do wywołania os.system () .. Na przykład ..

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

Moduł podprocesu jest lepszą opcją i zalecałbym unikanie używania czegoś takiego jak os.system / subprocess, gdy tylko jest to możliwe.

dbr
źródło
-3

Prawdziwa odpowiedź brzmi: nie używaj os.system()w pierwszej kolejności. Użyj subprocess.callzamiast tego i podaj argumenty bez zmiany znaczenia.

Scarabeetle
źródło
6
Pytanie zawiera przykład, w którym podproces po prostu kończy się niepowodzeniem. Jeśli możesz użyć podprocesu, to oczywiście powinieneś. Ale jeśli nie możesz ... podproces nie jest rozwiązaniem na wszystko . Aha, a twoja odpowiedź w ogóle nie odpowiada na pytanie.
Jürgen A. Erhard
@ JürgenA.Erhard czy przykład OP nie zawodzi, ponieważ chce użyć rur osłonowych? Powinieneś zawsze używać podprocesu, ponieważ nie używa on powłoki. To jest trochę niezgrabny przykład , ale możesz robić potoki w natywnych podprocesach, jest kilka pakietów pypi, które próbują to ułatwić. Zwykle robię post-processing, którego potrzebuję w Pythonie tak bardzo, jak to tylko możliwe. Zawsze możesz stworzyć własne bufory StringIO i całkowicie kontrolować wszystko za pomocą podprocesów.
ThorSummoner