os.system()呼び出しをエスケープする方法は?


123

os.system()を使用する場合、コマンドにパラメーターとして渡されるファイル名やその他の引数をエスケープする必要があることがよくあります。これどうやってするの?できれば、複数のオペレーティングシステム/シェルで機能するものですが、特にbashの場合に適しています。

私は現在次のことをしていますが、これにはライブラリ関数、または少なくともよりエレガントで堅牢で効率的なオプションが必要です。

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

編集:私は引用符を使用するという単純な答えを受け入れましたが、なぜそれを考えなかったのかわかりません。'と "の動作が少し異なるWindowsから来たのだと思います。

セキュリティに関しては、私は懸念を理解していますが、この場合、os.system()が提供する迅速で簡単な解決策に興味があり、文字列のソースはユーザーが生成したものではないか、少なくとも信頼できるユーザー(私)。


1
セキュリティ問題に注意してください!たとえば、out_filenameがfoo.txtの場合。rm -rf /悪意のあるユーザーは、シェルによって直接解釈されるコマンドをさらに追加できます。
スティーブグリー

6
これは、サブプロセスがオプションでさえない状況で、os.systemなしでも役立ちます。たとえば、シェルスクリプトの生成。

理想的なsh_escape関数は、;とスペースをエスケープし、のようなファイルを作成するだけでセキュリティの問題を解消しますfoo.txt\;\ rm\ -rf\ /
トム

ほとんどすべての場合、os.systemではなく、サブプロセスを使用する必要があります。os.systemの呼び出しは、インジェクション攻撃を要求するだけです。
allyourcode 2016年

回答:


84

これは私が使用するものです:

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

シェルは常に引用符で囲まれたファイル名を受け入れ、問題のプログラムに渡す前に周囲の引用符を削除します。特に、これにより、スペースやその他の厄介なシェルメタキャラクターを含むファイル名の問題が回避されます。

更新:Python 3.3以降を使用している場合は、独自にロールするのではなく、shlex.quoteを使用します。


7
@pixelbeat:これがまさに彼が一重引用符を閉じ、エスケープされたリテラル一重引用符を追加してから、もう一度一重引用符を再び開く理由です。
lhunath 2009年

4
これはshellquote関数の責任ではほとんどありませんが、引用符で囲まれていないバックスラッシュがこの関数の戻り値の直前にある場合は失敗することに注意してください。士気:これを安全であると信頼できるコード(ハー​​ドコードされたコマンドの一部など)で使用するようにしてください。他の引用符で囲まれていないユーザー入力には追加しないでください。
lhunath 2009年

10
シェル機能が絶対に必要でない限り、おそらく代わりにJamieの提案を使用する必要があります。
lhunath 2009年

6
これに似たものが、正式にshlex.quoteとして利用可能になりました
Janus Troelsen 2012年

3
この回答で提供されている関数は、shlexまたはよりもシェル引用の仕事が優れていpipesます。それらのpythonモジュールは、引用符で囲む必要があるのは特殊文字だけであると誤って想定しています。つまり、その動作が予期されていない場合にtime、シェルキーワード(や、caseなどwhile)が解析されます。そのため、この回答では単一引用符ルーチンを使用することをお勧めします。これは、「賢く」しようとしないため、ばかげたエッジケースがないためです。
user3035772

157

shlex.quote() Python 3以降で必要なことを行います。

pipes.quotepython 2とpython 3の両方をサポートするために使用)


もありcommands.mkargます。また、(引用符の外に)必要な場合とそうでない場合がある先行スペースが追加されます。これらの実装が互いにまったく異なっていることは興味深い点であり、Greg Hewgillの回答よりもはるかに複雑です。
ローレンスゴンサルベス

3
何らかの理由で、pipesモジュールの標準ライブラリドキュメントではpipes.quote言及されていません
Day

1
どちらも文書化されていません。command.mkargpipes.quoteが残っている間、3.xで非推奨になり削除されました。
Beni Cherniavsky-Paskin、2011

9
訂正:shlex.quote()3.3のように公式に文書化されpipes.quote()、互換性のために保持されています。[ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin

7
パイプはWindowsでは機能しません-二重引用符が挿入された単一引用符が追加されます。
Nux 2014年

58

おそらく、を使用する特定の理由があるかもしれませんos.system()。しかし、そうでない場合は、おそらくsubprocessモジュールを使用する必要があります。パイプを直接指定して、シェルの使用を回避できます。

以下は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]

6
subprocess(特にcheck_calletcを使用する場合)は劇的に優れていることがよくありますが、シェルのエスケープが依然として役立つ場合がいくつかあります。私が実行している主なものは、sshリモートコマンドを呼び出す必要がある場合です。
クレイグリンガー2013年

@CraigRinger、うん、sshリモーティングが私をここにもたらしました。:PIは、sshがここで役立つことを願っています。
ユルゲンA.エアハルト2013年

