0

私はコードを持っています:

open(FILE, "<$new_file") or die "Cant't open file \n";
@lines=<FILE>;

close FILE;

open(STDOUT, ">$new_file") or die "Can't open file\n";
$old_fh = select(OUTPUT_HANDLE);
$| = 1;
select($old_fh);

for(@lines){
    s/(.*?xsl.*?)xsl/$1xslt/;
    print;
}
close(STDOUT);  
STDOUT -> autoflush(1);
print "file changed";

閉じた後STDOUT、プログラムは最後の印刷を書き込みませんprint "file changed"。どうしてこれなの? *編集済み*コンソールに書き込みたいメッセージを印刷します

4

5 に答える 5

4

printデフォルトのファイルハンドルがSTDOUTであり、その時点ですでに閉じられているためだと思います。再度開いたり、他のファイルハンドルに出力したりできますSTDERR

print STDERR "file changed";
于 2012-04-26T10:01:03.997 に答える
3

printステートメントは、デフォルトの出力ファイル ハンドルである に文字列を出力しますSTDOUT

だから声明は

print "This is a message";

と同じです

print STDOUT "This is a message";

あなたのコードではSTDOUT、メッセージを閉じてから印刷していますが、これは機能しません。ファイルハンドルを再度開くSTDOUTか、閉じないでください。スクリプトが終了すると、ファイル ハンドルは自動的に閉じられます。

于 2012-04-26T10:26:19.330 に答える
3

に保存されているファイルハンドルを閉じたためSTDOUTprintもう使用できません。一般に、定義済みのハンドル名の 1 つに新しいファイルハンドルを開くことは、混乱を招く可能性があるため、あまり良い考えではありません。レキシカルファイルハンドルを使用するか、出力ファイルに別の名前を付ける方がはるかに明確です。はい、呼び出しでファイルハンドルを指定する必要がありますprintが、何が起こったのか混乱することはありませんSTDOUT

于 2012-04-26T10:10:01.917 に答える
2

あなたはperlでファイルIO操作がどのように行われるかについて混乱しているようですので、それを読むことをお勧めします。

何が悪かったのか?

あなたがしていることは:

  • 読むためにファイルを開く
  • ファイル全体を読んで閉じます
  • STDOUTファイルハンドルを使用して、上書きするために同じファイルを開きます(組織ファイルは切り捨てられます)。
  • 表示されているコードで開かれていないファイルハンドルに自動フラッシュを設定するには、デフォルトの印刷ハンドルを調整します。
  • すべての行を置換して印刷します
  • STDOUTを閉じ、すべてが完了したらメッセージを出力します。

主な最大の間違いは、デフォルトの出力ファイルハンドルSTDOUTを再度開こうとすることです。これは、どのように機能するかわからないためprint、つまり、に印刷するファイルハンドルを指定できるためだと思いますprint FILEHANDLE "text"。または、STDOUTが事前定義されたファイルハンドルであることを知らなかった。

その他のエラー:

  • 使用しませんでしたuse strict; use warnings;。あなたが書くプログラムはこれらなしであってはなりません。彼らはあなたが悪いことをするのを防ぎ、あなたにエラーに関する情報を与え、そしてあなたにデバッグの時間を節約するでしょう。
  • 本当に必要な場合を除いて、ファイルを「スラップ」(ファイル全体を変数に読み込む)しないでください。これは効果がなく低速であり、巨大なファイルの場合、メモリ不足のためにプログラムがクラッシュするためです。
  • デフォルトのファイルハンドルSTDIN、STDOUT、STDERRを再割り当てしないでください。ただし、A)本当に必要な場合、B)何をしているかがわかっている場合を除きます。
  • select印刷用のデフォルトのファイルハンドルを設定します。ドキュメントをお読みください。これはめったにあなたが自分自身を心配する必要があるものではありません。変数は、現在選択されているファイルハンドルの$|自動フラッシュをオンに設定します(true値に設定されている場合)。OUTPUT_HANDLEは存在しないファイルハンドルであるため、実際には何も実行しませんでした。ステートメントをスキップした場合は、STDOUTの自動フラッシュが設定されます。(しかし、違いに気付かなかったでしょう)select
  • print効率的であるため、印刷バッファを使用します。プリントがバッファに引っ掛かると思うので、自動フラッシュしようとしていると思いますが、これは正しくありません。一般的に言って、これはあなたが心配する必要があるものではありません。プログラムが終了すると、すべての印刷バッファが自動的にフラッシュされます。
  • ほとんどの場合、ファイルハンドルを明示的に閉じる必要はありません。ファイルハンドルは、スコープ外になるか、プログラムが終了すると自動的に閉じられます。
  • 前のステートメントのため、またグローバル変数を避けることが常に良い考えであるためopen my $fh, ...、たとえばグローバルの代わりに、たとえばレキシカルファイルハンドルを使用することをお勧めします。open FILE, ..
  • 3つの引数を開くことをお勧めしますopen FILEHANDLE, MODE, FILENAMEopenこれは、ファイル名のメタ文字がステートメントを破損するリスクがあるためです。

