web-dev-qa-db-ja.com

プログラムがPOSIXのコマンドライン引数の間にスペースの数を取得することは可能ですか?

次の行でプログラムを書いたとしましょう。

int main(int argc, char** argv)

これで、argvの内容を確認することで、どのコマンドライン引数が渡されるかがわかります。

プログラムは引数間のスペースをいくつ検出できますか?私がこれらをbashでタイプするときのように:

ibug@linux:~ $ ./myprog aaa bbb
ibug@linux:~ $ ./myprog       aaa      bbb

環境は最新のLinux(Ubuntu 16.04など)ですが、POSIX準拠のシステムすべてに当てはまるはずです。

23
iBug

「引数の間のスペース」について話すことは意味がありません。それがシェルのコンセプトです。

シェルの仕事は、入力の行全体を取り、それらを引数の配列に形成して、コマンドを開始することです。これには、引用符で囲まれた文字列の解析、変数の展開、ファイルのワイルドカードとチルダ式などが含まれます。コマンドは、文字列のベクトルを受け入れる標準のexecシステムコールで開始されます。

文字列のベクトルを作成する方法は他にもあります。多くのプログラムは、事前に定義されたコマンド呼び出しで独自のサブプロセスをフォークして実行します。その場合、「コマンドライン」などは存在しません。同様に、ユーザーがファイルアイコンをドラッグしてコマンドウィジェットにドロップすると、グラフィカル(デスクトップ)シェルがプロセスを開始する可能性があります。ここでも、引数の間に文字を持つテキスト行はありません。

呼び出されたコマンドに関する限り、シェルまたは他の親/前駆体プロセスで行われていることはプライベートで非表示です-標準Cがmain()が受け入れることができると指定している文字列の配列のみが表示されます。

39
Toby Speight

一般的には違います。コマンドラインの解析は、呼び出されたプログラムで解析されていない行を利用できないようにするシェルによって行われます。実際、プログラムは、文字列を解析するのではなく、プログラムで引数の配列を構築することによってargvを作成した別のプログラムから実行される可能性があります。

58

スペースがの一部引数でない限り、これは不可能です。

コマンドは配列から個々の引数にアクセスし(プログラミング言語に応じて1つの形式または別の形式で)、実際のコマンドラインは履歴ファイルに保存される場合があります(履歴ファイルがあるシェルのインタラクティブプロンプトで入力された場合)。いかなる形式でもコマンドに渡されることはありません。

Unix上のすべてのコマンドは、最終的にはexec()ファミリーの関数の1つによって実行されます。これらはコマンド名と引数のリストまたは配列を取ります。シェルプロンプトで入力されたコマンドラインを使用するものはありません。 system()関数は実行しますが、その文字列引数はexecve()によって後で実行されます。これも、コマンドライン文字列ではなく引数の配列を取ります。

16
Kusalananda

