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


23

次の行を使用してプログラムを作成したとしましょう。

int main(int argc, char** argv)

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

プログラムは引数間のスペースをいくつ検出できますか?これらをbashで入力したときのように:

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

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


22
好奇心のために、なぜあなたのプログラムはそれを知る必要があるのでしょうか?
nxnev

2
@nxnev以前はいくつかのWindowsプログラムを作成していましたが、それが可能であることを知っているので、Linux(またはUnix)に似たようなものがあるのだろうかと思います。
iBug

9
CP / Mでは、プログラムが独自のコマンドラインを解析する必要があることを漠然と覚えています。これは、すべてのCランタイムがシェルパーサーを実装する必要があったことを意味します。そして、彼らは皆、わずかに異なってそれをしました。
トビースパイト

3
@iBugはありますが、コマンドを呼び出すときに引数を引用符で囲む必要があります。これがPOSIX(および同様の)シェルで行われる方法です。
コンラッドルドルフ

3
@iBug、... Windowsには、上記のCP / MからTobyが言及したのと同じデザインがあります。UNIXはそれを行いません-呼び出されたプロセスの観点から、実行に関与するコマンドラインはありません。
チャールズダフィー

回答:


39

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

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

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

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


良い答え-Unix初心者の場合、これを指摘することが重要です。Unix初心者tar cf texts.tar *.txtは、tarプログラムが実行されると2つの引数を取得し、2番目の引数(*.txt)自体を拡張する必要があるとしばしば考えます。多くの人々は、引数を処理する独自のスクリプト/プログラムを書き始めるまで、それが実際にどのように機能するかを理解していません。
ローレンスレンショー

58

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


9
あなたは言及したいと思うかもしれませんexecve(2)
iBug

3
あなたは正しい、言い訳として、私は現在電話を使用しており、マニュアルページを
検索

1
これはPOSIXの関連セクションです。
スティーブンキット

1
@ Hans-MartinMosner:Termux ...?;-)
DevSolar

9
「一般的に」は、可能であれば特別な複雑なケースを引用することに対する安全策として意図されていました-たとえば、suid rootプロセスは呼び出しシェルのメモリを検査し、未解析のコマンドライン文字列を見つけることができます。
ハンスマーティンモスナー

16

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

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

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


2
@LightnessRacesinOrbit「引数間のスペース」について混乱が生じた場合に備えて、そこにそれを置きました。間の引用符でスペースを置くhelloworldされ、文字通り 2つの引数の間のスペース。
クサラナナンダ

5
@Kusalananda -まあ、ない...間の引用符でスペースを置くhelloworldされ、文字通り 3つの引数の第二の供給します。
ジェレミー

@Jeremy先ほど言ったように、「引数間」が意味することについて混乱が生じた場合に備えて。はい、そうする場合、他の2つのの2番目の引数として。
クサラナンダ

あなたの例は素晴らしく、有益でした。
ジェレミー

1
まあ、みんな、例は混乱と誤解の明らかな原因でした。answerの値に追加しなかったため、それらを削除しました。
クサラナナンダ

9

一般に、他のいくつかの回答で説明したように、それは不可能です。

ただし、Unixシェル通常のプログラムです(コマンドラインを解釈してグロブする、つまり&を実行する前にコマンドを展開する)。シェル操作に関するこの説明を参照してください。あなたは可能性があり、独自のシェルを作成する(または、あなたはいくつかの既存のパッチを当てることができましたフリーソフトウェアのシェルを、例えばGNU bashの(あるいはあなたのログインシェル、参照)、シェルとして使用するpasswdの(5) (5)シェル)。forkexecvebash

たとえば独自のシェルプログラムで完全なコマンドラインを何らかの環境変数に配置する(想像MY_COMMAND_LINEする)か、他の種類のプロセス間通信を使用してコマンドラインをシェルから子プロセスに送信します。

なぜあなたがそれをしたいのか理解できませんが、そのように動作するシェルをコーディングするかもしれません(しかし、そうしないことをお勧めします)。

ところで、プログラムは、シェルではない(しかしfork(2)を実行してからexecve(2)を実行する、または単にexecve現在のプロセスでプログラムを起動する)プログラムによって起動できます。その場合、コマンドラインはまったくなく、プログラムはコマンドなしで起動できます...

