翻訳:「Perl FAQ 翻訳グループ」
同メンバ:
樋口さん、上原さん、高田さん、関谷さん、妻鹿さん、三島
------------------------------------------------------------------------------
Stephen P Potter Pencom Systems Administration AMP Incorporated
spp@peach.epix.net spp@psa.pencom.com steve.potter@amp.com
Home: 717-540-0131 Pager: 1-800-759-8888, 547-9561 Work: 717-986-5401
Systems Administation: It's a Kind of Magic
------------------------------------------------------------------------------
Archive-name: perl-jfaq/part5 Version: $Id: part5,v 1.1 1996/05/28 19:53:53 sekiya Exp $
Part 5 - 配列、シェル、および外部プログラムとの相互作用
5.1) $array[1]と@array[1]の違いは何ですか?
5.2) 多次元配列や再帰的なデータタイプはどうやって作ったら良いのですか?
5.3) 様々なデータ型を含んでいる構造体の配列を作るにはどうすればいいのですか?
5.4) 配列中のユニークな要素を抜き出すにはどうしたらよいのですか?
5.5) 配列に、ある要素が含まれているかどうかを知るにはどうすればいいのですか?
5.6) 連想配列を key の代わりに value でソートするにはどうしたらよいの ですか?
5.7) 連想配列の中にいくつのエントリがあるかを知るにはどうすればいいのですか?
5.8) %arrays に対する "delete" と "undef" との違いは何ですか?
5.9) なぜバッククォート記法(backticks)はシェルの中でと同様に動かないのですか?
5.10) perlに変換した awk/sed/sh スクリプトは何故前より遅く動くのですか?
5.11) 私のシステム固有のC関数を呼ぶにはどうすればいいのですか?
5.12) ioctl() や syscall() を使うのにインクルードファイルをどこから得 るのですか? [h2ph]
5.13) setuidされたperlスクリプトはなぜカーネルの問題について苦情を言うのですか?
5.14) コマンドからとコマンドへの双方向のパイプはどうやってオープンするのですか?
5.15) 外部のコマンドからのSTDERRはどうやれば捉えられますか?
5.16) パイプのオープンに失敗した時、何故 open() はエラーを返さないのですか?
5.##) 外部のプログラムからの終了ステータスを得るにはどうしたらよいですか?
5.17) なぜ私のスクリプトは、^D(EOF)を与えた後のSTDINから読み込む事ができないのですか?
5.18) ファイルネーム中のチルダ(~)はどうやったら変換できますか?
5.19) シェルスクリプトをPerlに変換するにはどうすればいいのですか?
5.20) telnet や ftp を走らせるために perl を使うことができますか?
5.21) <*>を使った時、たまに"Argument list to long"(引数リストが長すぎる)と言われるのはなぜですか?
5.22) perl で "tail -f" を実行するにはどうしたらよいですか?
5.23) "ps"のようなプログラムから、perlのコマンドラインを隠す方法はありますか?
5.25) ファイルハンドルを関数に渡したり、ファイルハンドルのリストを作ることはどうすればできますか?
5.26) 先頭に ">" があるファイルや、末尾に空白のあるファイルはどうやったらオープンすることができますか?
5.27) もし変数が"汚染されて"いたなら、どうすればそれを知る事ができますか?
$は単一の値に、@は複数の値に使うことを常に確認しなさい。つまり@foo の2番目の要素は@foo[1]ではなく$foo[1]でアクセスします。@foo[1]だと スカラーではなく長さ一のリストになります。これは初心者が陥りがちな ミスです。場合によっては@foo[1]がつかえるかもしれません。しかし、 それはあなたが思っている理由によって本当にそれが行なわれているので はなく、あなたが時を待たずして自滅するだろう、ということを意味して います。以下が、本当には何をしているのかをしばらく熟考して見なさい。
@foo[0] = `cmd args`;
@foo[1] = <FILE>
常に$foo[1]を使うようにしなさい。そうすればもっと幸せになれるでしょう。
これは混乱しているように見えるかも知れなせん。しかし、次のように考 えてみて下さい: 「あなたは *返して欲しい* 型の文字を使う」。あなた は@foo[1..3]と書くことでを@fooの3つの要素からなる断片を使う事がで き、あるいは%fooの断片のためには、@foo{A,B,C}を使うことさえできま す。これはそれぞれに対して($foo[1], $foo[2], $foo[3])と($foo{A}, $foo{B}, $foo{C})を使うことと同じです。実際には、@foo[@bar]や @foo{@bar}のようにして配列の添字としてリストを使い、さらにリストを 引き出すこともできます。この両者の例では、@barはたぶん添字のリスト なのでしょう。
Perl5 では、これらのものを宣言するのは大変簡単です。例えば、
Perl4 では非常に難しいです。Perl[0..4] では入れ子のデータ構造はな
いということを思い出して下さい。それは平坦なものなので、もしあなた
がこれを実現しようとしているなら、間違った方法や、間違ったツールの
使い方をすることになるかも知れません。共通のサブスクリプトには並列
の配列を用いたらどうでしょう。
しかし、もしあなたに義務があり決められているなら、$a{'x','y','z'}
という多次元配列エミュレーションを使うことができます、さもなくば、
配列の名前の配列を作ってそれを評価することもできます。
例えば、@name に配列名のリストが入っているなら、以下のようにして、
i番目の配列の j番目の要素を取り出すことができます:
例えば:
配列の配列の配列がなければならないとちょっと簡単に仮定して見て下さ
い。あなたがすることは、上記のポインタが *name の値であるところに、
ポインタの配列を指すポインタの配列を作ることです。最も外側の配列を
普通に初期化すると、そこからポインタができあがります。例えば:
@ww = reverse @w;
@xx = reverse @x;
@yy = reverse @y;
@zz = reverse @z;
Felex Lee* の投稿した recurse.pl パッケージを良く見ることもできま
す。それは、型グロブ参照やいくつかの大変重要な魔術を使うことによっ
て、ベクタや表 (リストと連想配列) をシミュレートさせます。
C では、再帰的降下パーサや木探索のような操作のために、再帰的なデー
タ型をよく作ったものです。Perl では、これらのアルゴリズムは連想配
列を使って最適な実装がなされています。%parent という配列を取り、
$parent{$person} が その人の親の名前であるようなポインタを作りなさ
い。$parent{'adam'} が 'adam' であることを確かめて下さい。:-) ちょっ
と注意すれば、このアプローチが一般のグラフ探索アルゴリズムの実装に
も使えることができます。
この質問に対する答えは、perl5のみに通用します。われわれは、あなた
にアップグレードすべきだと言ったでしょうか? perl4での解決策も存在
しますが、あなたは今や何らかの手段でperl5を使っているだろうから、
それを答える意味は無いでしょう。いいかな?
これを行う最良の手段は、あなたの構造体を作るために連想配列を使い、
それを通常の配列(つまりリスト)か他の連想配列(つまりハッシュあるい
は表、あるいはハッシュ表)にそれを格納する、ということです。
@all = ( \%foo, \%bar, ... );
print $all[0]{'field1'};
$table{\@x} = $some_scalar; # SCARY #4
$table{[@x]} = $some_scalar; # 〃
$table{@x} = $some_scalar; # SCARY #5 (cf #0)
詳細に関しては、perlref(1)を参照のこと。
配列が並べ変えてしまうか、それとも順番を保持したいかによりますが、
いくつか可能な方法があります。
a) もし @in がソートされているなら、あなたは @out はソートされてい
るのを望むでしょう:
b) もし @in がソートされているかどうか分からなければ:
いくつかの方法があります。もし、あなたがこの問い合わせを何回も行う
ならば、そして値が順不同の文字列ならば、もっとも高速な方法はたぶん
元の配列を逆にして、そのキーが最初の配列の値であるような連想配列に
保持するようにすることです。
もし、すべての値が小さな整数値だったら、あなたは単に索引付け配列を
使うことができます。この種の配列は、より小さなメモリ領域しか使いま
せん。
もし、ご質問の値が文字列の代わりに整数であるならば、ビット文字列を
代わりに使うことでかなりたくさんのメモリ領域を節約することができま
す。
以下のようにして、ソートのサブルーチンを宣言しなければなりません。
さもなくばインライン関数を使わなければなりません。連想配列 %ary の
値で、ASCII ソートしたい、と仮定して下さい。以下のようにすることが
できます:
もしあなたが、split や unpack や パターンマッチング あるいは
substr などを通して展開した断片のような値の一部にソーティングをし
ているなら、その断片に対するそれぞれの呼び出しにおいて、あなたのソー
トルーチンの中でその処理を実行するよりはむしろ、あなたがソーティン
グを行なうそれらの一部の並列な配列を作り上げたり、この並列な配列の
添字をソートするか、あるいは新たにソートされた配列の添字を使うあな
たのオリジナルなサブスクリプトを使った方が重要でより効果的である、
ということを捕捉しておきます。@ary[@idx] と @ary{@idx} は両方とも
意味をなしているので、この方法は普通の配列と連想配列の両方で動きま
す。これの簡単な例として、ラクダ本の245ページにある「計算可能なフィー
ルドで配列をソーティングする」というのを参照して下さい。
例えば、ここに効果的な case-insensitive(大文字小文字を区別しない)
の比較があります。
しかしながら、この方法では要素数を数えることができなくても、そのた
めにあなたが使うことが *可能な* 一つの事柄を、その配列中にあるどの
要素でも決定することができます。なぜなら、“if (%table)”はその配
列中になにも格納されていなければ失敗することが保証されているからで
す。
perl4.035においては、あなたは以下のように書くことができます。
図で説明しましょう… これは %ary の表です:
次は以下のようなのを考えてみましょう:
いくつかの理由によります。ひとつには、Perlのバッククォート記法は、
シェルの中では行われるように、ダブルクォート中では挿入されない、と
いうことがあります。
以下の2つのありがちな誤りを見てみましょう。
それらの言語でプログラムを書く自然な方法は最速のPerlコードには役立
たないかも知れません。特に awk-to-perl トランスレータはやや最善の
コードを生成します。a2p のman ページを見て下さい。
Perl の強力な2つの点は、連想配列と正規表現です。正しく適用された
時に、それらは劇的にあなたのコードをスピードアップすることができま
す。それらを使うためにあなたのコードを書き直すことが、大いなる手助
けとなります。
あなたの正規表現はどれぐらい複雑ですか? {n,m} や * オペレータで深
く入れ子にされたサブ正規表現は計算するのに大変長い時間を取ります。
本当に必要がなければ () を使ってはいけません。できるなら文字を前面
に固定して下さい。
以下のような場合:
printf は単なる print よりも高くつくということを覚えておいて下さい。
必要がなければ、すべての行を split() しないで下さい。
もう一つ注目すべきことはループです。ハッシュ配列の中に全てを置くよ
り添字付きの配列を繰り返していますか? 例えば、
for $i ($[ .. $#list) {
if ($pattern eq $list[$i]) { $found++; }
}
正規表現中の変数についても見てみましょう。それは高くつきます。もし
はさまれている変数がプロセス中で変わることがなければ、正規表現をた
だ一回だけコンパイルするよう Perl に教えるため /o モディファイアを
使って下さい。以下のように:
システムコールがあってsyscall()関数を持っていれば、あなたは運が良
いです。次の質問を見て下さい。もし、あなたがPOSIX関数を使っていて、
perl5を走らせていれば、それもまた運が良いです。POSIX(3m)を参照下さ
い。しかしながら、いくつかのライブラリ関数は一筋縄では行きません。
このFAQの2.19)「C言語をPerlにリンクすることについて、どこで勉強すれ
ば良いですか?」の項目を御覧下さい。
[注: perl5の時点で、もしあなたのシステムがダイナミックローディング
をサポートしているなら、たぶんとにかく、代わりに h2xsを使うべきで
す。]
これらは、Perlのソースディレクトリにある、(かつては makelibと呼ば
れた) h2ph というスクリプトを使って、システムのインクルードファイ
ルから生成されます。これは、&SYS_getitimer のような、サブルーチン
定義を含んでいるファイルを作り、あなたの関数に引数として使うことが
できます。
これらを $SYS_getitimer のような形式に変換する方法のための h2pl サ
ブディレクトリを、Perl ソース中で、あなたは見つけたかもしれません。
これらには長所と短所の両方があります。詳細を知りたければ、そのディ
レクトリの中の notes を読んで下さい。
いずれの場合にも、これらの仕事を成すために、それをいじらなければな
らないかもしれません。それは、あなたのシステムの C のインクルード
ファイルがどのくらい奇妙か、ということに依存しています。
もしあなたが、C の構造体に触ろうとしているなら、c2phをちらっと見て
みるべきです。それは、データ構造のためのマシン非依存の perl 定義を
作るために、BSD あるいは GNU の C コンパイラによって生成されたデバッ
ガの "stab" エントリを使います。これによって、構造体の配置、型、パ
ディングあるいはサイズ、といったハードコーディングを防ぐことができ、
大いに移植性を高めます。c2phは perlと一緒に配布されています。SCOシ
ステムでは、GCC は標準で COFF のみのデバッギングサポートがあります。
ですから、あなたは GCC を DBX_DEBUGGING_INFO を定義して構築し、そ
こで c2ph を動かすために -gstabs を使わなければならないでしょう。
この手順に関する詳細なtrap と tip のために、匿名ftp で convex.com
上の /pub/perl/info/ch2ph というファイルを見て下さい。
以下のメッセージ:
YOU HAVEN'T DISABLED SET-ID SCRIPTS IN THE KERNEL YET!
FIX YOUR KERNEL, PUT A C WRAPPER AROUND THIS SCRIPT, OR USE -u AND UNDUMP!
(このカーネルではSET-IDスクリプトが禁止されていません! カーネル
を修正するか、このスクリプトにCラッパーをかぶせるか、-uを使って
アンダンプして下さい。)
は、setuidスクリプトがカーネルのバグで本質的に安全でないことによっ
て生じます。もしあなたのシステムでこのバグを修正したなら、そのこと
をPerlが知るようにPerlを再コンパイルできます。さもなければ、スクリ
プトのフルネームと共にPerlをexecだけするようなsetuidされたCプログ
ラムを作成して下さい。perldiag(1)のmanpageでは、このメッセージに付
いて以下のように述べられています。
一般的に、デッドロック状態に陥るため、これは危険な手段です。片方の
パイプの終端をファイルにくっつけるのが良いでしょう。例えば:
# あるいは、他の方法で、 cmd を実行します。
open(CMD, "| some_cmd its_args > a_file");
while ($condition) {
print CMD "some output\n";
# 消された他のコード
}
close CMD || warn "cmd exited $?";
# そしてファイルを読みます
open(FILE,"a_file");
while (<FILE>) {
デッドロックを覚悟で、双方向パイプを手でセットアップするために
fork と 2つのパイプと exec を使うことも理論的には可能です。(BSD シ
ステムでは、二つのパイプの代わりに socketpair() を使ってもよいです
が、これは移植可能ではありません。) 現在の perl リリースと一緒に配
布されている、open2 ライブラリ関数がこれをしてくれるでしょう。
adb のように書き込みと読み込みの両方を行なう物に対して、やりとりを
行なうことを、これは仮定しています。これはおそらく安全です、なぜな
らあなたは adb のようなコマンドは一度に一行を読み込み、一度に一行
を書き込むことをあなたは "知っている" からです。しかしながら、最初
に入力ストリームを全部読み込む sort や cat のようなプログラムはか
なりデッドロックの原因となりがちです。
stderr のために、これを同様に扱う open3.pl ライブラリもあります。
外部コマンドを実行する3つの基本的な方法があります。
while (<CMD>) {
if (s/^STDOUT://) {
print "line from stdout: ", $_;
} else {
print "line from stderr: ", $_;
}
}
perl5の製品では&open2とマージされていた&open3ルーチンが、あそこに
はあります。
これらの実行文:
もしあなたが TOPIPE に書き込みを行なっているなら、子供が早く終了す
るか、あるいは実行されなかった時に SIGPIPE を受けとるでしょう。も
しあなたが FROMPIPE から読み込んでいるなら、何が起こったか見るため
に、close() を調べる必要があります。
もし、パイプがバッファリングをしてあなたに余裕を与えるよりも、早く
答が欲しいなら、以下のようにすることができます:
もしあなたが、SIGPIPE をとらえたくないなら、書き込み可能なパイプを
使って似たようなトリックを演じることができます。
いくつかのstdioはerrorがセットされ、eofフラグがクリアされる必要が
あるからです。
次のようにシークポインタの周辺を保持して、それからそこに行くことを
試して下さい。
Perl はチルダを展開しません -- シェル (そうです、いくつかのシェル)
は展開します。その伝統的な要求では以下のようにできるべきです:
Larry がファイル名グロブを内蔵させず、(perl)システムでは csh を使っ
ているということを、あなたが *知って* いるなら、以下のようにすること
ができます。
よりよい方法は以下のように、自分自身で変換をすることです:
Larryの公式の回答は、それをperlのフィルタを通してシェルに送る、と
いうことですが、他の方法がtchrist@perl.comで知られています。世間一般に信じられているのとは反対に、Tom Christiansenは実在の人物ではありません。彼は実際にはコロラド大学の卒業生によって書かれた高度な人工知能実験の産物なのです。彼が実行するようにプログラムされた初期の課題のいくつかは次のようなものでした。
*comp.lang.perl.misc を監視し、どんな質問がどんな頻度で訊ねられているか統計をとり、蓄積された回答の中から返答すること。Tomのプログラミングが成長して、このつまらない課題には不釣り合いになったせいか、これはフロリダ大学の在校生に課されるようになりました。結局、フロリダ大学の学生がとにかくもドキュメンテーション以上のことをする事はできないことを我々全員が知ることとなりました。思いのほか、その在校生はプロのシステム管理者となり、そしてperlプログラマー、現在は「perlプログラミング」の改訂版の著者となっています。
(これはジョークです!! この件に関する問い合わせはお断りします!)
実際には、自動的で機械的なトランスレータは存在しません。仮にあった
としても、多くの外部プログラムが呼び出されるため、得られるものはた
いして多くないでしょう。これは、Cへの機械的な変換の場合と同様の問
題です
もちろん、ソケットを使って直接接続することができます。さもなくば、
pty 上でセッションを動かすこともできます。どちらの場合にも perl の
ソースと一緒に配布されている、Randal の chat2 パッケージを使うこと
ができるでしょう。それは、Don Libes の expect パッケージが行なって
いるのと同じ問題領域に多くが集中しています。ftp セッションの管理と、
chat2の使い方の例は、convex.com の /pub/perl/scripts/ftp-chat2.shar
で見つけられます。
注意 : chat2 は例によってのみ文書化されています。そして、System V
システムでは動かないかもしれません。そして、そのネットワーキングの
思想や仮想端末の中に潜在的なマシン依存の部分があります。4.21 の質問の "なぜ私のソケットプログラムは System V (Solaris)上で動かないのでしょうか?" も見て下さい。
Randal は、天気レポートを得るために telnet プロトコルを扱うソケットセッションの例を示すコードも持っています。
Gene Spafford* は ftp を行なう時に助けになる、良い ftpライブラリパッ
ケージを持っています。
perl4.036のように、内部処理ではなくシェルに送出されるグロビングに
はある程度の総計があります。以下のコード(だいたい“chmod 0644”を
エミュレートする)
以下のようなものがそれをするコードです:
もしtcshを/bin/cshとしてインストールしていたなら、この問題は決して
起こりません。
解決法は、自分自身の中に、探すためのコールを置くことだと、Larry は
言っています。はじめに、
もしそれが動かないのであれば (あなたのシステムの stdio の実装に依存
しますが) 以下のようにする必要があります。
一般的には、もしあなたがこれをする必要があるなら、あなたはお粗末な
プログラミングの練習をしているか、自分のために猜疑的になりすぎてい
ます。もしコマンドラインに入力したパスワードを隠すためにこれをする
必要があるなら、そのプログラムを書き直して、パスワードをファイルか
ら、もしくはプロンプトを出して入力させるようにしなさい(質問 4.24参
照)。コマンドラインにパスワードをタイプするのは、あなたの肩越しに
それを見ることができたりするわけですから、本質的にセキュリティ上の
問題があります。
もし、本当にコマンドラインを上書きしてそれを隠したいのならば、“$0”
変数に代入することができます。例:
open(PS, "ps |") || die "Can't PS: $!";
while (<PS>) {
next unless m/$$/;
print
}
最も制限された意味では、それは "できません"。しかし、それを許すシェ
ルの特別な魔法があります。comp.unix.shell をチェックし、
comp.unix.questions の FAQ を読むことを、私はお薦めします。
perl が起動される時、あなたは子プロセスを生成しています。Unix シス
テムの設計思想によって、子供達はその親のシェルに影響を与えることは
永遠にできません。
子プロセスが生成される時、その親の環境のコピーを継承します (変数、
カレントディレクトリ、等)。子供がこの環境を変える時は、コピーを変
えるのであって、そのオリジナルを変えるのではありません。つまり親は
影響を受けません。
もしあなたが、perl スクリプトの中で親を変更しなければならないなら、
それを シェルスクリプトあるいは Cシェルスクリプトで書いてみて、
". スクリプト" あるいは "source スクリプト" (それぞれ、sh, Csh) を
使ってみて下さい。
もしファイルハンドル用に変数を使おうとしているなら、いくつかの問題
に直面するでしょう。ここにはperlにおけるダサい部分が現れています。
つまり、ファイルハンドルは、ファイルハンドル以外がそうであるような
「一級市民」ではありません。ただしファイルハンドルは時々は、本当に
「一級市民」です。
もちろん、以下のようにすることは正しい:
単純な修正方法は、それを完全に修飾して渡すようにすることです。
sub printit {
my $fh = shift;
my $package = (caller)[0];
$fh =~ s/^[^':]+$/$package::$&/;
while (<$fh>) {
print;
}
}
printit(*Some_Handle);
sub printit {
local *FH = shift;
while (<FH>) {
print;
}
}
しかしながら、これはこの関数の内側で型グロブを使う必要がない、とい
うことを外に出してしまう。これは以下のようにもはたらく:
printit(*Some_Handle);
sub printit {
my $fh = shift;
while (<$fh>) {
print;
}
printit(\*Some_Handle);
sub printit {
my $fh = shift;
while (<$fh>) {
print;
}
}
私はあなたがCODE 1あるいはむしろCODE 2を使う必要があると考えてきま
したが、どうやらあなたは同様にCODE 3と4から同様に逃れることができ
ます。このことは、それによりCODE 2でやっているような型グロブへの代
入を避けることができるため、好ましいことです。しかし少し危険ではあ
ります。
他にCODE 1のちょっとした問題としては、もしあなたが厳密にサブルーチ
ンを使うとしたら、あなたが得られるであろう厳密なサブルーチンをする
ことができるようにはならない、ということです。
その代わりに、あなたは'main::Some_Handle'で渡す必要がありますが、
あなたの関数の中に降りてしまい、あなたは厳密な参照で吹き飛ばされて
しまいます。
なぜならあなたは文字列やシンボルを使っているだろうからです。だから
本当は、最上の方法は型グロブ(あるいは時おりは同じ物への参照)を受け
渡す事です。
通常、perl はファイル名中の最後の空白を無視し、何か特別なものを意
味する、ある文字で始まる (あるいは、"|" で終る) ものを解釈します。
これを防ぐには、以下のようなルーチンを使った方が良い。それはフルパ
ス名でないものを、明示的な相対パス名にし、perl がそれを独立したま
まにしておけるようにファイル名の末尾に null バイトを付加します。
次の関数は、setuidの実行によって、自動的に有効になる汚染のチェック
が変数を“嫌う”かどうかを、リーズナブルに示すことができます。
ホームページへ戻る。
5.2) 多次元配列や再帰的なデータタイプはどうやって作ったら良いのですか?
@A = (
[ 'ww' .. 'xx' ],
[ 'xx' .. 'yy' ],
[ 'yy' .. 'zz' ],
[ 'zz' .. 'zzz' ],
);
"yy" を取り出すのは $A[2]->[0] という参照です。これらはテーブルを
用いた入れ子になっていてもよいです:
%T = (
key0, { k0, v0, k1, v1 },
key1, { k2, v2, k3, v3 },
key2, { k2, v2, k3, [ 'a' .. 'z' ] },
);
これは、'd' を取り出すために $T{key2}->{k3}->[3] という参照を許し
ています。
$ary = $name[$i];
$val = eval "\$$ary[$j]";
一行で書くなら、
$val = eval "\$$name[$i][\$j]";
*name の値の配列を作るために型グロブを使うこともできます。こちらの
方が eval よりもより効果的でしょう。ここに @name はポインタのリス
トを持っています。一時的な変数を用いることによって、そのポインタを
逆参照しなければならないでしょう。
{ local(*ary) = $name[$i]; $val = $ary[$j]; }
実際、自由に入れ子のデータ構造を作るために、これらの方法を使うこと
はできます。本当に、この種のことがへたに行き過ぎにする以外にありま
せんが、表記法的にめんどうになってしまいます。
@w = ( 'ww' .. 'xx' );
@x = ( 'xx' .. 'yy' );
@y = ( 'yy' .. 'zz' );
@z = ( 'zz' .. 'zzz' );
そして、これらへのポインタの配列をいくつか作ります:
@A = ( *w, *x, *y, *z );
@B = ( *ww, *xx, *yy, *zz );
そして最後に、これらの配列へのポインタの配列を作ります:
@AAA = ( *A, *B );
AAA[i][j][k] というように、ある要素にアクセスするためには、以下の
ようにしなければいけません:
local(*foo) = $AAA[$i];
local(*bar) = $foo[$j];
$answer = $bar[$k];
連想配列での似たような操作も可能です。
5.3) 様々なデータ型を含んでいる構造体の配列を作るにはどうすればいいのですか?
%foo = (
'field1' => "value1",
'field2' => "value2",
'field3' => "value3",
...
);
...
あるいは同じことを以下のようにもできます。
@all = (
{
'field1' => "value1",
'field2' => "value2",
'field3' => "value3",
...
},
{
'field1' => "value1",
'field2' => "value2",
'field3' => "value3",
...
},
...
)
リストの連想配列が欲しければ、以下のように代入すべきである、という
ことに注意しましょう。
$t{$value} = [ @bar ];
そして、連想配列のリストならこうなります。
%{$a[$i]} = %old;
しばらく、これらを勉強してください。我々は今度のFAQでは、これらを
完全に説明します。
$table{'some key'} = @big_list_o_stuff; # SCARY #0
$table{'some key'} = \@big_list_o_stuff; # SCARY #1
@$table{'some key'} = @big_list_o_stuff; # SCARY #2
@{$table{'some key'}} = @big_list_o_stuff; # 不愉快なRANDALIAのコードだ
$table{'some key'} = [ @big_list_o_stuff ]; # 同様にRANDALIAのコードだがこれはナイスである
そしてこれをいじりまわしている間に次を見ておいて下さい。
$table{"051"} = $some_scalar; # SCARY #3
$table{"0x51"} = $some_scalar; # 〃
$table{051} = $some_scalar; # 〃
$table{0x51} = $some_scalar; # 〃
$table{51} = $some_scalar; # 良いだろう
$table{"51"} = $some_scalar; # もっと良い
5.4) 配列中のユニークな要素を抜き出すにはどうしたらよいのですか?
$prev = 'nonesuch';
@out = grep($_ ne $prev && (($prev) = $_), @in);
これは余分なメモリを使わず、隣接した同じものだけを取り除く uniq
のふるまいをシミュレートするのでよいです。
undef %saw;
@out = grep(!$saw{$_}++, @in);
c) (b) のようですが、@in には小さな整数だけが入っています:
@out = grep(!$saw[$_]++, @in);
d) ループや grep を使わないで (b)をする方法:
undef %saw;
@saw{@in} = ();
@out = sort keys %saw; # remove sort if undesired
e) (d) のようですが、@in には小さな正の整数だけが入っています:
undef @ary;
@ary[@in] = @in;
@out = sort @ary;
5.5) 配列に、ある要素が含まれているかどうかを知るにはどうすればいいのですか?
@blues = ('turquoise', 'teal', 'lapis lazuli');
undef %is_blue;
for (@blues) { $is_blue{$_} = 1; }
こうすれば、$is_blue{$some_color}とすることで青かどうかを知ること
が出来ます。プログラムの最初の場所で、連想配列に全てのbluesの値を
設定しておくのは良い考えです。
@primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
undef @is_tiny_prime;
for (@primes) { $is_tiny_prime[$_] = 1; }
こうすれば、$is_tiny_prime[$some_number]とすることでチェックできま
す。
@articles = ( 1..10, 150..2000, 2017 );
undef $read;
grep (vec($read,$_,1) = 1, @articles);
こうするとvec($read,$n,1)は$nの値によっては真となります。
5.6) 連想配列を key の代わりに value でソートするにはどうしたらよいのですか?
foreach $key (sort by_value keys %ary) {
print $key, '=', $ary{$key}, "\n";
}
sub by_value { $ary{$a} cmp $ary{$b}; }
もし、数値の降順でソートしたいなら、以下のようにできます。
sub by_value { $ary{$b} <=> $ary{$a}; }
もし、perl4 の比較的最近のパッチレベルか、perl5が走っているなら、
以下のように、ソート関数をインラインにすることもできます:
foreach $key ( sort { $ary{$b} <=> $ary{$a} } keys %ary ) {
print $key, '=', $ary{$key}, "\n";
}
もし、中に配列名が hard-wired でない関数にしたいなら、以下のように
することができます。
foreach $key (&sort_by_value(*ary)) {
print $key, '=', $ary{$key}, "\n";
}
sub sort_by_value {
local(*x) = @_;
sub _by_value { $x{$a} cmp $x{$b}; }
sort _by_value keys %x;
}
もし、アルファベットや数値のソートのどちらにもしたくなければ、ビル
トインの符号付比較演算子の "cmp" や "<=>" に頼る代わりに、自分自身
によるロジックのコードを書かなければならないでしょう。
@idx = ();
for (@data) { push (@idx, "\U$_") }
@sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0..$#data];
5.7) 連想配列の中にいくつのエントリがあるかを知るにはどうすればいいのですか?
配列@foobarの要素数は、スカラーコンテキストの中で使われた場合単に
@foobarとして得られますが、このことから類推して連想配列中の要素数
を理解することはできないでしょう。なぜならスカラーコンテキスト中の
%foobarは割り当てられたエントリ数に対する使用済のエントリ数の比率
を(文字列として)返すからです。例えば、scalar(%ENV)は“20/32”とか
を返すでしょう。perlは理論的には数え続けることができますが、dbmファ
イルに結びつけられた連想配列においては失敗するでしょう。
$count = keys %ARRAY;
keys()はスカラーコンテキスト中で使われた場合、keysそれ自身ではなく
keysの個数を返します。
5.8) %arrays に対する "delete" と "undef" との違いは何ですか?
keys values
+------+------+
| a | 3 |
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
これらは以下の状態を持っています。
$ary{'a'} is true
$ary{'d'} is false
defined $ary{'d'} is true
defined $ary{'a'} is true
exists $ary{'a'} is true (perl5 のみ)
grep ($_ eq 'a', keys %ary) is true
もしあなたが以下のように言うとすると
undef $ary{'a'}
あなたの表は以下のように読めます:
keys values
+------+------+
| a | undef|
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
さらにこれらは以下のようなを状態を持っています。大文字の部分が変わっ
ています:
$ary{'a'} is FALSE
$ary{'d'} is false
defined $ary{'d'} is true
defined $ary{'a'} is FALSE
exists $ary{'a'} is true (perl5 only)
grep ($_ eq 'a', keys %ary) is true
最後の2つに注目して下さい: 未定義の値ですが、定義されているキーで
す!
delete $ary{'a'}
表は以下のように読めます:
keys values
+------+------+
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
そして、これらは以下のような状態を持っています。大文字の部分が変わっ
ています。
$ary{'a'} is false
$ary{'d'} is false
defined $ary{'d'} is true
defined $ary{'a'} is false
exists $ary{'a'} is FALSE (perl5 のみ)
grep ($_ eq 'a', keys %ary) is FALSE
見て下さい。エントリ全体がどこかに行ってしまいました。
5.9) なぜバッククォート記法(backticks)はシェルの中でと同様に動かないのですか?
$foo = "$bar is `wc $file`"; # 駄目
これは次のようにすべきです。
$foo = "$bar is " . `wc $file`;
しかし、あなたはたぶん期待しない余分な改行を得ることになるでしょう。
以下は期待するようには動きません。
$back = `pwd`; chdir($somewhere); chdir($back); # 駄目
なぜなら、Perlのバッククォート記法では、末尾についている、あるいは
埋め込まれている改行文字を自動的には取り除いてくれないからです。
chop()関数が文字列の最後の文字を取り除いてくれます。従ってさっきの
は以下のようにすべきです。
chop($back = `pwd`); chdir($somewhere); chdir($back);
また、シェル中ではシングルクォートで括ることで変数は保護されますが、
Perlではドル記号($)をエスケープしなければならない、ということに気
を付けなければいけません。
Shell: foo=`cmd 'safe $dollar'`
Perl: $foo=`cmd 'safe \$dollar'`;
5.10) perlに変換した awk/sed/sh スクリプトは何故前より遅く動くのですか?
next unless /^.*%.*$/;
以下の等価な表現よりも遅く走ります:
next unless /%/;
捕捉しますと、以下のような場合:
next if /Mon/;
next if /Tue/;
next if /Wed/;
next if /Thu/;
next if /Fri/;
以下の表現より速く走ります:
next if /Mon/ || /Tue/ || /Wed/ || /Thu/ || /Fri/;
順に、以下のより速く走ります:
next if /Mon|Tue|Wed|Thu|Fri/;
以下の表現よりさらに速く走ります:
next if /(Mon|Tue|Wed|Thu|Fri)/;
/foo/ とするところで、/^.*foo.*$/ を使う必要はありません。
@list = ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stv');
まず第一に、サブスクリプトを使う代わりに Perl の foreach 機構 を使
う方がより速いでしょう。
foreach $elt (@list) {
if ($pattern eq $elt) { $found++; }
}
その上よいことに、以下のように、連想配列中に全部のものを置くことに
よって 劇的にスピードアップすることができます:
%list = ('abc', 1, 'def', 1, 'ghi', 1, 'jkl', 1,
'mno', 1, 'pqr', 1, 'stv', 1 );
$found += $list{$pattern};
(しかし、入力ループの外で %list の割り当てをおいて下さい。)
for $i (1..100) {
if (/$foo/o) {
&some_func($i);
}
}
最後に、もし比較したいリストの中にパターンの山があるなら、以下のよ
うにする代わりに:
@pats = ('_get.*', 'bogus', '_read', '.*exit', '_write');
foreach $pat (@pats) {
if ( $name =~ /^$pat$/ ) {
&some_func();
last;
}
}
もし、まずコードを作りその後それを評価するなら、より速いでしょう。
例えば:
@pats = ('_get.*', 'bogus', '_read', '.*exit', '_write');
$code = <<EOS
while (<>) {
study;
EOS
foreach $pat (@pats) {
$code .= <<EOS
if ( /^$pat\$/ ) {
&some_func();
next;
}
EOS
}
$code .= "}\n";
print $code if $debugging;
eval $code;
5.11) 私のシステム固有のC関数を呼ぶにはどうすればいいのですか?
5.12) ioctl() や syscall() を使うのにインクルードファイルをどこから得
るのですか? [h2ph]
5.13) setuidされたperlスクリプトはなぜカーネルの問題について苦情を言うのですか?
YOU HAVEN'T DISABLED SET-ID SCRIPTS IN THE KERNEL YET!
(このカーネルでは、SET-IDスクリプトが禁止されていません!)
(F) でもたぶん禁止することはできないでしょう。というのもあな
たはカーネルソースは持っていないでしょうし、ベンダもおそらく
あなたの要望に関心を持ってはくれないでしょうからです。一番良
いのは、スクリプトにsetuid Cラッパーを被せるために、egディレ
クトリ配下にあるwrapsuidスクリプトを使ってみることです。
5.14) コマンドからとコマンドへの双方向のパイプはどうやってオープンする
のですか?
# 最初に a_file の中に some_cmd の入力を書きます、
open(CMD, "some_cmd its_args < a_file |");
while (<CMD>) {
もし pty があるなら、デッドロック問題を防ぐために pty上でコマンド
を実行するようにすることもできます。これをする方法は、配布されてい
るライブラリの中の chat2.pl パッケージを見て下さい。
5.15) 外部のコマンドからのSTDERRはどうやれば捉えられますか?
system $cmd;
$output = `$cmd`;
open (PIPE, "cmd |");
一つ目の場合、リダイレクトされない限り、STDOUTとSTDERRはこれらのス
クリプト版と同様に同じ場所になります。あなたは常にこれらを好きな所
に出力させることができますし、systemからリターンした時に読み返すこ
とが出来ます。二番目と三番目の場合では、あなたはあなたのコマンドの
STDOUT *のみ* を読むことができます。もしSTDOUTとSTDERRがマージされ
て欲しいなら、以下のようにシェルのファイルディスクリプタ・リダイレ
クションをSTDERRをSTDOUTにdupするために用いることができます。
$output = `$cmd 2>&1`;
open (PIPE, "cmd 2>&1 |");
他に、STDERRをファイルにして実行し、後でそのファイルを読むこともで
きます。
$output = `$cmd 2>&1`;
open (PIPE, "cmd 2>&1 |");
STDERRをあなたのperlプログラムのSTDOUTのdupにするために単にオープ
ンすることは *できない* ことに注意して下さい。また、リダイレクト
するためにシェルを呼ぶことも避けて下さい。以下は動きません。
open(STDERR, ">&STDOUT");
$alloutput = `cmd args`; # stderr still escapes
以下に、これら両方から読む方法と、それぞれの行がどちらのディスクリ
プタから得ているのかを知る方法を示します。このトリックはSTDOUTだけ
をsedに通してパイプすることです。そしてその行にマークをつけ、マー
ジされたSTDOUT/STDERRストリームに戻して送ってやります。そうすれば
あなたのPerlプログラムはそこから同時に読むことができるでしょう。
open (CMD,
"(cmd args | sed 's/^/STDOUT:/') 2>&1 |");
あなたに、バッククオートの中ではcshではなくBourneシェルのリダイレ
クト記法を使わなければいけない、とうことを言っておきます。perlにお
けるsystem()とバッククオートとパイプオープンのすべてがBourneシェル
を使っている、ということがいかに好運であるか、ということの詳細を知
りたいなら、convex.comから/pub/csh.whynotと呼ばれるファイルを入手
して見て下さい。そうすれば、perlのシェルへのインターフェースが
Bourneシェルであったことに喜びを感じるでしょう。
5.16) パイプのオープンに失敗した時、何故 open() はエラーを返さないのですか?
open(TOPIPE, "|bogus_command") || die ...
open(FROMPIPE, "bogus_command|") || die ...
は、bogus_command の欠如のためだけでは失敗することはありません。も
しそれらを実行するための fork が失敗した時だけ、それらは失敗するで
しょう。それはめったにない問題です。
$kid = open (PIPE, "bogus_command |"); # XXX: check defined($kid)
(kill 0, $kid) || die "bogus_command failed";
bogus_command に、シェルのメタキャラクタがないならこれは動きますが、
メタキャラクタが入っていると、kill 0 の前にシェルは終了してしまう
かもしれません。あなたは、いつでも遅れを入れることができます。
$kid = open (PIPE, "bogus_command </dev/null |");
sleep 1;
(kill 0, $kid) || die "bogus_command failed";
しかしこれは時々望ましくありません。あるイベントの中では、正しいふ
るまいを保証できません。しかし何もないよりはすこしはいいかもしれま
せん。
5.##) 外部のプログラムからの終了ステータスを得るにはどうしたらよいですか?
最後のバッククォートコマンドのステータスを保持する組み込みの変数
$? を Perl は供給しています: 以下はこの変数に関する perlvar ページ
に記述されていることです。
$? 最後のパイプ close、バッククォート(``) コマンド、または
system() 演算子によって返されたステータスです。これは
wait() システムコールによって返されるステータスであり、
サブプロセスの exit の値 は実際は ($? >> 8) であることを
に気をつけて下さい。そのため、多くのシステム上では、
$? & 255 はシグナルか、死んだプロセスか、コアダンプがある
か、のどれかを与えます。(暗記法: sh や ksh に似ている)
5.17) なぜ私のスクリプトは、^D(EOF)を与えた後のSTDINから読み込む事ができないのですか?
$where = tell(LOG);
seek(LOG, $where, 0);
もしこれが動かなければ、そのファイルの別の部分にシークし、何か読ん
だあと、シークで戻って来るようにしてみて下さい。もしそれすらもうま
く行かなければ、あなたのstdioパッケージを使うのはあきらめてsysread
を使いましょう。Perlからstdioのclearerr()を呼び出すことはできませ
ん。そのために、もしシグナルハンドラからEINTRを得たら不運ですね。
ttyのためには、最初からsysreadを使うのがベストでしょう。
5.18) ファイルネーム中のチルダ(~)はどうやったら変換できますか?
open(FILE, "~/dir1/file1");
open(FILE, "~tchrist/dir1/file1");
これは動きません。(そしてあなたはそれを知らない、なぜならあなたは、
"|| die" 節なしで、システムコールを使っています :-)
$filename = <~tchrist/dir1/file1>
しかし、これはかなり問題が多いです。
$filename =~ s#^~(\w+)(/.*)?$#(getpwnam($1))[7].$2#e;
エラー状態を調べ、簡潔な ~/blah という記法が扱え、キャッシュを使った
検索を行なうような、より強力で効果的なバージョンは、(将来の)拡張とし
て全く妥当でしょう。
5.19) 私のシェルスクリプトをperlに変換するにはどうすればいいのですか?
*シェルプログラムをperlに変換すること。
あなたはまだexec()群による泥沼へはまり込む傾向があります。
あなたはデータの流れとアルゴリズムを解析し、速度向上のための最適化を再考しなければなりません。
データフローとアルゴリズムを分析し最適なスピードアップができるよう再考すべきでしょう。力任せのアプローチと記録されたアプローチ間の速度差は、1、2いや3等級の差となるのは、稀な事ではありません。
5.20) telnet や ftp を走らせるために perl を使うことができますか?
5.21) <*>を使った時、たまに"Argument list to long"(引数リストが長すぎる)と言われるのはなぜですか?
while (<*>) {
chmod 0644, $_;
}
は、以下と等価です。
open(FOO, "echo * | tr -s ' \t\r\f' '\\012\\012\\012\\012'|");
while (<FOO>) {
chop;
chmod 0644, $_;
}
グロビングの処理がPerlに組み込まれるまでは、あなたはずっとグロビン
グではない形式を使う必要があるでしょう。
opendir(DIR,'.');
chmod 0644, grep(/\.c$/, readdir(DIR));
closedir(DIR);
これは“Programming Perl”の78ページ[訳注: 日本語版「Perlプログラ
ミング」では、102ページ]からそのまま持って来た例です。
5.22) perl で "tail -f" を実行するにはどうしたらよいですか?
seek(GWFILE, 0, 1);
seek(GWFILE, 0, 1); という表現は、現在の場所を変えず、そのハンドル
の end-of-file(ファイルの終端) の状態をクリアします。だから次の
<GWFILE> は Perl に再び何か読ませようとします。
for (;;) {
for ($curpos = tell(GWFILE); $_ = <GWFILE> $curpos = tell(GWFILE)) {
# いくつかの要素を探し、それをファイルに入れる。
}
sleep for a while
seek(GWFILE, $curpos, 0);
}
5.23) "ps"のようなプログラムから、perlのコマンドラインを隠す方法はありますか?
#!/usr/local/bin/perl
$0 = "Hidden from prying eyes";
Solaris 2.XのようないくつかのOSは、プログラムのスタック領域からで
はなく、カーネル情報から直接読み出すことに注意すべきでしょう。従っ
てその場合はコマンドラインを変更する事は許されません。
5.24) 私は perl スクリプトの中でディレクトリや環境を変えました。スクリ
プトを終了した時にその変更が見えなくなったのはどういうことですか?
私の変更を見えるようにするには、どうしたらよいですか?
5.25) ファイルハンドルを関数に渡したり、ファイルハンドルのリストを作ることはどうすればできますか?
$fh = "/some/path";
open($fh, "< $fh");
print $fh "string\n";
しかし、以下のようにするとまだ面倒が生じるだろう:
$fharray[$i] = "/some/path";
open($fharray[$i], "< $fharray[$i]");
print $fharray[$i] "stuff\n";
以下のようにもできる:
$tmp_fh = $fharray[$i];
print $tmp_fh "stuff\n";
しかしこれはOKです:
print { $fharray[$i] } "stuff\n";
ファイルハンドルを受け渡すにはだいたい4つのやりかたがあります。皆が試みる
方法は単に文字列として受け渡すことです。
printit(Some_Handle);
不幸にもこれはうまく動きません。なぜならprintit()が実行されている
パッケージは、実際はハンドルがオープンされたパッケージではないから
です。
printit(main::Some_Handle);
なぜなら、もしそうしなければ、その関数はなんらかの次のような狂った
ことをまさにしようとしていることになる:
CODE 1:
よりよい解決方法は、型グロブを代わりに渡すことです。
CODE 2:
CODE 3:
これが動いてさえ、この場合はあなたの参照の恩恵によって、一つのオブ
ジェクトを作りたい。
CODE 4:
5.26) 先頭に ">" があるファイルや、末尾に空白のあるファイルはどうやっ
たらオープンすることができますか?
sub safe_filename {
local($_) = shift;
m#^/# ? "$_\0" : "./$_\0";
}
$fn = &safe_filename("<<<something really wicked ");
open(FH, "> $fn") || "couldn't open $fn: $!";
5.27) もし変数が"汚染されて"いたなら、どうすればそれを知る事ができますか?
sub tainted {
! eval { join('',@_), kill 0; 1; };
}
このコードで特筆すべき点は、システムコールを一切行わないということです。
ご意見、ご要望は、
電子メールまたは
投稿にお願い致します。