VimでMarkdownをカンタンにHTML化する

note103.hateblo.jp

以前に上の記事を書いたのは、もう1年以上前のこと。だから、というわけでもないのだけど、少し方法がアップデートされているので差分を記してみておきます。

まず、以前の方法を簡単にまとめると、

  1. Markdown記法で何か書く。
  2. 自作の md2html.pl で変換。
  3. HTML化された文書の出来上がり。

というもので、シェル上のコマンドで言うと、こんな感じ*1

$ perl md2html.pl in.md > out.html

それがその後、Perl界の先輩である @karupanerura さんから、それだったらこう書けるよ! と教えて頂きまして。

$ cat in.md | Markdown.pl > out.html

つまり、わざわざそういうスクリプトを使わなくても、Perlの Text::Markdown モジュールをインストール済みなら Markdown.pl コマンドを使えるので、それを通せば変換できるよ、と。

それで、その後はありがたく1年ほどその方法にて変換作業をしていたのですが、ふとこれ、Vim のほうにショートカットを仕込めるはずだなあ、と思いまして。

以下のようにマッピングしてみました。

nnoremap <Leader>mh :%! Markdown.pl %<CR>

使っているところ。

f:id:note103:20160605153705g:plain

変換後に、Vimプラグインの previm を使ってブラウザで確認しています。

で、とりあえずこれだけでもOKといえばOKなのですが、僕の性格的にはソースのファイル(バッファ)を上書きしてしまうのは結構抵抗があるので、変換後のHTMLは別のバッファに保存して、元のファイルは 残しておけるように以下のような関数を vimrc に設定してみました。

function! s:Markdown2HTML()
  execute ":%y"
  execute ":new"
  execute ":0put +"
  execute ":w ".strftime('%Y-%m-%d-%H-%M').".html"
  execute ":%! Markdown.pl %"
  execute ":w"
endfunction
nnoremap <Leader>mh :<C-u>call <SID>Markdown2HTML()<CR><CR>

ん〜む……なんだかむっちゃ冗長感溢れていますが、リファクタリングはまた学習の進度に沿ってということで……。
とりあえずやっていることとしては、

":%y" <= バッファ全体をコピー
":new" <= 新規バッファを水平分割で作成
":0put +" <= コピーしておいたテキストを新規バッファへペースト
":w ".strftime('%Y-%m-%d-%H-%M').".html" <= 新規バッファを現在日時分秒を元にしたファイル名で保存(この時はまだMarkdown書式)
":%! Markdown.pl %" <= 保存したバッファでテキストをMarkdownからHTMLへ変換
":w" <= HTMLファイルを保存

という感じです。

動いている様子は、こんな。

f:id:note103:20160605153635g:plain

便利!!

成果

さて上記でサンプル的に使ったMarkdownファイルですが、じつは本日パブリッシュしたこちらの記事でした。

perl-entrance.blog.jp

冒頭に示した記事でも書いたように、Perl入学式では各回のレポートを参加したサポーターさんに書いてもらって、その校正&ブログUPは可能な範囲でぼくが担当したりしています。

この新たに手に入れた Vim & Perl ワザを使って、今後ますます同ブログをカンタンに更新していけそうです。

*1:厳密にはちょっと当時の設定と違うのだけど、実際の構造を把握しやすいように少しアレンジした。

Perlで配列を1行ずつカンタンに出力したい

Perlの小ネタシリーズ。

と言いつつ、文章はそこそこ長いです。

課題

さっそくですが、以下のような配列があるとして、

my @colors = qw/red blue orange green yellow/;

このまま出力すると、

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my @colors = qw/red blue orange green yellow/;

say @colors;

こんな感じになる。

redblueorangegreenyellow

これだとなんだかわからないので、要素間に改行を入れて、以下のように配列の中身を見やすくしたい、ということがよくある。

red
blue
orange
green
yellow

そのための方法として、初心者的に最初に思いつくのは、これとか。

for (@colors) {
    say $_;
}

あるいはこれとか。

say join "\n", @colors;

あるいは最初のやつを後置にするとか。

say $_ for (@colors);

