プログラムがPOSIXのコマンドライン引数の間のスペース数を取得できますか?

プログラムがPOSIXのコマンドライン引数の間のスペース数を取得できますか?

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

int main(int argc, char** argv)

.txtの内容を調べて、どのコマンドライン引数が渡されたかを確認できますargv

プログラムが引数の間に空白がいくつあるかを検出できますか? bashに次のように入力したときと同じです。

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

環境は最新のLinux(例:Ubuntu 16.04)ですが、答えはすべてのPOSIX互換システムに適用する必要があると思います。

答え1

一般的に言えばそうではありません。コマンドラインの解析はシェルによって実行され、呼び出されたプログラムで解析されていない行を使用できるようにはしません。実際には、文字列を解析するのではなく、プログラムで引数配列を構築してargvを生成する他のプログラムでプログラムを実行できます。

答え2

「引数間のスペース」について話すのはシェルの概念です。

シェルの役割は、完全な入力行を取得してコマンドを実行するための引数の配列として構成することです。これには、引用符付き文字列の解析、変数拡張、ファイルワイルドカード、チルダ式などが含まれます。このコマンドは、exec文字列ベクトルを受け入れる標準システムコールで始まります。

文字列ベクトルを生成する別の方法があります。多くのプログラムは、スケジュールされたコマンド呼び出しを介して独自のサブプロセスを分岐して実行します。この場合、「コマンドライン」のようなものはありません。同様に、グラフィカル(デスクトップ)シェルは、ユーザーがファイルアイコンをドラッグしてコマンドウィジェットにドロップすると、プロセスを開始できます。繰り返しますが、引数「間」に文字を含むテキスト行はありません。

シェルまたは他の親/先駆的なプロセスで何が起こるかは、呼び出されるコマンドに関する限り、個人的で隠されています。main()標準Cで許可されている文字列配列のみを表示できます。

答え3

いいえ、空白がなければ不可能です。部分議論。

コマンドは配列の個々のパラメータにアクセスし(プログラミング言語によっては何らかの形で)、実際のコマンドラインは履歴ファイルに保存できます(プロンプトに履歴ファイルが入力されたシェルで対話型の場合)。しかし、いかなる形でもコマンドに渡されるわけではありません。

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

答え4

いつでも、シェルにアプリケーションを実行させるシェルコードをアプリケーションに通知させることができます。たとえば、フックを使用してその情報を環境変数に渡しますzsh(プログラムで使用する例)。$SHELL_CODEpreexec()printenvgetenv("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=..."]);

これらのパラメータを使用して、実行を引き起こしたzshコードをprintenv取得できます。printenvこの情報で何をしたいのかは不明です。

の場合、sにbash最も近いzsh関数はこれをトラップで使用preexec()しますが、ある程度書き換えがあります(特に区切り文字として使用されている空白の一部をリファクタリング)、これは実行されるすべての(まさにいくつかの)コマンドに適用されます。プロンプトに入力された完全なコマンドラインの代わりに(このオプションも参照)$BASH_COMMANDDEBUGbashfunctrace

$ 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

wcこの秘密をおよびに公開しますuntrustedcmd

もちろん、シェル以外の言語でも可能です。たとえば、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プリプロセッサが特定のスペースをどのように圧縮するかを学びます。ほとんどの(すべてではありませんが)言語で区切り文字に使用されるスペースの量に違いはないため、コンパイラ/通訳者がここで区切り文字をある程度自由に使用することは驚くべきことではありません。

関連情報