web-dev-qa-db-ja.com

「!!!!」を含む文字列を渡すときのargvの奇妙な動作

*argv[]からいくつかの入力パラメーターを取得して出力する小さなプログラムを作成しました。ほとんどすべてのユースケースで、私のコードは完璧に機能します。引数として渡したい文字列の最後に複数の感嘆符を使用すると問題が発生します...

これは動作します:

./program -m "Hello, world!"

これは動作しません:

./program -m "Hello, world!!!!"

^^これを行うと、プログラム出力はその文字列の2倍になるか、。/ programの前に入力したコマンドになります。

しかし、私が絶対に理解していないこと:次の、奇妙なことに、動作します:

./program -m 'Hello, world!!!!'

^^出力は正確に...

Hello, world!!!!

...必要に応じて。

だから、私の質問は次のとおりです。

  • 文字列で複数の感嘆符を使用すると、なぜこの奇妙な動作が発生しますか?
  • 私の知る限り、Cでは、文字列に""を使用し、単一の文字に''を使用します。それでは、''を使用しているときに必要な結果が得られますが、""を使用しているときはそうではありません(私の理解では)。
  • コードに間違いがありますか、または文字列を入力できるようにするために何を変更する必要がありますか?

私のコードの関連部分:

// this is a simplified example that, in essence, does the same 
// as my (significantly longer) code
int main(int argc, char* argv[]) {
    char *msg = (char *)calloc(1024, sizeof(char));

    printf("%s", strcat(msg, argv[2])); // argv[1] is "-m"

    free(msg);
}

私はすでにargv[2]のコンテンツを最初にchar*バッファーにコピーし、それに'\0'を追加しようとしましたが、何も変わりませんでした。

31
ci7i2en4

これは、コードではなく、それを開始するシェルに関連しています。

ほとんどのシェルでは、!!は、最後に実行されたコマンドの省略形です。二重引用符を使用すると、シェルは文字列内で 履歴展開 (変数置換などとともに)を許可するため、!!二重引用符で囲まれた文字列内で、最後に実行されたコマンドを置き換えます。

これがあなたのプログラムにとって意味することは、これはすべて、プログラムが実行される前に起こるということです。したがって、文字列が渡されたは有効です。

対照的に、一重引用符を使用する場合、シェルは置換を実行せずnot、文字列は変更されずにプログラムに渡されます。

したがって、この文字列を渡すには単一引用符を使用する必要があります。ユーザーは、置換を実行したくない場合、これを知る必要があります。別の方法は、渡す文字列の入力をユーザーに求めるラッパーシェルスクリプトを作成し、その後スクリプトが適切な引数を使用してプログラムを呼び出すことです。

68
dbush

シェルは、二重引用符で囲まれた文字列で展開します。 Bashマニュアルページ (ほとんどのLinuxディストリビューションのデフォルトであるBashを使用すると仮定)を読むと、 History Expansionセクション を見ると、 !!

前のコマンドを参照してください。

したがって、二重引用符で囲まれた文字列の!!!!は、前のコマンドに2回展開されます。

このような拡張は、単一引用符で囲まれた文字列に対しては行われません。

したがって、問題はプログラム内にあるのではなく、プログラムを呼び出す環境(シェル)が原因です。

提供された回答に加えて、echoはシェルの友人であることを忘れないでください。コマンドに「echo」というプレフィックスを付けると、シェルが実際にスクリプトに送信しているものが表示されます。

echo ./program -m "Hello, world!!!!"

これはあなたにいくつかの奇妙さを示し、正しい方向にあなたを導くのに役立つかもしれません。

8
UncleCarl