で、これまではこういう場合、一番よく使うのは2番めの join を使う方法だったのだけど、昨日ふと思ったのが「いや、面倒すぎるだろ」ということで。

たしかに一回一回はさほどのタイプ数でもないのだけど、チリも積もればというやつで、これを一生繰り返し続けたら一体何タイプになるのだ、と。

というか、その join 方式にしても地味にダブルクオートやバックスラッシュなども入っていて、案外コストは高いとも言えるし。
今まで見てみぬふりをしてきたけど、これはちょっと改善しないとマズイよなあ……と。

で、普通こういうときというのはすでに誰かが同じことを考えていて、かつすでに解決策を講じてくれていて、さらにはすでにその方法を広く公開・共有してくれていたりするのだけど、寡聞にしてぼくはその辺のことをよく知らない。
皆さん、どうしてるんでしょうね? こういうの。

とりあえずなんとなーく、おぼろげな記憶というか印象として、あれ、こういうモジュールあったような……と思って、List::Util というのを当たってみたのだけど、
List::Util - search.cpan.org

ん〜、機能はむっちゃ充実しているのだけど、そして今あらためて見ながら「うわ、こんな機能あったんだ…… unique とか今度からはこれ使おう」などと思ったのだけど、当の求める機能はなさそう。(たぶん)

ちなみに、同モジュールについては以下に日本語での平易な解説があって、大変参考になりました。

検討

さて、ではあらためて、それならどうすればいいのか。

もしこれがターミナルから使うbashのコマンドとかだったら、これまでに結構似たようなことをやってきているので(ディレクトリ移動やファイルのリネームをするツールなど)、その応用でいけそうな気もするのだけど、今回はちょっと毛色が違うのでどうしたらいいのだか……。

それで、パッと思ったのはとりあえず自分で専用のモジュールを書いて、毎回それを use などで読み込んで使えるようにする、ということかなと。

もちろん、というか特定のモジュール化をしてしまうと、それを使ったコードは汎用性が低くなるというか、他の人とコードの共有をしづらくなってしまう可能性が高まるのだけど、ここでの目的はあくまで「配列を手軽に改行しながら表示したい!」という、個人的かつ一時的なデバッグ的な欲望にもとづくものなので、その限りにおいては「自分しか使わないモジュール」に依拠してしまってもとりあえず構わないかな、と。

ただし、それでも残る懸念としては、今回のモジュールは特定のファイル内でのみ使うものではなくて、コンピュータ内のどこでPerlを書いても使えるようにしたいという前提があるので、たぶん例の @INC 問題でハマる可能性がかなり高そう……

ということで、これまで幾度となく挑んでは敗れてきた @INC 系の情報に関してもあちこち調べて回った末、とりあえず至った結論は以下のようなものでした。
時系列で記してみます。

試行

とりあえずモジュール書く

まず、今回求めている機能は非常にシンプルなもので、こんな感じ。

sub foo {
    my @foo = @_;
    for (@foo) {
        say $_;
    }
}

んで、使うときには、

foo @colors;

とかする。だいぶシンプル!

名前を決める

さてそれで、そのとりあえず「foo」としているところ、つまりモジュール名、および関数名をどうするか……

命名の条件としては、そもそもタイプ数を少なくしたいので数文字で固めたい。
次に、内容をある程度想起させるもので。

今回の場合、配列に改行を挟みたいということなので、キャリッジリターンこと「cr」とか。あるいは「改行」で辞書を見ると「newline」とあるので「nl」とか?

ん〜、しかし「cr」は若干打ちづらいし、「nl」はそれよりは打ちやすいけど、見栄えがパッとしない……ああ、じゃあ「n」とか?

ということで、モジュールはこんな感じに。

n.pm
package n;
use strict;
use warnings;
use feature 'say';
use parent 'exporter';

our @export = qw(n);

sub n {
    my @n = @_;
    for (@n) {
        say $_;
    }
}

1;

はい。モジュール(パッケージ)名も関数名も「n」にしました。

初めてのエクスポート

上のコードで、自分的には初めてやったことがひとつあります。

use parent 'Exporter';
our @EXPORT = qw(n);

この2行を入れることによって、本来なら

