シェルコマンドを実行するために呼び出しを実行するPerlスクリプトがあり、複数のsystem()
コマンドを実行してコマンド間でデータを転送したいと思います。 (Perlから)次のようになります:
system("command1 | command2");
Perl のsystem()
目的は/bin/sh
Ubuntu Server システムで実行されるため、/bin/sh
他のdash
多くのシェルと同様に、ダッシュのパイプ出口値は右端のコマンドの出口値です。これは、次の内容が0
(成功的に)返されることを意味します。
system("false | true")
/bin/sh
私が使用していたシステムは、次のオプションを追加することで簡単に修正bash
できました。pipefail
system("set -o pipefail; false | true");
実際、これは私のローカルArchシステムで期待どおりに機能し、/bin/sh
そのポイントは次のとおりですbash
。
$ perl -le '$status = system("false | true"); print $status>>8'
0
$ perl -le '$status = system("set -o pipefail; false | true"); print $status>>8'
1
(はい、これが奇妙に見えることを知っていますが、Perlがわからない場合は、私の言葉を信じてください。実際の終了ステータスを取得するには8を移動する必要があります。)
ただし、実行される予定のマシンには次のものがあるため、dash
同じ操作が失敗します。
$ perl -le '$status = system("set -o pipefail; false | true"); print $status>>8'
sh: 1: set: Illegal option -o pipefail
2
set -e
また、ソリューションは以下を使用していません。
$ perl -le '$status = system("set -e; false | true"); print $status>>8'
0
パイプを使用しない、モジュールを使用するなど、いくつかのPerlの解決策を考えることができますが、ダッシュからIPC::System::Simple
直接これを行うより簡単な方法があるかもしれません。
dash
もしそうなら、パイプライン命令セットの終了状態が0
(成功)でなければならないことを知っているいくつかのトリック、トリック、またはオプションがありますか?みんなコマンドは有効であり、?のように失敗した場合はset -o pipefail
ゼロではありません。bash
答え1
Dashでこれを行う方法はわかりませんが、/bin/sh
完全にバイパスすることはできます。
Perlは、system()
プログラマが何をしたいかを推測しようとします。引数がsh -c
1つだけで、その引数にシェルメタ文字が含まれている場合にのみ使用します。それ以外の場合は、指定されたコマンドを直接実行し、正確な引数を指定できます(「間接オブジェクト」を介して渡されるシェルまたはargv[0]
シェル$0
内のコマンド名を含む)。
パラメータ処理はパラメータ数によって異なります。 LISTに複数の引数がある場合、またはLISTが複数の値を持つ配列の場合、リストの最初の要素によって提供されるプログラムは、リストの残りの要素が提供する引数を使用して開始されます。
スカラー引数が1つしかない場合は、引数にシェルメタ文字が含まれていることを確認します。そうであれば、構文解析のために引数全体がシステムのコマンドシェルに渡されます(これは
/bin/sh -c
Unixプラットフォームではありますが、他のプラットフォームでは異なります)。
これら2つは同じexecve()
呼び出しを行います。
system{"/bin/sh"} ("sh", "-c", 'false |true; echo $?')
system('false |true; echo $?')
つまり、以下のようにこれですstrace
。
execve("/bin/sh", ["sh", "-c", "false |true; echo $?"], [/* 39 vars */]) = 0
(似ていますがforをsystem("sh", "-c", '...')
探して、単に代わりに実行されたシェルから渡されるという点で少し異なります。大きな違いはありません。)$PATH
sh
system("/bin/sh", "-c", '...')
/bin/sh
argv[0]
sh
したがって、Perlで実行できます。
system("bash", "-o", "pipefail", "-c", 'false |true; echo $?');
# or a bit shorter
system(qw/bash -o pipefail -c/, 'false |true; echo $?');
往復は避けsh
、そしてBashコマンドラインへの関連付けの二重引用符。
答え2
非常に醜いですが、実用的な解決策は次のとおりです。
system(q{
exec 3>&1; \
status1="$(((false 2>&1 1>&3 3>&- 4>&-; echo $? >&4) \
| true 1>&2 3>&- 4>&-) 4>&1)"; \
status2=$? \
[ $status2 != 0 ] && exit $status2; \
exit $status1
});
バックスラッシュ、改行、インデントをすべて1行に表示するには、削除できます。これは以下で適応された。有害とみなされるCshプログラミングPaper、§1d「より細かい組み合わせ」[PosixシェルはできますがCshはできません]。
これはファイル記述子3を開き、stdoutを一時的に保存し、最初のコマンドを実行して出力位置を再指定します。 FD4とFD3は閉じ、FD1(標準出力)はFD3に移動し、FD2(標準エラー)はFD1に移動します。次に、2番目のコマンドを実行する前に終了コードをFD4に入れます。 2番目のコマンドはFD4とFD3を閉じてからstderrをFD2に入れます(stderrが実際に印刷していると思います。これにはstdoutを含めることができますが、魔法を正しく読み取ることはできません)。出力を印刷した後、FD4をstdoutとして返して保存するのは安全です$status1
。 2番目のコマンドの状態がいっぱいになるため、割り当てが$status2
簡単になります$?
。今私たちはそれを実装するだけですpipefail
。$status2
きちんとした終了でない場合は0で終了します。それ以外の場合は、$status1
保存内容に関係なく終了します。
$status
より多くのパイプの新しいファイル記述子(5、6など)と変数を追加しました。
これはPosixコンプライアンスと移植性を提供しますが、Perlでより基本的に実行するか、次を使用する方が良いでしょう。アニクのアドバイスbashを呼び出すと(オプションだと仮定)、比較すると次のようにエレガントに見えます。
system("bash", "-o", "pipefail", "-c", 'false |true; echo $?')
(Perlのより安全な実装についての主張については、以下を参照してください。イルカチュウの答え通話の詳細。 )