クイックフィックス:

さて、コメントで述べたように、これは、つまり、このコードが間違っているために意図し-pたものであり、コマンドラインスイッチの慣用的な使用法とほとんど同じです。

perl -pi.bak -e 's/(.*?xsl.*?)xsl/$1xslt/' file.txt

この短いスニペットは、実際にはプログラムが実行するすべてのことを実行しますが、はるかに優れています。説明:

  • -pswitchは、指定したコードがループ内にあると自動的に想定し、コードのwhile (<>) { }実行後に各行を出力します。
  • -iスイッチはperlにファイルのインプレース編集を実行するように指示し、バックアップコピーを「file.txt.bak」に保存します。

したがって、そのワンライナーは次のようなプログラムと同等です。

$^I = ".bak"; # turns inplace-edit on
while (<>) {  # diamond operator automatically uses STDIN or files from @ARGV
    s/(.*?xsl.*?)xsl/$1xslt/;
    print;
}

これはこれに相当します:

my $file = shift; # first argument from @ARGV -- arguments 
open my $fh, "<", $file or die $!;
open my $tmp, ">", "/tmp/foo.bar" or die $!; # not sure where tmpfile is
while (<$fh>) {                           # read lines from org file
    s/(.*?xsl.*?)xsl/$1xslt/;
    print $tmp $_;                        # print line to tmp file
}
rename($file, "$file.bak") or die $!;     # save backup
rename("/tmp/foo.bar", $file) or die $!;  # overwrite original file

inplace-editオプションは、実際には別のファイルを作成し、それを元のファイルにコピーします。バックアップオプションを使用する場合、元のファイルが最初にバックアップされます。この情報を知る必要はありません。-iスイッチを使用すると、-p(および-n)オプションが元のファイルに対して実際に変更を実行することを知っているだけです。

バックアップオプションを有効にしてスイッチを使用する-i必要はありませんが(Windowsを除く)、推奨されます。最初にオプションなしでワンライナーを実行することをお勧めします。そうすれば、出力は代わりに画面に出力され、出力に問題がないことを確認したら追加します。

正規表現

s/(.*?xsl.*?)xsl/$1xslt/;

「xsl」を含む文字列を2回検索します。の使用法は.*?、2番目のケースでは適切ですが、最初のケースでは適切ではありません。ワイルドカード文字列で正規表現を開始していることに気付いたときはいつでも、おそらく何か間違ったことをしていることになります。その部分をキャプチャしようとしているのでない限り。

ただし、この場合は、キャプチャして削除し、元に戻すだけで、まったく役に立ちません。したがって、ビジネスの最初の順序は、その部分を取り除くことです。

s/(xsl.*?)xsl/$1xslt/;

さて、何かを削除して元に戻すことは、実際にはそれをまったく削除しないための単なる魔法のトリックです。そもそもそれを取り除くことができないとき、私たちはそのような魔法のトリックを必要としません。ルックアラウンドアサーションを使用すると、これを実現できます。

この場合、可変長式があり、後読みアサーションが必要なため、可変長読み\Kが実装されていないため、代わりに(ニーモニック:Keep)オプションを使用する必要があります。

s/xsl.*?\Kxsl/xslt/;

したがって、何も取り出さなかったので、を使用して何も戻す必要はありません$1。これで、「ねえ、「xsl」を「xslt」に置き換えれば、「xsl」を削除する必要はまったくありません」と気付くかもしれません。どちらが正しいですか:

s/xsl.*?xsl\K/t/;

のようなこの正規表現のオプションを使用することを検討してください/i。これにより、大文字と小文字が無視され、「XSLFOOXSL」などの文字列にも一致します。または、/g最初の一致だけでなく、行ごとに可能なすべての一致を実行できるようにするオプション。詳細については、perlopをご覧ください。

結論

完成したワンライナーは次のとおりです。

perl -pi.bak -e 's/xsl.*?xsl\K/t/' file.txt
于 2012-04-29T16:15:25.150 に答える
2
open OLDOUT, ">&", STDOUT;
close STDOUT;

open(STDOUT, ">$new_file") or die "Can't open file\n";
...
close(STDOUT);  
open (STDOUT, ">&",OLDOUT);
print "file changed";
于 2012-04-26T10:09:46.363 に答える