n::n @colors;

としなければならないところが

n @colors;

というふうに関数呼び出しのみで済むようになっている。

この辺の情報は、平松さん( id:kaz_hiramatsu )の『雅なPerl入門 第3版』で学びました。(P120-121)
kazhiramatsu.hatenablog.com

Expoter モジュールや @EXPORT はいまだに全然理解できていないのだけど、やりたいことはできつつあるのでひとまずヨシ。

Minilla

では次。上のモジュールを書いただけだと、それを use しても読み込んでくれない&上記のとおり、今回は特定のディレクトリ内だけではなく、コンピュータ上のどこでPerlのコードを書いてもそのモジュールを使えるようにしたいので、たぶん通常のCPANモジュールをインストールするようなことをしなきゃいけない気がする。

しかし、どうやってそのモジュールを「インストール」すればいいのか……知らない。わからない。

ということで、これはけっこう調べる&理解するのに時間がかかったのだけど、最初に試したのは Minilla で。

この Minilla についても、上記の『雅なPerl入門』で詳しく解説されていてとても参考になりましたが、とりあえずこれを使って、

$ minil new n

で新規のレポジトリを作って、そこに上記の n.pm を入れて、同ディレクトリ内で

$ minil install

としたら……使えた!!

のですが、なんか違う……いや、ちゃんと動くのは動いたので、それで解決って言いたい気もするのだけど、第一にMinillaってそういうためのソフトウェアではなさそうだし、第二に(違和感としてはこっちのほうが重要だったのだけど)、今回やりたいことをやるためとしてはちょっと大げさすぎるような。

ということで、ひとまずやりたいことは実現できたものの、別の方法を探してもうしばらく旅を続ける……

環境変数 PERL5LIB を使う

それで訪れた次の試行。この章題は、以下の記事中からいただきました。
qiita.com

いつもお世話になっています @xtetsuji さんのQiita記事ですね。

そしてそれに近いネタとして、ほぼ同時点でたまたま検索して辿りついたのが以下。
perl-users.jp

じつはというか、前者のてつじさんの記事も、後者の記事にインスパイアされて&アップデート的な意味も含めて書かれたもののようで、だからというか相乗効果的にお二方の言わんとすることを理解しやすかったように思います。

それでその、肝心の目的の実現方法についてですが、どうやら任意のモジュールを読み込み可能な状態とする(@INC の中にそのモジュールを追加する)ためには、いくつかの選択肢があって、そのひとつとして、「PERL5LIB」という環境変数に、任意のパスを追加すればいいという。

正直なところ、ぼくはこのパス(PATH/環境変数)というやつもまともに理解できていなくて、しばらく前に読んだ良書『新しいLinuxの教科書』でようやくその一端に触れられた感じはあるのだけど、

新しいLinuxの教科書

新しいLinuxの教科書

とはいえ、まだあんまり腑に落ちてはいない。

いないのだけど、とりあえず bash_profile にこんなふうに記入したうえで、

export PERL5LIB=/path/to/n.pm

(実際には「/path/to/」の部分にはモジュールまでのディレクトリパスを書いている)

source コマンドでリロードすると、

source ~/.bash_profile

なんとモジュールを読み込むようになった。

以下、その模様。

f:id:note103:20160605004050g:plain

キタ!!!

オチ

さて、ここまでやってくる中で、初めはたんに「いちいち改行処理するのがメンドイ!」というだけの怠惰なモチベーションに突き動かされて旅に出てはみたものの、その過程で様々な思いがけない知見を得ることができまして、やはり学習楽しい! プログラミング楽しいな! と思うばかりだったのですが、その途上でひとつ気づいてしまったのが、上述の @xtetsuji さんの記事中にて、以下のような記述がありまして。

Debian wheezy のシステムPerl (最初からシステムに入っていたり、ディストリビューション公式のパッケージとして提供されているPerl) では以下のようなコマンドを打つと、以下のような出力が返ってきます

$ perl -E 'say for @INC'
/etc/perl
/usr/local/lib/perl/5.14.2
/usr/local/share/perl/5.14.2
/usr/lib/perl5
/usr/share/perl5
/usr/lib/perl/5.14
/usr/share/perl/5.14
/usr/local/lib/site_perl
.

