構文は正しいですが、ある場合perl
にはエラーメッセージをドロップしています。
一般に、初期化中にシステムに必要なコマンドがあることをテストし、コマンドがない場合は早期に失敗することを検討してください。
my $foopath = "/usr/bin/foo";
die "$0: $foopath is not executable" unless -x $foopath;
# later ...
my $output = `$foopath 2>&1`;
die "$0: $foopath exited $?" if $?;
出力の違いを完全に理解するには、Unixシステムプログラミングの詳細を理解する必要があります。読む。
Unixファイル記述子
単純なperl
呼び出しについて考えてみます。
perl -e'print "hi \ n"; 警告"さようなら\n" '
その出力は
こんにちは
さよなら
print
の出力はSTDOUT
標準出力になり、標準エラーにwarn
書き込まれることに注意してください。STDERR
ターミナルから実行すると、両方がターミナルに表示されますが、別の場所に送信できます。例えば
$ perl -e'print "hi \ n"; 警告"さようなら\n">/ dev / null
さよなら
nullデバイスまたは/dev/null
それに送信された出力を破棄するため、上記のコマンドでは「hi」が消えます。上記のコマンドは略記です
$ perl -e'print "hi \ n"; 警告"さようなら\n"'1> / dev / null
さよなら
つまり、1はのファイル記述子ですSTDOUT
。代わりに「さようなら」を捨てるには、
$ perl -e'print "hi \ n"; 警告"さようなら\n"'2> / dev / null
こんにちは
ご覧のとおり、2はのファイル記述子ですSTDERR
。(完全を期すために、のファイル記述子STDIN
は0です。)
Bourneシェルとその派生物では、とをマージすることもSTDOUT
できSTDERR
ます2>&1
。「ファイル記述子2の出力をファイル記述子1の出力と同じ場所に移動させる」と読みます。</p>
$ perl -e'print "hi \ n"; 警告"さようなら\n"'2>&1
こんにちは
さよなら
ターミナル出力は区別を強調しませんが、追加のリダイレクトは何が起こっているかを示します。実行することで両方を破棄できます
$ perl -e'print "hi \ n"; 警告"さようなら\n">/ dev / null 2>&1
リダイレクトを左から右の順序で処理するこのシェルファミリでは順序が重要であるため、2つの結果を入れ替えます。
$ perl -e'print "hi \ n"; 警告"さようなら\n"'2>&1> / dev / null
さよなら
これは最初は意外かもしれません。シェルは最初に処理します。これは、ターミナルと同じ宛先に2>&1
送信することを意味します。次に、処理してnullデバイスにリダイレクトします。STDERR
STDOUT
>/dev/null
STDOUT
dup2
このファイル記述子の重複は、通常はのラッパーであるを呼び出すことによって発生しますfcntl
。
リダイレクトとパイプライン
ここで、コマンドの出力の各行にプレフィックスを追加するとします。
$ perl -e'print "hi \ n"; 警告"さようなら\n"'| sed -e's / ^ / got:/'
さよなら
取得:こんにちは
STDERR
順序は異なりますが、とSTDOUT
は異なるストリームであることを忘れないでください。「hi」だけがプレフィックスを持っていることにも注意してください。両方の行を取得するには、両方がに表示される必要がありますSTDOUT
。
$ perl -e'print "hi \ n"; 警告"さようなら\n"'2>&1 | sed -e's / ^ / got:/'
取得:さようなら
取得:こんにちは
パイプラインを構築するために、シェルはで子プロセスを作成しfork
、でリダイレクトを実行しdup2
、パイプラインの各ステージをexec
適切なプロセスでの呼び出しで開始します。上記のパイプラインの場合、プロセスは次のようになります。
- シェル:
fork
実行するプロセスsed
sed
シェル:からの終了ステータスを待つwaitpid
- sed:
pipe
入力をフィードするためのを作成しますperl
- sed:
fork
実行するプロセスperl
- sed:パイプの読み取り端から読み取り
dup2
を行うSTDIN
- sed :
exec
コマンドsed
- sed:入力を待つ
STDIN
- perl:ステップ3からパイプの書き込み端に
dup2
送信しますSTDOUT
- perl:の宛先
dup2
に送信STDERR
するSTDOUT
- perl :
exec
コマンドperl
- perl:出力を書き込み、最終的には
exit
- sed:入力ストリームを受信して編集します
- sed:パイプ上のファイルの終わりを検出します
- sed:reap
perl
の終了ステータスをwaitpid
- sed:
exit
- シェル:
$?
からの戻り値を入力しますwaitpid
子プロセスは右から左の順序で作成されることに注意してください。これは、Bourneファミリのシェルが、パイプラインの終了ステータスを最後のプロセスの終了ステータスとして定義しているためです。
日曜大工のパイプライン
以下のコードを使用して、Perlで上記のパイプラインを構築できます。
#! /usr/bin/env perl
use strict;
use warnings;
my $pid = open my $fh, "-|";
die "$0: fork: $!" unless defined $pid;
if ($pid) {
while (<$fh>) {
s/^/got: /;
print;
}
}
else {
open STDERR, ">&=", \*STDOUT or print "$0: dup: $!";
exec "perl", "-e", q[print "hi\n"; warn "bye\n"]
or die "$0: exec: $!";
}
のperlfuncドキュメントに記載されているように、最初の呼び出しopen
は私たちのために多くの作業を行います。open
MODEがの場合の3つ以上の引数の場合"|-"
、ファイル名は出力がパイプされるコマンドとして解釈され、MODEが"-|"
の場合、ファイル名は出力をパイプするコマンドとして解釈されます。2つの引数(および1つの引数)の形式では、ダッシュ("-"
)をコマンドに置き換える必要があります。この例の詳細については、perlipcでのIPCの使用open
を参照してください。
その出力は
$ ./simple-pipeline
取得:さようなら
取得:こんにちは
上記のコードは、の複製をハードコードしてSTDOUT
います。これは以下で確認できます。
$ ./simple-pipeline> / dev / null
Perlのバックティック
別のコマンドの出力をキャプチャするにはperl
、同じ機械を設定します。これは(in)で確認でき、pp_backtick
(in )pp_sys.c
を呼び出して子プロセスを作成し、配管(、、)を設定します。子はいくつかの配管を行い、(in )を呼び出して、出力が必要なコマンドを開始します。そこで、関連するコメントに気づきました:Perl_my_popen
util.c
fork
pipe
dup2
Perl_do_exec3
doio.c
/* handle the 2>&1 construct at the end */
実装はシーケンスを認識し2>&1
、複製STDOUT
し、シェルに渡されるコマンドからリダイレクトを削除します。
if (*s == '>' && s[1] == '&' && s[2] == '1'
&& s > cmd + 1 && s[-1] == '2' && isSPACE(s[-2])
&& (!s[3] || isSPACE(s[3])))
{
const char *t = s + 3;
while (*t && isSPACE(*t))
++t;
if (!*t && (PerlLIO_dup2(1,2) != -1)) {
s[-2] = '\0';
break;
}
}
後で見る
PerlProc_execl(PL_sh_path, "sh", "-c", cmd, (char *)NULL);
PERL_FPU_POST_EXEC
S_exec_failed(aTHX_ PL_sh_path, fd, do_report);
中S_exec_failed
には、
if (ckWARN(WARN_EXEC))
Perl_warner(aTHX_ packWARN(WARN_EXEC), "Can't exec \"%s\": %s",
cmd, Strerror(e));
それはあなたがあなたの質問で尋ねた警告の1つです。
タイムライン
perl
質問からのコマンドを処理する方法の詳細を見ていきましょう。
予想通り
$ perl-e'strictを使用します。警告を使用します。私の$out= `DNE`; $out'を印刷
「DNE」を実行できません:-e行1にそのようなファイルまたはディレクトリはありません。
-e行1の印刷での初期化されていない値の使用。
ここに驚きはありません。
微妙な詳細を理解することが重要です。社内で処理する上記のコード2>&1
は、実行するコマンドの条件が真の場合にのみ実行されます。
if (*s != ' ' && !isALPHA(*s) &&
strchr("$&*(){}[]'\";\\|?<>~`\n",*s)) {
これは最適化です。backticksのコマンドに上記のシェルメタ文字が含まれている場合は、それperl
をシェルに渡す必要があります。ただし、シェルメタ文字が存在しない場合は、コマンドを直接perl
実行できるため、シェルの起動コストを節約できます。exec
fork
存在しないコマンドDNE
にはシェルメタ文字が含まれていないため、perl
すべての作業が含まれます。コマンドが失敗し、warnings
プラグマを有効にしたため、exec-category警告が生成されます。perlopのドキュメントには、コマンドが失敗したときにバッククォートまたはスカラーコンテキストでqx//
戻ることが記載undef
されているため、未定義の値の出力に関する警告が表示されます$out
。
警告がありません
$ perl-e'strictを使用します。警告を使用します。私の$out= `DNE 2>&1`; $out'を印刷
-e行1の印刷での初期化されていない値の使用。
失敗したexec
警告はどこに行きましたか?
別のコマンドを実行する子プロセスを作成する基本的な手順を覚えておいてください。
- 子がその出力を親に送信するためのパイプを作成します。
- 呼び出し
fork
て、ほぼ同一の子プロセスを作成します。
- 子では、パイプの書き込み端に
dup2
接続します。STDOUT
- 子で
exec
、新しく作成された子に代わりに別のプログラムを実行させる。
- 親で、パイプの内容を読み取ります。
別のコマンドの出力をキャプチャするにperl
は、次の手順を実行します。実行を試みる準備として、子DNE 2>&1
をperl
フォークし、子プロセスSTDERR
での複製が発生しますが、別STDOUT
の副作用があります。
if (!*t && (PerlLIO_dup2(1,2) != -1)) {
s[-2] = '\0';
break;
}
2>&1
がコマンドの最後にあり、dup2
成功した場合はperl
、リダイレクトの直前にNULバイトを書き込みます。これには、コマンドから削除する効果があります。たとえば、!にDNE 2>&1
なります。DNE
これで、コマンドにシェルメタ文字がないためperl
、子プロセスで「自分自身、exec
このコマンドを直接実行できます」と考えます。</ p>
が存在しないため、への呼び出しはexec
失敗します。DNE
子はまだに失敗したexec
警告を発しSTDERR
ます。親に戻るパイプの書き込み終了と同じ場所dup2
を指しているため、ターミナルには移動しません。STDERR
STDOUT
親プロセスは、子が異常終了したことを検出し、失敗したコマンド実行の結果がであると文書化されているため、パイプの内容を無視しますundef
。
別の警告
$ perl-e'strictを使用します。警告を使用します。私の$out= `echo 123; DNE 2>&1`; $out'を印刷
123
sh:DNE:コマンドが見つかりません
ここでは、DNE
存在しないという別の診断が表示されます。最初に検出されるシェルメタ文字はです;
。したがってperl
、コマンドを変更せずにシェルに渡して実行します。はecho
正常に完了し、シェルでDNE
失敗し、シェルで失敗して親プロセスに戻りますSTDOUT
。STDERR
の観点perl
からは、シェルは正常に実行されたため、警告する必要はありません。
関連事項
warnings
プラグマを有効にすると(非常に良い方法です!)、これによりexec
警告カテゴリが有効になります。これらの警告の完全なリストを表示するには、perldiagのドキュメントで文字列を検索してくださいW exec
。
違いを観察してください。
$ perl -Mstrict -Mwarnings -e'my $ out = `DNE`; $out'を印刷
「DNE」を実行できません:-e行1にそのようなファイルまたはディレクトリはありません。
-e行1の印刷での初期化されていない値$outの使用。
$ perl -Mstrict -Mwarnings -M-warnings = exec -e'my $ out = `DNE`; $out'を印刷
-e行1の印刷での初期化されていない値$outの使用。
後者の呼び出しは、
use strict;
use warnings;
no warnings 'exec';
my $out = `DNE`;
print defined($out) ? $out : "command failed\n";
exec
、パイプなどで問題が発生したときに、自分のエラーメッセージをフォーマットするのが好きopen
です。これは、通常、exec警告を無効にすることを意味しますが、戻り値をテストするために特に注意する必要があることも意味します。