あなたは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, FILENAME
。open
これは、ファイル名のメタ文字がステートメントを破損するリスクがあるためです。
クイックフィックス:
さて、コメントで述べたように、これは、つまり、このコードが間違っているために意図し-p
たものであり、コマンドラインスイッチの慣用的な使用法とほとんど同じです。
perl -pi.bak -e 's/(.*?xsl.*?)xsl/$1xslt/' file.txt
この短いスニペットは、実際にはプログラムが実行するすべてのことを実行しますが、はるかに優れています。説明:
-p
switchは、指定したコードがループ内にあると自動的に想定し、コードの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