ふむふむ……ん? このコード部分の1行目、

$ perl -E 'say for @INC'

これって、もしかすると……

my @colors = qw/red blue orange green yellow/;

say for @colors;  #<= 入れてみた

実行。

red
blue
orange
green
yellow

ああ〜。

for文の頭に、joinどころかスカラー変数すら置かずに「say」するだけでいいんだ〜……へえ〜……。
もしも最初にこれを知っていたら、ぼくたぶんこの試み一切やってなかったですね(笑)。

上のGIF動画を作りながらも思っていましたが、いくら関数名やモジュール名が1文字でラクだと言っても、いちいちファイルごとに use するのはやはり普通に手間だし。

まあ、とはいえ、「自作モジュールをひとまず自分だけで自由に使うにはどうすればいいか」という問題に対しては大きな前進を遂げることができたので、それでヨシとします……!

付録

元々は想定していなかったけど、当然のようにスカラー変数も出力できますね。

my $foo = 'bar';
n $foo;

f:id:note103:20160605093532g:plain

Cool!

って、自分だけが見る前提のプリントデバッグぐらいでしか使えませんが……。

Perlの正規表現で後ろからマッチさせる

わかってみると単純ながら、けっこうハマったのでメモ。

以下のような文字列があるとして、

my $fruits = '/apple/orange/grape/lemon/banana/';

このうち「lemon」の一つ前にある果物だけを取り出したい、とする。

ただし、上記ではその果物が「grape」であり、その前の果物が「orange」であることが明らかだけど、設問上はそこは不明確というか、可変で、さらに言えばその前の「apple」の前にもまだ他の果物がどんどん入ってくるかもしれない、とする。

つまり確実にわかっているのは、「lemonの一つ前にある果物を取り出したい」ということだけ。

となると、「orange」と「lemon」で対象を挟むことはできないし、前から数えていくつめ、と指定することもできない。
よって「lemon」のほう、後ろからマッチさせる方向で考える。

で、最初はこういうのを書いたんだけど、

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /\/(.+)\/lemon/) {
    say $1;
}

それだと、結果はこうなる。

apple/orange/grape

いわゆる最長マッチ、欲張りなマッチ状態ですね。

んで、じゃあ最短マッチ(非欲張りマッチ)ってどうやるんだっけ、と思ってこのようにやると、

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /\/(.+?)\/lemon/) {  #<= 「+」の後に「?」を追加
    say $1;
}

実行。

apple/orange/grape

変わらない。そういう問題ではなかったみたい。

ここまで来て、ん〜あれ、正規表現って後ろからマッチしていくこと自体そもそもできないんだっけ・・?? とかしばらく考え込んでしまったのだけど、結論としてはこんな。

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /.+\/(.+)\/lemon/) {
    say $1;
}

実行。

grape

はい。
ということで、パターンの頭に「.+」を付けただけでした。「.*」でも構わない。

またひとつ ぱーる の どうぐ を てにいれた! という感じでした。

C言語の航海日誌(4)

参考書の通読や写経も続けていますが、今日はちょっと寄り道というか予習というか、そんな感じでポインタの自習を。

たとえば、こんなコードがあったとして。

fruits.c
#include <stdio.h>

int main(void)
{
    int apple = 3;
    int lemon = 8;

    printf("リンゴ: %d\n", apple);
    printf("レモン: %d\n", lemon);

    return 0;
}

こんな結果になる。

リンゴ: 3
レモン: 8

はい。

で、今回のテーマであるポインタ、というのはどういう時に使うかっていうと、同じ値を複数の関数の間で渡したりするときに使う。という認識が僕にはあるので、上記をポインタの練習用にアレンジすると、たとえばこんな。

fruits_practice.c
#include <stdio.h>

void practice(int *apl, int *lmn)
{
    printf("リンゴ: %p\n", apl);
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl);
    printf("*レモン: %d\n", *lmn);
}

int main(void)
{
    int apple = 3;
    int lemon = 8;

    practice(&apple, &lemon);

    return 0;
}