いつでもシェルに、どのシェルコードが実行につながるかをアプリケーションに伝えるように指示できます。たとえば、zshでは、preexec()フックを使用して_$Shell_CODE_環境変数にその情報を渡すことで、例として使用されます(printenvを使用すると、 getenv("Shell_CODE")(プログラム内):

_$ preexec() export Shell_CODE=$1
$ printenv Shell_CODE
printenv Shell_CODE
$ printenv  Shell_CODE
printenv  CODE
$ $(echo printenv Shell_CODE)
$(echo printenv Shell_CODE)
$ for i in Shell_CODE; do printenv "$i"; done
for i in Shell_CODE; do printenv "$i"; done
$ printenv Shell_CODE; : other command
printenv Shell_CODE; : other command
$ f() printenv Shell_CODE
$ f
f
_

これらはすべてprintenvを次のように実行します。

_execve("/usr/bin/printenv", ["printenv", "Shell_CODE"], 
       ["PATH=...", ..., "Shell_CODE=..."]);
_

printenvがこれらの引数を使用してprintenvを実行するzshコードを取得できるようにします。あなたがその情報で何をしたいのか、私にはわかりません。

bashを使用すると、zshpreexec()に最も近い機能が_$BASH_COMMAND_をDEBUGトラップで使用しますが、bashは、ある程度の書き換えを行い(特に、区切り文字として使用される空白の一部をリファクタリングし)、プロンプトで入力されたコマンドライン全体ではなく、すべての(実行された)コマンド実行に適用されます(「 functraceオプション)。

_$ trap 'export Shell_CODE="$BASH_COMMAND"' DEBUG
$ printenv Shell_CODE
printenv Shell_CODE
$ printenv $(echo 'Shell_CODE')
printenv $(echo 'Shell_CODE')
$ for i in Shell_CODE; do printenv "$i"; done; : other command
printenv "$i"
$ printf '%s\n' "$(printenv "Shell_CODE")"
printf '%s\n' "$(printenv "Shell_CODE")"
$ set -o functrace
$ printf '%s\n' "$(printenv "Shell_CODE")"
printenv "Shell_CODE"
$ print${-+env  }    $(echo     'Shell_CODE')
print${-+env  } $(echo     'Shell_CODE')
_

シェル言語構文で区切り文字であるスペースの一部が1に圧縮されていること、およびコマンドライン全体が常にコマンドに渡されていないことを確認してください。したがって、おそらくあなたのケースでは役に立たないでしょう。

次のようにすべてのコマンドに機密情報が漏洩する可能性があるため、このようなことはお勧めしません。

_echo very_secret | wc -c | untrustedcmd
_

その秘密をwcuntrustedcmdの両方に漏らします。

もちろん、シェル以外の言語でも同じようなことができます。たとえば、Cでは、コマンドを実行するCコードを環境にエクスポートするいくつかのマクロを使用できます。

_#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define WRAP(x) (setenv("C_CODE", #x, 1), x)

int main(int argc, char *argv[])
{
  if (!fork()) WRAP(execlp("printenv", "printenv", "C_CODE", NULL));
  wait(NULL);
  if (!fork()) WRAP(0 + execlp("printenv",   "printenv", "C_CODE", NULL));
  wait(NULL);
  if (argc > 1 && !fork()) WRAP(execvp(argv[1], &argv[1]));
  wait(NULL);
  return 0;
}
_

例:

_$ ./a.out printenv C_CODE
execlp("printenv", "printenv", "C_CODE", NULL)
0 + execlp("printenv", "printenv", "C_CODE", NULL)
execvp(argv[1], &argv[1])
_

Bashの場合のように、いくつかのスペースがCプリプロセッサーによってどのように圧縮されたかを確認してください。すべてではないにしてもほとんどの言語では、区切り文字で使用されるスペースの量に違いはありません。そのため、コンパイラー/インタープリターがそれらをある程度自由に使用できることは当然のことです。

3

他の答えに欠けているものを追加します。

番号

他の回答を見る

たぶん

プログラムで実行できることは何もありませんが、プログラムを実行すると、シェルで実行できることがいくつかあります。

引用符を使用する必要があります。だから代わりに

./myprog      aaa      bbb

これらのいずれかを行う必要があります

./myprog "     aaa      bbb"
./myprog '     aaa      bbb'

これは、すべてのスペースを含む単一の引数をプログラムに渡します。 2つには違いがあります。2番目はリテラルで、表示されるとおりの文字列です(ただし、'\'と入力する必要があります)。最初のものはいくつかの文字を解釈しますが、いくつかの引数に分割されます。詳細については、シェルの引用を参照してください。したがって、シェルを書き直す必要はありません。シェルの設計者はすでにそのことを考えています。ただし、これは現在1つの引数なので、プログラム内でより多くの受け渡しを行う必要があります。

オプション2

Stdinを介してデータを渡します。これは、大量のデータをコマンドに取り込む通常の方法です。例えば.

./myprog << EOF
    aaa      bbb
EOF

または

./myprog
Tell me what you want to tell me:
aaaa bbb
ctrl-d

(斜体はプログラムの出力です)

0
ctrl-alt-delor