シェルがインストールされていない(特殊な)Linuxシステムがある場合があることに注意してください。これは奇妙で珍しいことですが、可能です。その後、専門書く必要がありますのinitとして他のプログラムを起動するプログラムを必要と-任意のシェルを使用せずに、しかし、実行してforkexecveシステムコール。

読むにも3つの簡単な作品:オペレーティングシステムおよびそれを忘れていないexecve実質的に常にあるシステムコール(Linux上で、それらがにリストされているシステムコール(2)も参照)(イントロ2を再初期化)仮想アドレス空間(および他のいくつかのを物事)それを行うプロセス


これが最良の答えです。argv[0] プログラム名と引数の残りの要素はPOSIX仕様であり、変更することはできないと思います(調べたことはありません)。指定できランタイム環境argv[-1]コマンドラインのために、私が想定し...
ピーター-復活モニカ

いいえ、できませんでした。より注意深くexecveドキュメントを読んでください。を使用することはできません。使用するargv[-1]ことは未定義の動作です。
バジルスタリンケビッチ

ええ、良い点(syscallがあることのヒント)-アイデアは少し工夫されています。ランタイムの3つのコンポーネント(シェル、stdlib、およびOS)はすべて、連携する必要があります。シェルはexecvepluscmd追加のパラメーター(またはargv規則)を使用して特別な非POSIX 関数を呼び出す必要があり、syscallはプログラム名へのポインターの前にコマンドラインへのポインターを含むmainの引数ベクトルを構築し、アドレスを渡しますプログラム名へのポインタのargvプログラムの呼び出し時にmain...
ピーター-復活モニカ

シェルを書き直す必要はなく、引用符を使用してください。この機能は、ボーンシェルから利用できましたsh。新しくはありません。
ctrl-alt-delor

引用符を使用するには、コマンドラインを変更する必要があります。OPはそれを望んでいません
バジル・スタリンケビッチ

3

どのシェルコードが実行につながるかをアプリケーションに伝えるように、いつでもシェルに指示できます。たとえば、を使用して、フックを使用zshして$SHELL_CODE環境変数にその情報を渡すことによりpreexec()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の実行につながるzshのコード取得するためにprintenv、これらの引数を持つが。あなたがその情報で何をしたいのかは私には明らかではありません。

bashに最も近い機能zshのは、preexec()そのを使用されるだろう$BASH_COMMANDDEBUGトラップが、ノート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プリプロセッサによっていくつかのスペースがどのように凝縮されたかを確認してください。すべてではありませんが、ほとんどの言語では、区切り文字で使用されるスペースの量に違いはありません。したがって、コンパイラ/インタープリターがここでそれらにある程度の自由を持っていることは驚くことではありません。


私がこれをテストしていたとき、BASH_COMMAND引数を分離する元の空白が含まれていなかったため、OPのリテラルリクエストには使用できませんでした。この回答には、その特定のユースケースのいずれかのデモが含まれていますか?
チャールズダフィー

@CharlesDuffy、私はちょうどbashでzshのpreexec()に最も近いものを示したいと思っていました(それはOPが参照しているシェルであるため)、それはその特定のユースケースに使用できないことを指摘しましたが、非常に明確な。編集を参照してください。この答えは、実行されているコマンドに実行を引き起こしたソースコード(ここではzsh / bash / C)を渡す方法についてより一般的であることを意図しています(有用なものではありませんが、そうすることを望んでいます、特に例では、私はそれがあまり有用ではないことを示しています)
ステファンシャゼル

0

他の回答に足りないものを追加します。

いや

他の回答を見る

たぶん、

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

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

./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

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


技術的には、シェルコード:(./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"通常は子プロセスで)に格納されているファイルを実行し./myprog2つの引数を渡します:./myprogand ␣␣␣␣␣aaa␣␣␣␣␣␣bbbargv[0]and argc[1]argcbeing 2)OPの場合と同様に、これら2つの引数を区切るスペースはまったく渡されませんへmyprog
ステファンシャゼラス

しかし、あなたはコマンドを変更しているので、OPはそれを変更したくありません
バジル・スタリンケビッチ

@BasileStarynkevitchあなたのコメントに続いて、私は再び質問を読みました。あなたは仮定をしている。OPは、プログラムの実行方法を変更したくないとは言いません。たぶんこれは本当ですが、彼らはそれについて何も言うことがありませんでした。したがって、この答えが必要な場合があります。
ctrl-alt-delor

OPは聞いて明示的にスペースについての間の引数ではなく、スペースを含む1つの引数について
バジーレStarynkevitch
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.