結果。

リンゴ: 0x7fff54bad4f8
レモン: 0x7fff54bad4f4

*リンゴ: 3
*レモン: 8

はい。やってることとしては、main関数のほかにpractice関数というのを作って、そっちに「&apple, &lemon」という形で、appleと lemonのアドレスを渡している。
「アドレス」というのは、ここでは「0x7fff54bad4f8」とか出ている、そういうやつ。

ちなみに、ポインタの練習といえば通常、そのアドレスを渡した先(ここではpractice関数)で数値を入れ替えてみたりするわけだけど、この段階ではまだやらない。
まずはそれ以前の確認をきちんとしたいから。

さて、ポインタ関連の説明に触れ始めた頃、こうした構造というか状況を見て少し混乱したのは、このpractice関数のほうで引数を受け取る(=変数を宣言する)ときに、

void practice(int *apl, int *lmn)

というふうにしているんだけど、この「*apl, *lmn」というのはその後のプリントで「3, 8」と出ていることからもわかるように、アドレスではなく数値というか、値である。

つまり、main関数から投げた段階ではアドレスが示されているのに、それを受け取ったときにはアドレスではない「値」が入っているようなので、なぜ&どこで、それが変わってるの? という疑問を持った。
直感的には、受け取る側のほうでも、送る側と同じ「アドレス」をもらっているように見えると腑に落ちやすいのだけど。

具体的にいうと、現在このようになっているところが、

void practice(int *apl, int *lmn)
{
    printf("リンゴ: %p\n", apl);
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl);
    printf("*レモン: %d\n", *lmn);
}

こんなふうになっている、とか。

void practice(int &apl, int &lmn) // <= & でアドレスを受け取る
{
    printf("リンゴ: %p\n", &apl); // <= & でアドレスを表示
    printf("レモン: %p\n", &lmn);

    printf("*リンゴ: %d\n", apl); // <= 無印で値を表示
    printf("*レモン: %d\n", lmn);
}

あるいは、こんな。

void practice(int apl, int lmn) // <= 無印でアドレスを受け取る
{
    printf("リンゴ: %p\n", apl); // <= 無印 でアドレスを表示
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl); // <= * で値を表示
    printf("*レモン: %d\n", *lmn);
}

しかし実際はこうなっている。

void practice(int *apl, int *lmn) // <= * で値を受け取る
{
    printf("リンゴ: %p\n", apl); // <= 無印 でアドレスを表示
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl); // <= * で値を表示
    printf("*レモン: %d\n", *lmn);
}

最後の(そして実際の)状態でも受け取った後の整合性は取れているのだけど、受け取る瞬間がなんか「ん〜?」という感じではある。

ただし、確かに上の素朴な代案にしてもやはり問題はあって、最初の代案だとポインタ型の変数と通常のint型の変数との違いが見てわからないし、後者の案だと受け取っている段階の見え方がこれまた通常のint型の変数(仮引数)の宣言とまったく変わらないので、それはそれで問題ありそう。

まあこれはこれで妥当な落としどころなのかなあ、と思うけれど……。(とくにオチや結論はない)

さて元の例(fruits_practice.c)に戻ってもう少し理解の確認を進めたい。
このコードでは、まず二つある関数のうち下のmain関数のほうで、「apple」に3、「lemon」に8を代入している。

そして、それらをpractice関数に投げるときに、アドレス「&apple, &lemon」に切り替えて渡している。

さらに、それらを受け取る側では、「int *apl, int *lmn」というふうに受け取っていて、このときに「*apl」と「*lmn」の中に入っているのは「0x7fff54bad4f8」とかのアドレスではなくて、「3, 5」という値が入ってる。

じゃあmain関数から投げたアドレスはどこ行ったの? と言うと、それは「apl, lmn」に入ってる。

それがわかるのは以下の行で、

    printf("リンゴ: %p\n", apl);
    printf("レモン: %p\n", lmn);

実行するとこのように出ている。

リンゴ: 0x7fff54bad4f8
レモン: 0x7fff54bad4f4

%pがポインタ型の変数(ここではapl, lmn)のアドレスを示してくれる。

