標準入力の値を引数として実行する


perlでテキストファイルを操作するプログラム書いて使うと非常に簡単で便利です。
さすが、Practical Extraction and Report Language と呼ばれるだけはあります。
しかし、対象となるファイルが大量にある場合などは、一つ一つのテキストファイルに 対してプログラムを実行するとなると大変ですよね。
そこで、引数のリストに対して、プログラムを実行する方法を考えます


■引数のリストを作成する

test.txt として次の文を保存しました

aaa
bbb
ccc
ddd
eee
fff
ggg


catで出力させると次のように標準出力に出力されます

$ cat test.txt
aaa
bbb
ccc
ddd
eee
fff
ggg



■引数を受け取るプログラム

引数を受け取り、出力するのみのperlプログラムを作成しました

test.plとして次のプログラムを保存しました

print "0:$ARGV[0]\n";
print "1:$ARGV[1]\n";
print "2:$ARGV[2]\n";
print "3:$ARGV[3]\n";



■コマンドラインの自動作成

標準入力からコマンドラインを作成するのに xargs コマンドを使用します
xargsとは、標準入力からコマンドラインを作成し、それを実行するコマンドです
標準入力から引数を複数与えた場合、複数の引数を持つコマンドラインを作成して実行します

test.txtの内容を標準入力にしてコマンドラインを生成して実行しました

$ cat test.txt | xargs perl test.pl
0:aaa
1:bbb
2:ccc
3:ddd

引数が複数与えられてプログラムが実行されました。
これでは、複数回コマンドを実行できていませんね
そこでオプションの、--max-args を 2 に指定してもう一度実行してみます
--max-args とは、引数の最大数であり、これを超えた場合においては、新たにプロセスが実行されます

--max-args=2 を指定して実行

$ cat test.txt | xargs --max-args=2 perl test.pl
0:aaa
1:bbb
2:
3:
0:ccc
1:ddd
2:
3:
0:eee
1:fff
2:
3:
0:ggg
1:
2:
3:


引数が2個ずつ複数回プログラムが実行されているのが確認できます
次に --max-args=1 を指定して実行しました


$ cat test.txt | xargs --max-args=1 perl test.pl
0:aaa
1:
2:
3:
0:bbb
1:
2:
3:
0:ccc
1:
2:
3:
0:ddd
1:
2:
3:
0:eee
1:
2:
3:
0:fff
1:
2:
3:
0:ggg
1:
2:
3:

一つの引数に対して一つのプログラムが実行されているのが確認できます



■findと組み合わせて検索したファイルに対してプログラムを実行する

まずは3つのテキストファイルを用意しました
a.txt
b.txt
c c.txt

上記3つのテキストファイルにはそれぞれ目印として test という文字列を入れてあり、この目印以下をプログラムで切り取り、 abcd に置換したいと思います。
 そんなことはプログラムを書かなくてもエディタの機能を使えば出来るって言われそうですね、しかし、WEBページなんかで大量のHTMLファイルに対して文を追加する時やリンクを総て入れ替える時に、いちいちファイルを一つずつ開いて編集作業を行うよりも、なにか、目印となる文字列を入れておいて、一度に置換や編集するほうが便利じゃないかな?と私は思う。

作成したファイル

$ find *.txt
a.txt
b.txt
c c.txt
$ cat a.txt
aaaa
test
bbbb
$ cat b.txt
bbbb
test
cccc
$ cat "c c.txt"
cccc
test
dddd



▼findで検索されたファイル名 c c.txt にスペースが入っていますよね
これは通常コマンドラインとして使用すると、スペースの部分が区切りと判断されておかしな動きになってしまいます
そこで、総てのファイル名を""(ダブルクオーテーション)で囲むプログラムをperlで書きます

dquo.plというファイルを作成して次のプログラムを保存しました


while($line=<>){
	$line =~ s/\r//;		#改行を削除する
	$line =~ s/\n//;		#改行を削除する
	print "\"$line\"\n";	#""で囲って出力する
}


実行すると総てのファイル名に対してダブルクオーテーションで囲みます

$ find *.txt | perl dquo.pl
"a.txt"
"b.txt"
"c c.txt"



▼次にファイルを目的の形に編集し上書き保存するプログラムを書きます

test.pl として保存しました
ファイルを開き一行単位で配列に格納してゆきます、testという文字列を見つけた時点で、abcd を書き込み、ファイルを上書き保存します


$filename=$ARGV[0];
print "$filename\n";
$i=0;
#ファイルを開いて編集
if(open ( FHandle , "< $filename") ){
	while($line = <FHandle>){
		if($line=~/test/){
			$ar[$i++]="test\n";
			$ar[$i++]="abcd";
			last;
		}else{
			$ar[$i++]=$line;
		}
	}
	close(FHandle);
}else{
	print "FILE OPEN ERR$filename\n";
	exit;
}

#ファイル上書き
if(open ( FHandle , "> $filename") ){
	for($j=0;$j<$i;$j++){
		print FHandle $ar[$j];
	}
	close(FHandle);
}else{
	print "FILE WRITE ERR$filename\n";
}



▼今まで作成したプログラムを連結して実行してみます

$ find *.txt | perl dquo.pl | xargs --max-args=1 perl test.pl
a.txt
b.txt
c c.txt


ファイルの内容を表示

$ cat a.txt
aaaa
test
abcd
$ cat b.txt
bbbb
test
abcd
$ cat "c c.txt"
cccc
test
abcd



検索されたファイルの目印となる文字列 test 以下の文字列がすべて abcd に置換されていることが確認できます




■上記プログラムを文字列 test の行から上を書き換えるように修正したプログラム
testの上の行を総て削除して文字列 xxxx に書き換えます

$filename=$ARGV[0];
print "$filename\n";
$i=0;
if(open ( FHandle , "< $filename") ){
	while($line = <FHandle>){
		$line =~ s/\r//;		#改行を削除する
		$line =~ s/\n//;		#改行を削除する

		if("$line" eq "test"){
			$ar[$i++]="xxxx\n";
			$ar[$i++]="test\n";
			while($line = <FHandle>){
				$ar[$i++]=$line;
			}
		}
	}
	close(FHandle);
}else{
	print "FILE OPEN ERR$filename\n";
}

if($i==0){
	exit;
}

if(open ( FHandle , "> $filename") ){
	for($j=0;$j<$i;$j++){
		print FHandle $ar[$j];
	}
	close(FHandle);
}else{
	print "FILE WRITE ERR$filename\n";
}

上記プログラムを実行してファイルの内容を表示

$ find *.txt | perl dquo.pl | xargs --max-args=1 perl test.pl
a.txt
b.txt
c c.txt
$ cat a.txt
xxxx
test
abcd
$ cat b.txt
xxxx
test
abcd
$ cat "c c.txt"
xxxx
test
abcd

文字列 test より上の行の文字が変更されていることが確認できます。


20061009 作成


▲トップページ > perl 関連