@JürgenA.Erhard--execvp-remoteオプションがない(またはデフォルトでそのように動作する)ことは奇妙に思われます。シェルを通してすべてを行うことは、不器用で危険なようです。OTOH、sshには奇妙な癖がたくさんあり、多くの場合、「セキュリティ」という狭い視野の中で行われるため、人々はより安全でない回避策を思い付くようになります。
クレイグリンガー

10

多分subprocess.list2cmdline良いショットですか?


それはかなりよさそうだ。興味深いことは文書化されていません...(少なくともdocs.python.org/library/subprocess.htmlに)
Tom

4
それは適切にエスケープしません\:subprocess.list2cmdline(["'",'',"\\",'"'])与える' "" \ \"
Tino

シェル拡張シンボルをエスケープしません
grep

subprocess.list2cmdline()はWindowsのみを対象としていますか?
JS。

@JSはい、list2cmdlineWindows cmd.exe構文に準拠しています(Pythonソースコードの関数docstringを参照してください)。shlex.quoteUnixボーンシェル構文に準拠していますが、Unixは引数を直接渡すことをサポートしているため、通常は必要ありません。Windowsでは、ほとんどすべての引数を含む単一の文字列を渡す必要があります(したがって、適切にエスケープする必要があります)。
エストラーダ

7

pipes.quoteは実際にはPython 2.5およびPython 3.1で壊れており、安全に使用できないことに注意してください-長さ0の引数は処理しません。

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

Python issue 7476を参照してください。Python 2.6および3.2以降では修正されています。


4
どのバージョンのPythonを使用していますか?バージョン2.6は正しい出力を生成するようです:mycommand arg1 '' arg3(これらは2つの単一引用符ですが、スタックオーバーフローのフォントではわかりにくいです!)
Brandon Rhodes

4

注意:これはPython 2.7.xの回答です。

ソースによると、pipes.quote()/ bin / shの単一の引数として文字列を確実に引用する」方法です。(バージョン2.7以降非推奨であり、最終的にはPython 3.3でshlex.quote()関数として公開されています。)

上の一方subprocess.list2cmdline()「する方法であるのと同じ規則を使用して、コマンドライン文字列に引数の順序を翻訳MS Cランタイム」。

ここでは、コマンドラインの文字列を引用するプラットフォームに依存しない方法です。

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)

使用法:

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

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

3

os.systemは、ユーザー用に構成されたコマンドシェルを呼び出すだけなので、プラットフォームに依存しない方法では実行できないと思います。私のコマンドシェルは、bash、emacs、ruby、quake3のいずれでもかまいません。これらのプログラムの中には、渡される引数の種類を期待していないものもあり、そうした場合でも、同じ方法でエスケープを行う保証はありません。


2
ほとんどまたは完全にPOSIX準拠のシェルを期待することは不合理ではありません(少なくとも、Windowsのすべての場所で、シェルがどのようなシェルであるかを知っています)。os.systemは、少なくともここでは$ SHELLを使用しません。

2

私が使用する関数は次のとおりです。

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

つまり、私は常に引数を二重引用符で囲み、次に、二重引用符内の特殊文字だけをバックスラッシュで引用符で囲みます。


あなたはそれ以外の場合はエスケープ処理が発生しません「\\ "」、『\\ $』と『\ `』を、使用する必要があることに注意してください。
JanKanis

1
さらに、一部の(奇妙な)ロケールで二重引用符を使用すると問題が発生します。pipes.quote@JohnWisemanが指摘した修正案の使用も壊れています。したがって、グレッグ・ヒューギルの答えが使用されます。(これは、通常のケースでシェルが内部的に使用するものでもあります。)
mirabilos

-3

systemコマンドを使用する場合は、os.system()呼び出しに含まれるものをホワイトリストに登録してみます。たとえば、

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

subprocessモジュールの方が優れたオプションです。os.system/ subprocessのようなものはできるだけ使用しないようにすることをお勧めします。


-3

本当の答えは次のとおりos.system()です。そもそも使用しないでください。subprocess.call代わりに使用して、エスケープされていない引数を指定してください。


6
質問には、サブプロセスが単に失敗する例が含まれています。サブプロセスを使用できる場合は、そうする必要があります。しかし、それができない場合...サブプロセスはすべての解決策ではありません。ああ、そしてあなたの答えは、すべての質問に答えていません。
ユルゲン・A.エアハルト

@JürgenA.Erhardはシェルパイプを使用したいので、OPの例は失敗しませんか?あなたはいつも、サブプロセスを使用する必要がありますので、それはシェルを使用していません。これは例の少し不格好ですが、ネイティブサブプロセスでパイプを実行できます。これを簡単にしようとするpypiパッケージがいくつかあります。私はできる限りPythonで必要な後処理を行う傾向があります。常に独自のStringIOバッファーを作成し、サブプロセスでかなり完全に制御できます。
ThorSummoner 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.