Perlとの比較

ここまでのことを踏まえつつ、ぼくが思い浮かべるのはやはりPerlのリファレンスで、それと重ねながら考えてみると、ぼくはPerlのリファレンスっていうのは、複数のファイルをひとつのzipに圧縮するとか、あるいは海外旅行へ行くときの膨大な荷物を旅行カバンにパッキングすることなんかに似ていると思っている。

たとえば、以下のような配列があったとして、

my @fruits = qw/apple orange grape lemon/;

これを目的のサブルーチンへ、参照渡しで(値のコピーではなく値の現物ごと)渡そうと思ったら、配列のままでは渡せないので、一旦リファレンスにしてから渡す。

fruits.pl
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my @fruits = qw/apple orange grape lemon/;

say $fruits[0]; #=> apple

foo(\@fruits);

sub foo {
    my $bar = shift;
    $bar->[0] = 'I ate an apple!';
}

say $fruits[0]; #=> I ate an apple!

この中の

foo(\@fruits);

というのが、ぼくのイメージで言うと、フルーツの詰め合わせを真空パックして外からは触れられないようにみっちり梱包して宅配に出す、みたいな感覚。あるいは上記の旅行カバンに詰めるとか、zipに固める、というイメージ。

で、サブルーチン foo の

    my $bar = shift;

でその梱包された荷物を受け取って、その中のリンゴを食べたっていうのが上のコードの話です。

じゃあ、同じことをCのポインタでやろうとしたらどうなるか、と想像してみると、ん〜、こんな感じか?

fruits_eat.c
#include <stdio.h>

void eat(int *apl, int *lmn)
{
    *apl -= 2;
    *lmn -= 4;
}

int main(void)
{
    int apple = 3;
    int lemon = 8;

    eat(&apple, &lemon);

    printf("リンゴ: %d\n", apple);
    printf("レモン: %d\n", lemon);

    return 0;
}

実行。

リンゴ: 1
レモン: 4

ふむ。最初にmain関数からリンゴを3個、レモンを8個送る。で、それを受け取ったeat関数のほうでそれぞれ2個と4個食べて、残りがリンゴ1個とレモン4個になりました、というコードですが。

こうして考えると、送付元でやっていることは、Perlならリファレンス化、Cなら「&」を付けてアドレス化。
そして受け取り側でやることは、Perlなら「->」とか「@$foo」とか「%$bar」とかでデリファレンスして操作することだけど、Cなら「*」を付けて操作する、ということかな。

なるほど。ぼくはCの場合、「*」を付けることで「アドレス」を「値」に戻すのかと思っていたけど、「*」は値そのものに戻す(コピーする)のではなく、デリファレンスした状態で中身をいじる、といったことだと思えば腑に落ちやすいかもしれない。
言い換えると、あくまでアドレスを経由した操作である、というような。

ちなみに、今参考にしている以下の本によれば、

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

「&」は「アドレス演算子」、「*」は「間接参照演算子」と言うらしく、なるほどここまで考えてみると、確かにそんな感じかなという気もしてくる。

まあ上記のように、Perlでは送付元から送ったリファレンスを、受け取り側でもリファレンスとして受け取るのに対して、Cでは送付元からアドレスを送って、しかし受け取り側ではそれを(いわばデリファレンスした後の)ポインタ型の変数で受け取るわけで、その若干ながら確かなズレがちょっと、まだ結構気にかかってはいますが。

ということで、大変煩雑な感じの内容になりましたが、自分的にはそれなりの理解を得られたかなという気もします。

今後の予告としては、構造体、共用体、malloc() などの写経にそろそろ入ってくるので、それらを理解することが楽しみです。

現時点ではまだ、上のサンプルコードとか、あるいはポインタ界のお約束コードことswap関数ぐらいしか書けないので、もう少しCならではのコードで出来ることを増やしていきたいところです。

C言語の航海日誌(3)

少し日が空きましたが、写経は少しずつ進めていて、前回から今日までに8本のサンプルコードを写経しました。

ページで言うとp101〜157まで。
全340ページなので、もうすぐ半分に辿りつきそうです。

写したコードは、たとえば以下。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE *in_fp;
    FILE *out_fp;
    int ch;

    /* 入力用のファイルポインタの取得 */
    in_fp = fopen("data.txt", "r");
    if (in_fp == NULL) {
        fprintf(stderr, "ファイルが開けません\n");
        exit(1);
    }

    /* 出力用のファイルポインタの取得 */
    out_fp = fopen("out", "w");
    if (out_fp == NULL) {
        fprintf(stderr, "ファイルが開けません\n");
        exit(1);
    }

    while ((ch = getc(in_fp)) != EOF) {
        putc(ch, out_fp);
    }

    /* 使い終わったファイルポインタを閉じる */
    fclose(in_fp);
    fclose(out_fp);

    return 0;
}

ポインタへの変数が出てきていますが、じつはまだこの段階ではポインタの解説は始まっていなくて、書中ではひとまず「おまじない」として扱うよう言われます。
実際にポインタの解説が始まるのはさらに40ページ以上先に行ってから。

このサンプルコードにおけるメイン・トピックは、fopen関数(ファイルのオープン)です。

ぼくは大体、Cのサンプルコードを写しながらいつも「Perlだとどうなんだっけ」と頭の中で比べているんですが、そういう比較対象というか参照軸がひとつあると、また印象に残りやすい気がしています。

で、CとPerlだとほとんど同じように考えられるものもあれば、ちょっと違うな……みたいなものもあって、このファイル入出力の関数は後者かなと。似ているところもあるけど、違って感じられる要素のほうが多いというか。
ちなみに、ほとんど同様に使えるなと思ったのはif文、while文あたりです。

もう一つ、これも印象的なトピックでした。

#include <stdio.h>

int main(int argc, char **argv)
{
    int i;

    for (i = 0; i < argc; i++) {
        printf("argv[%d]: %s\n", i, argv[i]);
    }

    return 0;
}

ポインタが2重にくっついていて、それについては上記同様、ここでも「おまじない」扱いですが、コマンドライン引数の受け取り方を少し把握できた気がします。

この後は、仮引数の使い方、関数の分割、プロトタイプ宣言などを学んで、その後にようやくポインタが出てくるようです。

ポインタについてはすでに同書を含む書籍の通読や、ドットインストールを通して概要はチェック済みですが、実際に手を動かしながら理解していくのはこの後なので、楽しみです。

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

C言語の航海日誌(2)

今日は3点のサンプルコードを写経しました。

一つは以下。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int score[100];
    int status;
    int temp_score;
    int score_count;
    int i;

    /* 配列に値を入力する */
    score_count = 0;
    while (1) {
        status = scanf("%d", &temp_score);
        if (status == EOF) {
            break;
        } else if (status != 1) {
            fprintf(stderr, "入力エラーです\n");
            exit(1);
        }
        if (score_count >= 100) {
            fprintf(stderr, "データ件数が多すぎます\n");
            exit(1);
        }
        score[score_count] = temp_score;
        score_count++;
    }

    /* 配列の内容を表示する */
    for (i = 0; i < score_count; i++) {
        printf("score[%d]: %d\n", i, score[i]);
    }

    return 0;
}

動かすとこんな感じです。

f:id:note103:20160520012215g:plain

(ちなみにコマンドプロンプトの「2$」の「2」はカレントディレクトリ。今は第2章のサンプルコードを書いているので「2」というディレクトリ内で作業している)

「8, 9...」というふうに数字を入れていくのは前回同様ですが、今回は終了の合図を「-1」ではなく、EOFで示しています。
上記DEMOでは、標準入力からコントロール+DでEOFを伝えています。

ファイルのリダイレクトで同様のことする場合は、こんな感じ。

f:id:note103:20160520012814g:plain

標準入力版に戻って、数字ではなく文字を入れてしまうと……

f:id:note103:20160520012852g:plain

はい。

そのEOFですが、Perlだとあまり聞かないなあ、せいぜいヒアドキュメントの時に例としてそういう文字列が出てくるぐらいだけど、じゃあPerlで同様にEOFを示したい場合ってどうするんだろ?

と思ったんですが、ざらっと調べたかぎりでは、「Perlでも一応 eof という組み込み関数が用意されているけど、勝手にEOFを認識して終わってくれるから使うことはほとんどない」みたいな説明が多く、まあ確かにそのようにも思えるんだけど、じゃあ「ファイルが終わったら(あるいは標準入力からの入力が終了したら)メッセージ出してから終わらせたい」と思った場合どうするんだろ? みたいな疑問が湧いてきて……

こんなコードを考えてみました。

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

print ">>> ";
while (my $t = <STDIN>) {
    print $t;
    print ">>> ";

    say 'bye' if (eof);
}

動かすと、こんな感じ。

標準入力版

f:id:note103:20160520014920g:plain

※「7」を入力した後にコントロール+Dを入れています。

リダイレクト版

f:id:note103:20160520015031g:plain

まあ確かに、特殊といえば特殊、あまり使わないケースかもしれないですが……。

とはいえ、これまで知っているようでまったく理解していなかったEOF/eofについて(図らずもPerlを含めて)知見を得られて良かったです。

今日はその他、プリプロセッサとマクロの違いについて学べるサンプルコードなども写しましたが、たしかに言われたとおりに動いていることは確認できたものの、ちょっと難しかったかな……。

次回からは上記のサンプルコードをさらに発展させた、ソートをテーマにしたコードに入っていきそうです。
今日の記事では触れませんでしたが、今回のサンプルコードからfprintfを使ったり、scanfの戻り値を利用するようになったりと、ページが進むごとに結構目覚ましくトピックが増えていくので、次も楽しみ&大変そう……な感じですが。

C言語の航海日誌(1)

以下にも書いたように、
note103.hateblo.jp

最近はC言語の学習を進めています。

といっても、実際にはそれができるほど気持ちに余裕のある時間というのはなかなか無くて、業務をしなくていい時間があってもついダラダラしてしまいがちだったりするので、ちょっとでも進捗があればブログに残しておこうかなと。

今日(5/18・水)は以下の本から、2本、サンプルコードの写経をしました。

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

進捗的には、全P340ぐらいの本で、今日終わったのがP85ぐらい。
すでに一回通読しているので、前に読んだときに比べれば馴染みを感じる気もします。
というかまあ、一度Perlの基礎を見回っているので、そんなにハマりまくるということもないのですが……。

と言って早々にナンなのですが、今日写経したサンプルコード、すごくシンプルなんだけどそれでも実はハマって。

写したのはこんな感じ。

2-3.c
#include <stdio.h>

int main(void)
{
    int score[100];
    int score_count;
    int temp_score;
    int index;

    /* 配列に値を入力する */
    score_count = 0;
    while (1) {
        scanf("%d", &temp_score);
        if (temp_score == -1) {
            break;
        }
        score[score_count] = temp_score;
        score_count++;
    }

    /* 配列の内容を表示する */
    index = 0;
    while (index < score_count) {
        printf("score[%d]: %d\n", index, score[index]);
        index++;
    }

    return 0;
}

で、ほとんどこれ本にあるままぴったり書いているのだけど、なぜか思うように動かない……。

f:id:note103:20160519013325g:plain

というところで、しばらくエディタと本とを行ったり来たり、交互に見返して、ん〜……どこも間違ってないように見えるが……と思いながら数字を入れてみると、

f:id:note103:20160519013705g:plain

ああ、できてる。

ようするに、最後に「-1」を入れたら終端の印、という仕様で書いているコードだとはわかっていたんだけど、そもそもこれは整数を受け取る前提で書かれていた、ということを完全に忘れていたんですね〜……。
だから普通に文字列で「test」とか入れても動いてくれなかった、という。

ちなみに、上記DEMOで「2-3.c」というファイル名が出てくるのは、同書のサンプルコードに付けられた番号です。
これは第2章の3つめのサンプルコードなので、「2-3」。

これがこのまま第6章の「6-3」まで行くとひとまず1冊終わりです。
(1章あたりのサンプルコードの数はまちまち。第2章だと7個ある)

今日は「2-3」と「2-4」を写した&実行したので、次の機会でとりあえず第2章のぶんを終わらせたい……。