たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
そういえばちゃんとした書評を文章としてまとめていなかったことに気が付いたので、改めて書き留めておこうと思います。自分は対象読者層から外れていますが、「マンガで技術解説」という非常に近い領域で活動をしている以上、気になるのは事実なので、それならいっそちゃんと読んで学びを得ようと思い自費で購入しました。
本書はひとことで言えば、「今これからWebサイト制作を初めてみようと思っている、スタート前の人のための本」「格好いいサービスに憧れてWeb制作を始めてはみたけど、知れば知るほど次から次に新しいキーワードが出てきて、勉強しないといけない範囲がどんどん広がっていってしまい、途方に暮れている人」だと思います。
昨今のWebというと、アプリ寄りの見え方をするSingle Page Applicationと呼ばれるつくりが流行りで、やれAngularだのReactだのという話になりがちだと思うのですが、それらも全て基礎があっての話。SPAを作るにせよ、そこから移り変わった次のトレンドに乗るにせよ、絶対に外せないであろう知識というのはあります。本書は、主人公の「わかばちゃん」をはじめとするキャラクター達を立て、わかばちゃんを皆がサポートして導くという流れに乗せて、Webサイト制作の基礎中の基礎となるトピックを一通り解説する入門書ということになります。
本書の基本構成は「その節で解説する概念の大まかな絵解き説明、あるいは内容に絡んだネタの4コマ」と「それに続いてテキストや図での解説(本文)」という形で、マンガ部分の分量はそんなに多くはないです。「マンガで」という所に期待しすぎると、もしかしたら肩透かしを食らうかもしれません。マンガ部分だけを追った場合に得られる情報量は限られていますので、当たり前と言えば当たり前ですが、ちゃんとテキストも読むことが必要です。
自分は中学校でNEW CROWNで英語を初めて教わりましたが、いらすとや系の無色透明な・人格を意識させない絵ではない、漫画雑誌で見慣れた絵柄の・趣味嗜好などのバックグラウンドを持っていそうなキャラクター達(当時の物は「緋が走る」のあおきてつお氏がイラストを担当されており、この形式の先駆けだったそうです)がいることで、「堅苦しくてつまらない教科書、ではない。僕らの価値観、僕らの好みの事をちゃんと分かってくれている。頭ごなしに押し付けてきているのではない」と感じ、未知のものへの抵抗感がずいぶん薄れたような記憶があります。
<script src="https://source.pixiv.net/source/embed.js" data-id="59331506_9a7ef6e0b488bf3557348b4bf4a854e7" data-size="medium" data-border="on" charset="utf-8"></script><noscript>
10月の誕生盆栽で誕生日をお祝いするわかばちゃん&HTMLちゃん by Piro/結城洋志 on pixiv
</noscript>本書に対して抱く率直な感想は、その感覚に非常に近いと思います。解説のためのマンガというより、読者の心理的抵抗を和らげる緩衝材としてのマンガ、という性質が主であるように感じました。そして、その狙いは見事に果たされていると思います。自分を未熟な初心者のわかばちゃんと重ねて読み進めることで、Web制作にまつわる膨大なトピックの中から「まず最初に押さえておかないといけないのは、ここ!」という部分をストレスなく学べるのではないでしょうか。
マンガは導入に過ぎないとはいえ、本編のテキストも決して堅苦しくはなく、文字だけではイメージしにくいであろう抽象的な概念の説明に図を多用していて、全体としては平易な解説になっていてます。「分かりやすい解説書にする」ための工夫が凝らされていますので、引っかかりを覚えることなくするっと通読できると思います。
初心者向けの技術解説は、どこまで説明してどこからをカットするか、例え話をする時はどこにフォーカスしてどこを無視するか、話を単純にするために嘘をつくのかつかないのか、という匙加減が難しいものです。あれもこれもと入れていくと、必然的に個々の解説に割ける文章の分量が減り、説明はおざなりになってしまいます。
本書は、自分の役割はあくまで導入と割り切っていて、難しい概念の話は別の専門書に任せるスタンスを取ることにより、解説として無理をせず、極力嘘をつかない、誠実な立場を取っていると感じました。本書を読んだ後であれば、「扱う話題はやたら幅広いが、内容は薄い」初心者向けの本をすっ飛ばして、中級者向けやあるいはそれ以上の難易度の本や解説サイトに挑戦していけると思います。
自分が本書の最大の特長だと思ったのは、HTMLやCSSといった「Webサイト制作に必要な道具」の使い方の説明に終始してはいないという点です。
分量のほとんどの部分がそういった技術の解説なのは事実ですが、本書はそれらの手前の導入部に「そもそも、その道具を使って何を作ろうとしているのか? 何のためにWebサイトを作ろうとしているのか?」という目標設定のフェーズを、後ろに「で、作ったはいいが本当に目的は達成されているのか?」というフィードバックのフェーズを設けています。これにより、本書全体に一本の筋が通っていて、「イラストが豊富で内容も平易だが、作者が何を言いたいというのは特に読み取れない、雑多な内容の本」ではない、「やりたい事の本質は人とのコミュニケーションであり、前提の立て方次第で最適な手段は変わる。手段としてWebを選ぶというのは、こういうこと。その手段はこういうもの」という考え方までもを伝える野心的な構成の本になっていると思いました。
だからこそのマンガ要素、なのかもしれません。そんな深いテーマを語る長いテキストは途中で飽きてしまうという人でも、わかばちゃんと同じペースで進み続ければゴールに辿り着ける、そういう本なのだと言えます。
Webは新しい技術が絶えず生まれ廃れる、荒波のような世界であり続けています。Webサイト・Webページ制作に関わる技術の全体像を把握しきることは困難ですし、その場を乗り切るのに必要な部分だけをつまみ食いしていても、それぞれが文脈上結びつかない個別の情報を増やすだけになりがちなのではないかと思います。
本書「わかばちゃんと学ぶWebサイト制作の基本」は、そんな中を自分に自信を持って生きていくための基準点となる、一朝一夕に廃れることのない確かな知識を伝える1冊だと思います。趣味で始める人に、仕事で関わり始めたという新人に、あるいは、自分で制作はしないまでもWebサイト制作の専門家と組んで何かをしようという人に、おすすめです。
サークル「シス管系女子会」のここまでの活動の振り返りです。
技術書典と技術書典2、コミティア119、あとデブサミのDevBooks 2017にサークル参加して得られたデータや思った事を書き留めておきます。
最初にお金の絡む話から書いていきます。
技術書典とコミティアは商業誌の販売が可能とのことだったので、シス管系女子1と[2]を持っていった他、技術書典と技術書典2では無料頒布のフリーペーパーやリーフレットも持っていきました。先に大まかな数字を出しておくと、かかった費用と実績は以下のような感じでした。
イベント参加費と技術書典・DevBooksのコピー本、あと技術書典2のリーフレットの印刷費は日経BP持ちで、ムックの販売は中間マージン無しの委託販売の体裁として、ムック売上額はそっくりそのまま日経BP社に渡しています(oh, ボランティア……)。1日人を張り付ける人件費を考慮に入れなければ、多分黒字にはなっていそうな気がします。
技術書典1ではコピー本を無料配布し、技術書典2ではリーフレットを無料配布しました。
ただのチラシを配ってもどうせ見てもらえないと思ったので、中身は漫画になっています(シス管系女子BEGINSの前後に「いかにも宣伝」という感じの漫画を足した)。通り過ぎる一瞬で目を留めてもらうのは無理でも、持って帰ってじっくり読んでもらえるといいな……という魂胆です。この辺は、イベント以外の場でも買える商業出版物ならではの割り切りですね。
元々、技術書典2でもコピー本の体裁の物を無料配布する事を考えていたのですが、思ったよりページ数が増えて表紙込みで24ページになってしまった結果、どう作っても1冊あたりの原価が100円近くになってしまう事が分かった(なのでDevBooksでは一応100円での頒布としました)ため、A4サイズ3つ折りのリーフレット両面を使った縮刷版という体裁にしました。苦肉の策でしたが、これだと印刷枚数次第ではフルカラーでも1枚20円を下回るくらいになるので、結構アリな気がしています。
最初の技術書典では当初は、スペースに置いておいて立ち寄って話を聞いてくれた方や希望者に手渡すつもりだったのですが、「どうせ無料で配布するのなら通りかかった人にどんどん渡しても同じなのでは?」とツッコまれて、それもそうかと思って「無料配布のサンプルです」と声をかけて渡していくスタイルに切り替えたのでした。
通行中の人への声かけは同人イベントではマナー違反とされている事が多いようで、COMITIAに至っては「呼び込み禁止」とはっきり明記されています。技術書典では「商業出版物を取り扱う場合は企業参加扱い」というレギュレーションで、特に明示的に禁止するようなルールの記載も無かったので「企業ならこのくらいはやってもバチは当たるめぇ」と開き直って声を出していましたが、迷惑行為と言われてしまうとグウの音も出ないので、チラシ撒きともども、やるならせいぜい前を通りかかる人に届く程度の声量に留めた上で、恨まれる&出禁になる覚悟でやりましょう。とだけ書いておきます。
元々、シス管系女子会としてのイベント参加は以下のような目論見で始めました。
結論から言うと、このうち1と2は実現され、3は目立った結果には繋がっていないという印象です。
1は、前述の通り会場でのムック売り上げがそこそこあり、その際にシリーズ2冊をまとめ買いしてくださる方が多かったという事から、やはりまだまだリーチできていない潜在読者がいるという事の証明にもなっていると思います。
2は、元々出版社サイドに何度か「試し読みになるように本編の一部を公開したい」という事を打診しているのですが、「原稿料を支払って下請けに制作させたコンテンツを、何故下請けが勝手に外に出したがるのだ?」という意識があるのかないのか進展が無いので、だったら原稿料もらわなくていいから自分で好きに使えるように勝手に描くわという事でコンテンツを制作したという事です。実際にこれを動機として特別編を制作し公開に繋げられていることから、〆切駆動型の自分には確かに効果的でした。
3は、その後のイベントレポートやTwitterでの反応を見る限りでは、「このイベントで初めて知った」「この試し読みで初めて意識した」みたいな新規開拓の方の反応はあまり見られませんでした。また、全一般参加者の方のうち1/5~1/6にリーフレットを受け取って頂けた計算になりますが、イベントレポートの写真等で「会場で入手した物」の一覧に写っている例は少なかったように思いました。
理由は色々考えられますが、成果を上げるためにはまだまだ工夫が必要そうです。
初回の技術書典の話はその時のエントリをご参照下さい。以下は技術書典2の感想です。
技術書典2当日のTwitterの反応まとめを見ると、来場者が多すぎて入場が遅々として進まない事へのコメントが多く、参加を諦めた人も多かったというのは終わった後で知りました。方や会場内では、サークルによっては開場1時間や2時間で完売の拍手が上がっていました(6時間あるイベントの序盤での在庫切れというのは相当な読み違えがあったということになります)。「技術書オンリー」「同日に別の場所でも同人イベントが開催されており、その中にはけものフレンズのオンリーイベントも含まれていた」「天気は雨」など、来場者が少なくなる方向の材料ばかりがあったにも関わらずこの盛況さということで、当日に至ってすらも来場者数の読みが極めて難しい状況でした。
前回は会場が建物内の地下と2階に分断されていた上に、入場が整理券制になっていたことから、全体の様子というのは正直見えにくかったのですが、今回は他の同人誌即売会イベントに近いレイアウトだったので、全体の混雑具合などが俯瞰しやすかったです。そこで抱いた率直な感想は、まさに普通の同人誌即売会だなという事でした。
これは、コミケやコミティアなどに行った事のある自分からすると驚くべき事です。というのも、これらのイベントで評論・技術ジャンルのスペースを訪れると、他の混雑具合とは一転してびっくりするぐらい人がいないからです。そんなガラガラ具合のジャンルのサークルだけを集めるわけですから、本当にイベントとして成立するのか?という心配の声が運営サイドからすらも上がっていたのも頷けます。
別にアンケートや聞き取り調査をやったわけではないのでただの想像ですが、実はみんなこういう「知の共有」に焦点を当てたイベントを求めていたということなのではないでしょうか。
既存のイベントで技術同人が参加できるイベントといったら、基本的にはオールジャンル(取り扱う内容のジャンルを問わない、なんでもあり)のイベントですが、それらのイベントでは技術同人以外のサークルはほとんどが漫画やイラスト、エンターテインメント作品をメインにしていますし、比較的近いジャンルのオンリーイベントと思われる文学フリマであってもメインは小説や評論です。そういう状況では、技術同人に興味がある人でも、行く苦労や金銭的コストに対して得られる物が少ない、つまり「割に合わない」と判断されて、行ってみようという気が起こりにくいのではないかと思います。例えて言うと、アニメイトの一角で技術書籍のコーナーが設けられていたとして、技術者を自認する人がわざわざそこまで買いに行くのか? あるいは、アニメイトに来る客が技術書コーナーまで見て回るのか? っていう話です。
その点、技術書典は最初から技術書オンリーと銘打っていますから、「当たり」に出会えるだろうという期待値はグッと高まります。来場者が多いのは、他に類似のイベントが無いからそういうニーズがここに一気に集中してしまっているという事なのではないか。というのが、僕なりの予想です。
IT技術者をやっていると、よく「技術カンファレンスや勉強会は、発表する人が一番勉強になる」「雑誌の記事や本は、著者が一番勉強になる」なんて話を聞きます。
知見やノウハウは、自分だけが見るメモ程度であればいくらでも書けますが、他の人にも共有できるレベルの内容に引き上げるのには手間がかかります。話を一般化したり、あやふやだった所を調べ直して根拠をはっきりさせたり、話題を整理したり……こういう事が面倒で、メモのまま放置されてしまう情報や、メモすら残されないまま忘れられてしまう情報というのは結構あります。
1コマの発表内容だったり1冊の本だったりといった「パッケージ」の形にまとめる時には、必然的にそういった作業が発生します。「イベントに参加するので」「そこで新刊として発表するので」という風に〆切を設定する事で、情報を精錬するための動機が生まれ、より価値の高い情報が出てくる、そういう動機になるというのは、技術書典というイベントの重要な意義の1つと言えるでしょう。
イベント終了後、技術書典2での刊行物をベースに商業出版する事になったという話を見かけました。同人誌で活躍していた人が商業誌で活躍するようになるというのはマンガ・ゲーム業界にはよくある話で、出版社の人が会場内を見て回って、有望そうな人に声をかけるという事が、技術書でも起こっているということのようです。
会場内で実際に売れ行きが良ければ商品化する好材料になるというのはもちろんあるでしょうが、そもそもこういうイベントに物を出している時点で「〆切を設定して」「それに間に合うように」「情報を整理してパッケージ化して」同人誌として世に出すという事ができている訳で、ネットでブログに断片的な情報を書き散らかしているだけの人に比べれば、商業出版物の原稿を書くという仕事にちゃんと取り組んでくれそうだと期待できるのではないかと思います。
最後に、これは同人誌即売会一般の話ですが、対面での頒布は「読者の方と直接触れ合える」という事が最大の特長でしょう。
先のまとめで「ネットでええやん」という感じのコメントをいくつか見かけましたが、VRでないただのインターネットモールを想定しているのであれば、それは「欲しい物を事前に決めて、買いに行って、買う」という事以上の意義をイベントに対して見出していないという事なのではないかと思います。同人イベントには、会場で作者に直接感想やお礼を言いに行く人もいれば、会場で読者が喜び興奮している様子を見たくて出展する人もいます。
自分の場合は、Webでない紙媒体で連載をやっていて、読者層がWebでアクティブな人達の層とは微妙に違うらしいという日経Linux誌での連載であることから、いつもはあまり「読者の方々に実際読まれている」という実感を持てずにいます。そのため、目の前にいる方に「持ってる」とか「読んでる」とか言って頂けるのは素直に嬉しかったです。普段なかなか認識できない「読者の実在」を意識することができて、励みになるのは間違いないです。
あと、対読者というのとはちょっとズレますが、ご同業の方と話せる機会としても自分にとっては有意義でした。技術書典2ではお隣のスペースがマンガで分かるWebデザイン/マンガでわかるGitの湊川さんのスペースだったため、イベント中やイベント終了後の合同打ち上げでこの仕事の事やそれ以外の事など色々話せたのがとても嬉しかったです。
そういえば個別にレポートを書いていなかったので、DevBooksの事についてもここに書き留めておこうと思います。
恐らく技術書典の成功を受けてだと思いますが、今年はDevelopers Summit 2017の会場内の1室に小規模な同人誌即売スペースが設けられていました。商業出版物の頒布は不可というレギュレーションだったので、直前のコミティア119での在庫放出と技術書典2向けの頒布物のプレお目見えだけできればいいかと思って参加してみました。
で、参加してみた感想なのですが、とにかく精神的にキツかったです。
というのも、通常の同人イベントだと全時間を通じて人の流れがありますが、DevBooksはデブサミの1コーナーという性質上、セッションとセッションの合間にどっと人が来るもののそれ以外の時間はガラッガラで、もう暇で暇でしょうがなかったです。一人での参加だったので、誰かに店番を任せてセッションを見に行くという事もできませんでしたし……
あと、「シス管系女子」というコンテンツとイベントの来場者層がマッチしていなかったようだという事も感じました。デブサミはどちらかというと流行りの技術に強い関心のある方が多いようで、DevOpsとかと真逆の方向を向いている「シス管系女子」は訴求力が無いのでしょう……多分。
ということで、もし2回目以降があるとしたらですが、「元々デブサミのセッションに興味があって」「技術同人もやっている」「当日は店番の手伝いをしてくれる人がいる」という条件を満たせる方が、会場内での荷物置き場確保も兼ねて参加するのが良いのではないかと思います。
まあ何というかタラタラ書いてきましたが、オフラインのイベントはやっぱ良いですよ。オンラインでオンデマンドでいつでも欲しい情報が手に入る、どころか、欲しくない情報まで洪水のように押し寄せてくるのが当たり前の今だから、身体感覚を伴うライブな体験の価値がより際立つ。などという言い方も実に月並みですが、その月並みな体験をした上で「やっぱり月並みだね」と言うのと、体験しない状態で想像で物を言うのとでは違うと思いますので、未体験の人は体験してみて欲しいです。直近では4月29日に幕張メッセで「超技術書典」というのが開催されるので、まずはここから。
続き:技術書典3のご報告
When I start Firefox, tree of tabs and favicons are always lost. Even if I reorganize tree of tabs again, they are flattened after every restart.
Firefoxを起動すると、必ずタブのツリーとタブのアイコンが失われます。ツリーを作り直しても、再起動する度にツリーが失われてすべてのタブが同階層に表示されてしまいます。
Please press Ctrl(Command)-Shift-J to open the "Browser Console" - one of developer tools of Firefox. Is any error message like this reported?: NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsISerializationHelper.deserializeObject] Utils.jsm:94
Screenshot:
Then, the problem may be caused by invalid information stored in your session data. Try following steps to cleanup your session data:
var TabStateCache = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm',{}).TabStateCache;
Array.forEach(
gBrowser.browsers,
(browser)=>
TabStateCache.update(browser, {
iconLoadingPrincipal : null
})
);
"OK"
Ctrl(Command)-Shit-Jを押して、Firefoxの開発ツールの1つである「ブラウザコンソール」を開いて下さい。以下のようなエラーメッセージが出力されていませんか?:NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsISerializationHelper.deserializeObject] Utils.jsm:94
スクリーンショット:
もしこのエラーが出ているなら、問題はセッション情報の中に混入してしまった不正なデータが原因で引き起こされている可能性があります。以下の手順でセッション情報をクリーンアップしてみて下さい。
var TabStateCache = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm',{}).TabStateCache;
Array.forEach(
gBrowser.browsers,
(browser)=>
TabStateCache.update(browser, {
iconLoadingPrincipal : null
})
);
"OK"
このエントリはQiitaとのクロスポストです。
複数のファイルに共通する部分があるとき、共通箇所をまとめて切り出しておいて、各ファイルからはそれらを参照するだけにする、というのはよくある話です。C言語なら#include <stdio.h>
という書き方をしますし、Web制作をやる人なら、CSSの@import
規則をご存じだと思います。
しかしたまに、これに似たことを静的なファイルで行って「include文の位置に参照先のファイルがそのまま埋め込まれたファイル」を作りたいという場面が出てきます。
この記事では、そんな「静的なファイルを生成するために、ソースとなるテキストファイルに書かれたinclude文をシェルスクリプトで処理して、参照先ファイルの内容をその位置に埋め込んだ結果のファイルを得たい」というニーズに対する、なるべく効率のよい実現方法を模索してみます。
以前、さくらのレンタルサーバーの一番安いプランでWebサイトを公開するノウハウという記事で、「ssh接続できない月額129円の激安レンタルサーバーでも、手元にLinuxな環境があるならコマンドラインのFTPクライアントとシェルスクリプトでrsyncっぽいことができるよ! ついでに色々前処理もさせられるし、シス管系女子のサイトのようにチープな静的コンテンツだけのサイトなら全然余裕で運用できちゃう! やったね!」という事例をご紹介しました。
その際にやりたかった前処理のひとつに前述のinclude文があり、全ページで共通のヘッダやフッタを
html/_parts/metadata.html(共通パーツ):
<meta charset="UTF-8">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@piro_or">
<meta name="twitter:creator" content="@piro_or">
...
こんな風に断片ファイルとして切り出して用意しておいて、HTMLファイルの中に
html/index.html(ソースファイル):
<!DOCTYPE html>
<html lang="ja" xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
<!--EMBED(metadata.html)-->
<title>シス管系女子って何!? - 【シス管系女子】特設サイト</title>
...
のように書いておき、アップロード直前に
html_resolved/index.html(埋め込み後):
<!DOCTYPE html>
<html lang="ja" xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
<meta charset="UTF-8">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@piro_or">
<meta name="twitter:creator" content="@piro_or">
...
<title>シス管系女子って何!? - 【シス管系女子】特設サイト</title>
...
のように解決する、という事をしていました。
最初に先の記事を公開した時の実装は、以下のようなものでした(実際にはちょっと違う書き方でしたが、要旨としてはこんな感じ、ということで)。
build.sh:共通パーツを埋め込む:
#!/bin/bash
# 環境によってsedで拡張正規表現を使うためのオプションが違うので、
# egrepコマンドのように使える「$esed」を定義しておく。
case $(uname) in
Darwin|*BSD|CYGWIN*)
esed="sed -E"
;;
*)
esed="sed -r"
;;
esac
rm -rf html_resolved
cp -r html html_resolved
# include文の検出用正規表現。
# ファイル名部分は、後方参照で取り出せるように`()`で囲っておく。
embed_matcher='<!-- *EMBED\( *([^) ]+) *\) *-->'
# 処理対象のファイル(include文があるファイル)を検索する。
egrep -r \
--include='*.html' \
"$embed_matcher" \
html_resolved |
cut -d ':' -f 1 | # ファイルパスだけを取り出す。
uniq | # 1ファイルの中で何カ所も見つかる事があるので、重複を取り除く。
while read path
do
# 見つかった各ファイルに対して処理を行う。
echo "updating $path"
# ファイルを退避し、
mv "$path"{,.tmp}
# ファイルの内容を1行ずつスキャンする。
# `IFS= read -r`とすることで、行頭・行末の連続する空白文字や
# 行の中のエスケープ文字を保持する。
cat "$path.tmp" | while IFS= read -r line
do
# include文がある行だったら、
if echo "$line" | egrep "$embed_matcher" 2>&1 > /dev/null
then
# 埋め込み対象のファイルの内容をcatして、
# リダイレクトで書き出す。
parts_name="$(echo "$line" |
$esed -e "s/^.*$embed_matcher.*/\\1/")"
cat "html/_parts/$parts_name" >> "$path"
else
# それ以外の行はそのまま書き出す。
echo "$line" >> "$path"
fi
done
rm "$path.tmp"
done
これでも一応目的は達成されていたのですが、以下のような問題が残ってしまっていました。
while
ループを回すので、とにかく遅い。1は、処理対象のファイルの行数とファイル数が増えるごとに大きな負担となります。前の記事を書いた時には「変更が無かったファイルは無視する」という別方向からの対策を取ってみましたが、それでも全ファイルを対象に処理し直す時にはずいぶん待たされてしまいます。
2は、とりあえず今のところ問題にはなっていませんが、include文をHTMLのコメントの形式にしたので、もしかしたらこの制約の事を忘れて行中に書きたくなってしまうかもしれません。その時に「えっそんな制約あったなんて……」と戸惑う羽目になる前に、なんとかできるものならなんとかしておきたいところです。
その後長らくそれっきりになっていたのですが、仕事の中でまた似たようなことをやりたい場面(Firefoxの法人導入では管理者による設定を静的なJavaScriptファイルとして用意するのですが、「大部分は共通だけれども一部分だけが異なる」という設定ファイルを複数種類用意する必要が生じたのでした。共通部分を括り出すのでなく、ソースファイルに書かれたプレースホルダの位置に、環境ごとの別のソースファイルの内容を埋め込んで各環境ごとの静的なファイルをビルドしたい、という感じです)が出てたので、これを機にもっとマシなやり方を探してみました。すると、sed
のr
コマンドというまさにこういう事をやるためにあるような機能の情報が見つかりました。
ということで、ここからがこの記事の本題です。
sed
のr
コマンドは、「カーソル行の直後(次の行の直前)に別のファイルの内容を読み込んで挿入する」という物です。パターンマッチと組み合わせれば、「include文をパターンマッチで見つける→見つけたinclude文の箇所にinclude対象の外部ファイルの内容を埋め込む」という事もできるはずです。
例えば、最初に示した例をべた書きするとこうなります。
$ cat html/index.html |
sed '/<!--EMBED(metadata.html)-->/r html/_parts/metadata.html' \
> html_resolved/index.html
sed
の機能なので、Bashのwhile
ループと比べると圧倒的に高速です。これで、問題の1つ目の「とにかく遅い」という点が解消されます。やりましたね!
ただ、まだ問題はもう1つ残っています。sed
のr
は「マッチしたまさにその箇所」ではなく「マッチした箇所が含まれる行とその次の行の間」にファイルの内容を出力するため、行の中程にinclude文があると「include文の前の文字列→include文の後の文字列→参照されたファイルの内容→次の行」という結果になってしまいます。
この問題を解消するには、include文の後で必ず改行するように事前処理してしまうのが手っ取り早いです。
$ cat html/index.html |
$esed "s/($embed_matcher)( *[^$])/\1"\\$'\n''\3/g' |
sed '/<!--EMBED(metadata.html)-->/r html/_parts/metadata.html' \
> html_resolved/index.html
何だかゴチャゴチャ書いてあって分かりにくいですが、置換の指定としては以下のような内容になっています。
(<include文>)(<改行と空白以外でinclude文より後の文字列>)
<マッチしたinclude文><改行文字><include文より後の文字列>
g
フラグ)sed
で置換後の文字に改行文字を含めれば行の途中で改行することができますが、それには色々と工夫が必要です。詳細はsedで改行を出力するをご覧下さい。
このように置換してからsed
のr
でinclude文を処理すれば、ちゃんと「include文の前の文字列→参照されたファイルの内容→include文の後の文字列→次の行」という順で出力されるようになるわけです。
ここまでのコマンド列にはinclude文を解決するための指定をべた書きしていましたが、実際には任意のファイルでいろんなファイルに対するinclude文を処理する必要があります。後方参照でsed -r '/<!--EMBED\((metadata.html)\)-->/r html/_parts/\1'
みたいなことができると楽なのですが、残念ながらsed
のr
コマンドの読み込み対象ファイルの指定には後方参照は使えません。
解決策としては、sed
を実行するコマンド列をsed
で組み立てるという方法があります。
$ embed_matcher='<!-- *EMBED\( *([^) ]+) *\) *-->'
$ embed_mark_to_resolver="s|($embed_matcher)| -e '/\\1/r html/_parts/\\2'|"
$ cat html/index.html |
egrep -o "$embed_matcher" |
sort |
uniq
<!--EMBED(metadata.html)-->
<!--EMBED(footer.html)-->
<!--EMBED(header.html)-->
...
grep
やegrep
(grep -E
と同等)に-o
オプションを指定すると、「マッチした文字列がある行」ではなく「マッチした文字列そのもの」、ここではinclude文の部分だけが出力されます。それをsed -r "s|($embed_matcher)| -e '/\\1/r html/_parts/\\2'|"
で置換して-e 'sedスクリプト'
というコマンドラインオプションに変換すると、こうなります。
$ cat html/index.html |
egrep -o "$embed_matcher" |
sort |
uniq |
sed -r -e "$embed_mark_to_resolver"
-e '/<!--EMBED(metadata.html)-->/r html/_parts/metadata.html'
-e '/<!--EMBED(footer.html)-->/r html/_parts/footer.html'
-e '/<!--EMBED(header.html)-->/r html/_parts/header.html'
...
この出力に対してtr -d '\n'
で改行を削除し(1行に繋げ)てsed
のコマンドライン引数に指定すれば、ファイル内のすべてのinclude文を一気に処理することができます。
$ resolve_embedded_resources="sed $(cat "$path" |
egrep -o "$embed_matcher" |
sort |
uniq |
$esed -e "$embed_mark_to_resolver" |
tr -d '\n')"
$ cat html/index.html |
$esed "s/($embed_matcher)( *[^$])/\1"\\$'\n''\3/g' |
eval "$resolve_embedded_resources" \
> html_resolved/index.html
ちなみに、-e
オプションの指定の中には丸括弧など拡張正規表現では特別な意味を持つ文字があるので、これらは本来スケープする必要があります。が、$esed
ではなくsed
を使うようにすればエスケープは不要です。
$resolve_embedded_resources
と直接書くのではなく、わざわざeval
コマンドを使ってeval "$resolve_embedded_resources"
と書いているのは、組み立てたコマンドラインオプションの'
が値の一部にならないようにするためです。というのも、そのままパイプラインの中に
cat ... | $resolve_embedded_resources | ...
と書くと、シェル変数が展開されてsed -e "'/<!--EMBED(metadata.html)-->/r html/_parts/metadata.html'" ...
のように書かれた扱いとなってしまい、sed
に「'
なんてコマンドは無い」と言われてしまからです。eval
を使えば、指定文字列を改めてシェルのコマンド列としてパースするため、sed -e '/<!--EMBED(metadata.html)-->/r html/_parts/metadata.html' ...
と書いたのと同じに扱われるようになります。
また、これだけだとinclude文自体がソースの中に残ってしまうので、ついでにそれらを消す置換操作の指定も加えるとこうなります。
$ embed_mark_to_resolver="s|($embed_matcher)| -e '/\\1/r html/_parts/\\2' -e '/^ *\\1 *$/d' -e 's/ *\\1 *//'|"
1つのinclude文から3つの-e
オプションができる形ですね。
さらに、これだとマッチしたinclude文の中にファイルパスのデリミタの/
が入った時に破綻してしまうので、マッチングパターンの正規表現を囲う文字を/
から;
に変えておきます。
$ embed_mark_to_resolver="s|($embed_matcher)| -e '\\\\;\\1;r html/_parts/\\2' -e '\\\\;^ *\\1 *$;d' -e 's; *\\1 *;;'|"
s
コマンドでは単に;
で囲うだけでいいですが、r
コマンドとd
コマンドについては\;マッチングパターン;
という風に最初に\
を付ける必要があります。それをまた全体として1つの文字列の中に入れているので、エスケープがたくさん並ぶ読みにくいスクリプトになってしまいました……まぁこれはしょうがないです。
以上を踏まえて前述のスクリプトの例を書き直すと、こうなります。
build.sh:共通パーツを埋め込む(改良版):
#!/bin/bash
case $(uname) in
Darwin|*BSD|CYGWIN*)
esed="sed -E"
;;
*)
esed="sed -r"
;;
esac
rm -rf html_resolved
cp -r html html_resolved
embed_matcher='<!-- *EMBED\( *([^) ]+) *\) *-->'
embed_mark_to_resolver="s|($embed_matcher)| -e '\\\\;\\1;r html/_parts/\\2' -e '\\\\;^ *\\1 *$;d' -e 's; *\\1 *;;'|"
egrep -r \
--include='*.html' \
"$embed_matcher" \
html_resolved |
cut -d ':' -f 1 |
uniq |
while read path
do
echo "updating $path"
resolve_embedded_resources="sed $(cat "$path" |
egrep -o "$embed_matcher" |
sort |
uniq |
$esed -e "$embed_mark_to_resolver" |
tr -d '\n')"
mv "$path"{,.tmp}
cat "$path.tmp" |
$esed "s/($embed_matcher)( *[^$])/\1"\\$'\n''\3/g' |
eval "$resolve_embedded_resources" \
> "$path"
rm "$path.tmp"
done
筆者が普段使用している環境で新旧それぞれのスクリプトをtime ./build.sh -f
という感じで実行して計測してみたところ、
環境 | 改修前のrealtime | 改修後のrealtime |
---|---|---|
Ubuntu on Let's note CF-SX3 | 3.217秒 | 0.644秒 |
Raspbian on Raspberry Pi2 Model B | 20.072秒 | 2.475秒 |
という感じで実時間で5~8倍の高速化となりました。ラズパイでも、カジュアルに全ファイルを処理させても気にならない程度まで高速になっています。万歳!
sed
やawk
だけでも頑張ればこういう事ができるのかもしれません。でも、ごく基本的な機能だけしか知らなくても「コマンドの実行結果でコマンド列を作る」という一工夫によってできる事の幅はかなり広がると思います。実際、この記事に「grep
の結果をcut | uniq
で加工しなくてもgrep -l
でいける」というフィードバックを頂きましたが、これもまさに「grep
の-l
オプションを知らなくても、基本的な文字列加工コマンドの組み合わせで目的は達成できる」という事の一例と言えるでしょう。
この記事をご覧になった皆さんも、「自分はどうせ基本的な使い方くらいしか知らないから……ガッツリ覚えるつもりもないし……」と卑屈にならず、ぜひ柔軟な発想で問題を解決してみてはいかがでしょうか?
cpp
コマンド)を使うここまで「include文のようなことをsed
でやる」という事を頑張ってみましたが、C言語のプリプロセッサ向けのinclude文そのものと同じ仕様でよければ、それこそCプリプロセッサをそのまま使うという方法もあります。
C言語のプリプロセッサはcpp
というコマンドとして単体で使う事ができ、UbuntuやDebianであればその名もcpp
というパッケージでインストールできます。Gemのパッケージ等でバイナリをビルドする必要がある物をインストールする際の依存関係で既にインストールされているという人も多いのではないでしょうか。これがインストールされている環境であれば、
html/index.html(ソースファイル):
<!DOCTYPE html>
<html lang="ja" xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
#include "html/_parts/metadata.html"
<title>シス管系女子って何!? - 【シス管系女子】特設サイト</title>
...
このようにソースに書いておいて
$ cat html/index.html |
cpp -P \
> html_resolved/index.html
と実行すれば、まさにC言語のソースと同じ要領でinclude文を処理した結果を得る事ができます(cpp
コマンドの-P
オプションは、プリプロセッサの行番号情報を出力しないようにする指定です。これを指定しないと、# 1 "<command-line>"
やら# 1 "html_resolved/index.html" 1
やらといった処理中のデバッグ情報的なメッセージまでが出力に含まれてしまいます)。
もちろん、include以外の構文も使えます。ただ、たまたまプリプロセッサ向けの書き方と同じ文字列があると意図せず処理されてしまうというリスクはあります。自分はC言語には詳しくなくて地雷を踏むのが怖いので、しこしこsed
で頑張ろうと思います……
Raspberry Pi 2にUbuntu(Lubuntu)を入れて、その後USB接続の外付けHDDを使うようにしたRaspberry Piの自宅サーバですが、ディスクがお亡くなりになったようです……
当該サーバに相乗りしていたみんとちゃんbotが2月24日16時半頃の自動ツイートを最後に停止していた。帰宅後普段使いのユーザ=みんとちゃんbotを動作させているユーザでsshでログインしようとするが、鍵がないと言われて蹴られる(そんな馬鹿な!)。管理作業用に用意してあった別のユーザではログインできた。
普段使いのユーザのhomeは外付けHDDにあり、管理作業用のユーザのhomeはラズパイ本体のMicroSDにあったので、この時点でもう外付けHDD自体がアクセス不能になっていたようだ。
その後再起動してみるもUbuntu自体が起動せず(起動プロセスの途中で止まってしまう)。
Ext4を扱える作業用PCを用意してMicroSDからとりあえず/logと/etcを取り出した後、メインとバックアップの外付けHDDをそれぞれUSBケーブルで作業PCに接続するも、以下のようなメッセージが/var/log/kern.log(dmesgでも可)に出て先に進まない。
Feb 27 23:54:22 hostname kernel: [ 482.750854] usb 2-2: new high-speed USB device number 4 using xhci_hcd
Feb 27 23:54:23 hostname kernel: [ 482.881646] usb 2-2: New USB device found, idVendor=0411, idProduct=024f
Feb 27 23:54:23 hostname kernel: [ 482.881649] usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Feb 27 23:54:23 hostname kernel: [ 482.881651] usb 2-2: Product: HD-LBVU3
Feb 27 23:54:23 hostname kernel: [ 482.881652] usb 2-2: Manufacturer: BUFFALO
Feb 27 23:54:23 hostname kernel: [ 482.881653] usb 2-2: SerialNumber: 000000270000F09E
Feb 27 23:54:23 hostname kernel: [ 483.068504] usb-storage 2-2:1.0: USB Mass Storage device detected
Feb 27 23:54:23 hostname kernel: [ 483.068580] scsi host4: usb-storage 2-2:1.0
Feb 27 23:54:23 hostname kernel: [ 483.068679] usbcore: registered new interface driver usb-storage
Feb 27 23:54:23 hostname kernel: [ 483.146849] usbcore: registered new interface driver uas
Feb 27 23:54:24 hostname kernel: [ 484.067523] scsi 4:0:0:0: Direct-Access BUFFALO External HDD 0000 PQ: 0 ANSI: 3
Feb 27 23:54:24 hostname kernel: [ 484.067952] sd 4:0:0:0: Attached scsi generic sg2 type 0
Feb 27 23:54:55 hostname kernel: [ 514.996705] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:55:26 hostname kernel: [ 546.102442] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:55:57 hostname kernel: [ 577.080222] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:56:28 hostname kernel: [ 608.058034] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:56:28 hostname kernel: [ 608.187252] sd 4:0:0:0: [sdb] Read Capacity(10) failed: Result: hostbyte=DID_TIME_OUT driverbyte=DRIVER_OK
Feb 27 23:56:28 hostname kernel: [ 608.187256] sd 4:0:0:0: [sdb] Sense not available.
Feb 27 23:56:59 hostname kernel: [ 639.100029] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:56:59 hostname kernel: [ 639.229451] sd 4:0:0:0: [sdb] Write Protect is off
Feb 27 23:56:59 hostname kernel: [ 639.229457] sd 4:0:0:0: [sdb] Mode Sense: 00 00 00 00
Feb 27 23:57:30 hostname kernel: [ 670.081799] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:57:30 hostname kernel: [ 670.211139] sd 4:0:0:0: [sdb] Asking for cache data failed
Feb 27 23:57:30 hostname kernel: [ 670.211156] sd 4:0:0:0: [sdb] Assuming drive cache: write through
Feb 27 23:58:01 hostname kernel: [ 701.119601] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:58:32 hostname kernel: [ 732.097607] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:59:03 hostname kernel: [ 763.075505] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 27 23:59:34 hostname kernel: [ 794.053339] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:00:05 hostname kernel: [ 825.031343] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:00:36 hostname kernel: [ 856.137236] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:00:36 hostname kernel: [ 856.266439] sd 4:0:0:0: [sdb] Read Capacity(10) failed: Result: hostbyte=DID_TIME_OUT driverbyte=DRIVER_OK
Feb 28 00:00:36 hostname kernel: [ 856.266447] sd 4:0:0:0: [sdb] Sense not available.
Feb 28 00:01:07 hostname kernel: [ 887.115166] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:01:38 hostname kernel: [ 918.093122] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:02:09 hostname kernel: [ 949.071135] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:02:40 hostname kernel: [ 980.113167] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:02:40 hostname kernel: [ 980.242580] sd 4:0:0:0: [sdb] Attached SCSI disk
Feb 28 00:03:11 hostname kernel: [ 1011.155150] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:03:42 hostname kernel: [ 1042.133159] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:04:13 hostname kernel: [ 1073.111069] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:04:44 hostname kernel: [ 1104.089034] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:05:15 hostname kernel: [ 1135.071017] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:05:46 hostname kernel: [ 1166.108960] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
Feb 28 00:06:17 hostname kernel: [ 1197.151034] usb 2-2: reset high-speed USB device number 4 using xhci_hcd
取り出してあったラズパイのログを見てみると、/var/log/kern.log(kern.log.4.gz)に1月下旬からエラーが出ていた。
Jan 23 07:42:11 hostname kernel: [6895164.358213] sd 1:0:0:0: rejecting I/O to offline device
Jan 23 07:42:11 hostname kernel: [6895164.358268] sd 1:0:0:0: rejecting I/O to offline device
Jan 23 07:42:11 hostname kernel: [6895164.376947] sd 1:0:0:0: rejecting I/O to offline device
Jan 23 08:48:38 hostname kernel: [6899151.391096] EXT4-fs (sdb1): error count since last fsck: 1815
Jan 23 08:48:38 hostname kernel: [6899151.391146] EXT4-fs (sdb1): initial error at time 1482879680: __ext4_get_inode_loc:3798: inode 129499236: block 517996582
Jan 23 08:48:38 hostname kernel: [6899151.391172] EXT4-fs (sdb1): last error at time 1485039158: ext4_find_entry:1289: inode 128602714
Jan 24 05:00:01 hostname kernel: [6971834.191579] sd 1:0:0:0: rejecting I/O to offline device
Jan 24 07:55:57 hostname kernel: [6982390.836437] sd 1:0:0:0: rejecting I/O to offline device
Jan 24 07:55:57 hostname kernel: [6982390.836522] EXT4-fs warning: 43 callbacks suppressed
Jan 24 07:55:57 hostname kernel: [6982390.836538] EXT4-fs warning (device sdb1): __ext4_read_dirblock:674: error -5 reading directory block (ino 128199902, block 0)
Jan 24 07:55:57 hostname kernel: [6982390.836599] sd 1:0:0:0: rejecting I/O to offline device
Jan 24 07:55:57 hostname kernel: [6982390.836632] EXT4-fs warning (device sdb1): __ext4_read_dirblock:674: error -5 reading directory block (ino 128199902, block 0)
ログローテートされていない最新の/var/log/kern.logは295MBあって(ローテートされた方は300KBもない)、そちらはこんな感じ。
Feb 24 09:45:59 hostname kernel: [9667395.780613] EXT4-fs (sdb1): error count since last fsck: 2139
Feb 24 09:45:59 hostname kernel: [9667395.780665] EXT4-fs (sdb1): initial error at time 1482879680: __ext4_get_inode_loc:3798: inode 129499236: block 517996582
Feb 24 09:45:59 hostname kernel: [9667395.780692] EXT4-fs (sdb1): last error at time 1485815831: ext4_find_entry:1289: inode 128602714
Feb 24 12:07:43 hostname kernel: [9675899.677825] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:09:01 hostname kernel: [9675977.758202] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:10:33 hostname kernel: [9676070.622174] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:11:05 hostname kernel: [9676101.654074] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:11:15 hostname kernel: [9676111.814096] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:11:31 hostname kernel: [9676127.974069] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:11:31 hostname kernel: [9676128.150102] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:11:41 hostname kernel: [9676138.310126] usb 1-1.2: reset high-speed USB device number 4 using dwc_otg
Feb 24 12:11:41 hostname kernel: [9676138.399352] sd 0:0:0:0: Device offlined - not ready after error recovery
Feb 24 12:11:41 hostname kernel: [9676138.399399] sd 0:0:0:0: [sda]
Feb 24 12:11:41 hostname kernel: [9676138.399412] Result: hostbyte=DID_ABORT driverbyte=DRIVER_OK
Feb 24 12:11:41 hostname kernel: [9676138.399427] sd 0:0:0:0: [sda] CDB:
Feb 24 12:11:41 hostname kernel: [9676138.399437] Write(16): 8a 00 00 00 00 00 0e 9d f4 e2 00 00 00 08 00 00
Feb 24 12:11:41 hostname kernel: [9676138.399510] blk_update_request: I/O error, dev sda, sector 245232866
Feb 24 12:11:41 hostname kernel: [9676138.399539] EXT4-fs warning (device sda1): ext4_end_bio:317: I/O error -5 writing to inode 7077983 (offset 0 size 4096 starting block 30654109)
Feb 24 12:11:41 hostname kernel: [9676138.399561] Buffer I/O error on device sda1, logical block 30654104
Feb 24 12:11:41 hostname kernel: [9676138.399635] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.399658] sd 0:0:0:0: [sda] killing request
Feb 24 12:11:41 hostname kernel: [9676138.399702] sd 0:0:0:0: [sda]
Feb 24 12:11:41 hostname kernel: [9676138.399739] Result: hostbyte=DID_NO_CONNECT driverbyte=DRIVER_OK
Feb 24 12:11:41 hostname kernel: [9676138.399755] sd 0:0:0:0: [sda] CDB:
Feb 24 12:11:41 hostname kernel: [9676138.399763] Write(16): 8a 00 00 00 00 00 ae 84 88 9a 00 00 00 30 00 00
Feb 24 12:11:41 hostname kernel: [9676138.399836] blk_update_request: I/O error, dev sda, sector 2927921306
Feb 24 12:11:41 hostname kernel: [9676138.401773] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.401825] EXT4-fs warning (device sda1): ext4_end_bio:317: I/O error -5 writing to inode 7078075 (offset 0 size 4096 starting block 30654110)
Feb 24 12:11:41 hostname kernel: [9676138.401848] Buffer I/O error on device sda1, logical block 30654105
Feb 24 12:11:41 hostname kernel: [9676138.402111] Aborting journal on device sda1-8.
Feb 24 12:11:41 hostname kernel: [9676138.402166] EXT4-fs error (device sda1) in ext4_reserve_inode_write:4764: Journal has aborted
Feb 24 12:11:41 hostname kernel: [9676138.402233] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.402343] JBD2: Error -5 detected when updating journal superblock for sda1-8.
Feb 24 12:11:41 hostname kernel: [9676138.402376] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.402485] EXT4-fs error (device sda1): mpage_map_and_submit_extent:2129: comm bash: Failed to mark inode 7077983 dirty
Feb 24 12:11:41 hostname kernel: [9676138.402503] EXT4-fs (sda1): previous I/O error to superblock detected
Feb 24 12:11:41 hostname kernel: [9676138.402572] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.402663] EXT4-fs error (device sda1) in ext4_writepages:2420: Journal has aborted
Feb 24 12:11:41 hostname kernel: [9676138.402681] EXT4-fs (sda1): previous I/O error to superblock detected
Feb 24 12:11:41 hostname kernel: [9676138.402736] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.410089] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:11:41 hostname kernel: [9676138.410149] EXT4-fs warning (device sda1): ext4_end_bio:317: I/O error -5 writing to inode 7077983 (offset 0 size 4096 starting block 30653956)
Feb 24 12:11:41 hostname kernel: [9676138.410174] Buffer I/O error on device sda1, logical block 30653951
Feb 24 12:12:42 hostname kernel: [9676199.410548] EXT4-fs (sda1): previous I/O error to superblock detected
Feb 24 12:12:42 hostname kernel: [9676199.410719] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 12:12:42 hostname kernel: [9676199.410826] EXT4-fs error (device sda1): ext4_journal_check_start:56: Detected aborted journal
Feb 24 12:12:42 hostname kernel: [9676199.410846] EXT4-fs (sda1): Remounting filesystem read-only
Feb 24 12:12:42 hostname kernel: [9676199.410856] EXT4-fs (sda1): previous I/O error to superblock detected
Feb 24 12:12:42 hostname kernel: [9676199.410890] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.720835] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.720999] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.721346] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.721443] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.722797] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.722917] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.723035] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.723109] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.723303] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.723381] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.723476] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.723542] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.723634] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.723697] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.723786] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.723850] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.723941] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.724003] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.724113] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.724177] EXT4-fs warning (device sda1): __ext4_read_dirblock:884: error -5 reading directory block (ino 7078003, block 114)
Feb 24 16:28:21 hostname kernel: [9691537.724267] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.724399] sd 0:0:0:0: rejecting I/O to offline device
Feb 24 16:28:21 hostname kernel: [9691537.724533] sd 0:0:0:0: rejecting I/O to offline device
...
(以下同じログが延々300万行ほど)
...
認識されてる順番的に、バックアップ側(/dev/sdb1)の方がまず死んで、その後メイン側(/dev/sda1)が死んだという感じでしょうか。
fstabでこれらのディスクを自動的にマウントするように書いてあったので、Raspberry Piが起動途中で止まるのはそのせいかもしれない。後でfstabを書き換えて試してみようと思う。
あと筐体を分解して中のHDDを取り出して直接接続したらデータを読めたという話もあるみたいなので、これも試してみないといけない。
仮にデータを取り出せたとして、今後どうするか。普通にメインとバックアップの2台を用意してrsyncするようにしてただけだと、今回のようにバックアップ側が先に死んだことに気がつけない。死活監視とかちゃんとやらないとなんだろうけど、結局の所「ちゃんと動いてるかどうか」って機械的にスッキリ判定できる物なのかどうかよく分からない。理想を言うと、RAID1みたいなクラスタになってるか1日おきにメインとバックアップが入れ替わるかみたいになってて、普通に使ってて「何かおかしいぞ」とすぐ気付けるようになってるといいのかなと思うんだけど、それはそれで「壊れた方をコピー元にして壊れた物を無事な物に上書きしてしまう」問題がやっぱりあるわけで。
エラー検知までしてくれるようなちゃんとしたNASの製品を買うのが一番なのかな……もういい歳なんだし金で解決できる物は金で解決するのが手っ取り早いよね。
類似モデルの分解事例を見ながら分解してみたところ、中身はSeagateのSATAのディスクでした。
これを手持ちのSATA→USB変換器に繋いでみたところ、1台はウンともスンとも言わなかったのですが、もう1台の方はシーク音らしき音がし始めてkern.logにもディスクとして認識できたっぽいメッセージが出始めたので「やった!」と思った……のもつかの間、
Error mounting /dev/sdb1 at /media/piro/buffalo-external: Command-line `mount -t "ext4" -o "uhelper=udisks2,nodev,nosuid" "/dev/sdb1" "/media/piro/buffalo-external"' exited with non-zero exit status 32: mount: wrong fs type, bad option, bad superblock on /dev/sdb1,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so.
てなダイアログが出て、マウントされずにそのまま無反応になってしまいました。認識されかけた時もkern.logにはEXT4-fsのエラーがぽろぽろ出ていたので、ハードウェア障害が根本にあってさっきのが最後の一息みたいなもんだったっぽいです。
もうこりゃ自分の手に負えないわと諦めて、でも思い出の写真とかこの中にしかないデータもあるので、金額次第ですが専門の業者に依頼してみようかと思ってます。「HDD データ復旧 Ext4」とかで検索したら業者がいくつか出てきたので、とりあえず見積もりから……(経費扱いで計上したら控除対象にならんかなこれ)
あ、そんな感じで肝心のHDDはさっぱりだったのですがラズパイの方はあっさり復旧できました。問題の外付けHDDを認識させるための設定を/etc/fstabに直書きしてたのをコメントアウトして、他にも外付けHDDの中のディレクトリへのシンボリックリンクになってた部分を全部普通のディレクトリに改めてみた所、無事Lubuntuのログイン画面まで辿り着けました。でもこれだけ復活しても、記憶を全部なくした抜け殻みたいなもんだからあんまり意味無い……
昨年、このツイートを見たんですよ。
夏コミでポルリン(@porurin0 )さんと湊川あい(@llminatoll )さんとお会いしました!
— もっちー黒蜜きな粉フレンズ (@singen_motti) 2016年9月2日
おふたりとも、とてもいい方でした!これからも応援してます٩( ๑╹ ×╹)۶
カフェちゃんとHTMLちゃんの共演でーすwww pic.twitter.com/RlgmYf1PU6
それで「うらやましすぎる!!!」とテンション爆上がりになってしまった勢いで妄想グッズの絵を描いてしまったりなんかしまして。
作者さんが「カフェちゃんブックエンド https://t.co/kYaHsUBNHj いいなあ、見てたら #シス管系女子 もブックエンド作りたくなってきた! 」って言って妄想画像作られたみたいですよ〜😲💦 まさかの実用的グッズ⁉ pic.twitter.com/1iYo4hF3D0
— 利奈みんとbot/マンガでLinux! (@sysadgirl_mint) 2016年9月10日
いやね、元々シス管系女子も何かグッズ作りたいなあとは思ってたんですよ。でも、ステッカーとかマグカップとかの比較的すぐ作れそうな物で読者の方に喜んで頂けるイメージがわかなくて。
思えば、以前Mozilla JapanのFirefoxマーケティング活動のお手伝いをしてた頃にストラップや紙袋やフォクすけぬいぐるみのデザインをやらせてもらった時も、やれ「印刷のペラいのじゃなくてちゃんとしたラバーストラップの方が絶対満足感ありますって!!」だの「紐の色と紙袋本体のコントラストが大事なんですよ!!」だの「もっと鼻の所がツンと出てた方がカワイイですって!!」だのと好き勝手駄目出しして、自分が素直に欲しいと思える物を他人のお金で作ってもらうというヤクザなことをしていたのでした(ぬいぐるみは根来さんの駄目出しもすごかったけど)。
でも、Firefoxならロゴマークがかっこいいからそれだけで満足感あるけど、自作の美(少)女イラストとなると、まず美(少)女という時点で照れの方が勝ってしまうし、そもそも自分の絵自体がそんなに上手なわけじゃないし……と色々考えてしまって、何作っても素直に自分で使える気がしなかったのです。
そんな折に見かけたのが冒頭の写真。見た瞬間に「これだ!!!っていうか自分が欲しいわ!!!!」と思いましたね。
だってシス管系女子って一応技術の本で学習(解説)マンガですやん。「これ使って本棚が技術書で埋まるくらいにいっぱい勉強しましょう!」っていうの、シャレが効いててよくないすか? 作品コンセプトにめっちゃマッチしてません? いや「今どき紙の本かよ」って呆れられそうですけども……
あと、仕様上どうしたって単色にならざるを得なくて、でもそれが却ってポップでかっこよくね?とも思いましたし。自分の絵ってそんな上手な方じゃないから丁寧に描いたり塗ったりすればするほどアラが目立って死にたい気持ちが増してくるけど、デザイン的に処理すれば見る側の脳内で勝手に補って見てもらえて実物以上に良く見えそうだし。
そう思ったらもう止まらなくて、妄想絵だけじゃ満足できず、実現できないものかと水面下でなんやかや動いていたのです。日経BPからは予算が出ない自主制作で、完全に趣味の世界です。
そしてついに実現された試作品がこちら!!!
まさにイラストの通りの仕上がり!!!! 素晴らしい!!!!
ですがひとつ難点が。
単色の場合はまだマシで、絵が入ってきちゃうと
もうワケが分からないことに……
これは正直盲点でした。デザイン画の時点で表紙画像と合わせたりすれば一発で分かる事だったのに、それを怠ったばかりに、試作品で実物を見るまでこの問題に全く気付いてなかったという。
なので泣く泣く一からやり直しました。
明暗反転版です。
といっても、単純に図案の明暗を反転するだけだとパーツが宙に浮いてしまう箇所が結構ありました。そこで、妄想イラストの単純化された図案から「元絵」にあたる線画を起こして、そこから改めて各所の線を拾い上げる形で図案化するという事をした結果がこれです。
これなら、多少うるさい内容の表紙と合わせても顔がちゃんと判別できます(真っ白の紙でも、本の表紙に対してブックエンド自体の影が落ちるので)。
しかも、タイツの部分を抜いたので、本の表紙の色や柄がそのままみんとちゃんのタイツの色や柄になります。つまりタイツの履き替え遊びができます。試しに手元の本をいくつか合わせてみました。
濃い色はもちろん合いますし……
文字が入っててもへっちゃら。
帯部分だけ色が違うのも良いですね。
オライリー柄とか。
英字柄とか。
淡い色もいいですね。
試作2号で勝利が見えたので、これをベースに微修正した物を最小ロットで量産して、シス管系女子Advent Calendar 2016にご参加頂いた皆様にお礼として贈答した残りを4月9日の技術書典2に持っていこうと思ってます。利益をほとんど載せない状態でも数千円にはなってしまいます(少数生産だとどうしても割高になってしまう……かといって個人で何百個もこんなかさばる物を発注しても手に負えませんし)が、もし良かったら手に取ってみて頂ければと思います。
以下、デザインするときに分かったこととかコツとかをメモしておきます。
基本的には切り絵の要領なんですが、切り絵の中でも全パーツが繋がってるタイプの形になってないといけないというのがポイントです。
試作1号の図案の時は顔の肌部分を抜いて輪郭を残すデザインにしようとしたのですが、そうすると普通に絵を描くと鼻と口がどうしても輪郭に繋がらないパーツになってしまいます。なので、顔の角度やポーズを工夫してそれらのパーツに髪や目や膝が接するようにすることで、どうにか浮かない形でパーツを残すことができました。
明暗反転版では鼻や口のように短い線は逆に穴を開けるだけなので簡単だったのですが、顔全体を残して輪郭を穴にする場合、輪郭を全部繋げないでちょっとだけ橋渡しする部分を残してやらないといけません。あまり橋をかけすぎると見た目が悪いと思って、目立たない所(普通に線画を描く時に輪郭を途切れさせるような所)に絞って橋をかけてみました。ただ、試作2号では数を絞りすぎて頭の曲線部分が完全に枠から切り離されてしまい、枠が歪むと頭の方が飛び出てしまうようだったので、強度を増すために量産版では頭と枠の間に2箇所橋を増やそうと思ってます。
ということで、ご報告という体裁でのただの見せびらかし記事でした。
2016年4月2日でに始めて、途中からペースダウンしつつも同年12月25日に100枚を達成したので、これを一区切りとして振り返ってみます。
シス管系女子 イラスト100枚まとめ❗/2016春 シス管系女子 イラスト100枚まとめ❗/2016夏 シス管系女子 イラスト100枚まとめ❗/2016秋・冬
背景事情の要点を整理すると、
そんな感じで、現状分析も甘ければ効果測定の方法も定義できないまま、とにかく数を増やさねばという焦燥感にのみ駆られて始めたのでした。
広報的な成果は、期待したほどには無かったように思います。
タイムラインによく艦これ等のラフなイラストが流れてきていたので勘違いしてしまっていましたが、あれは作品のファン同士のコミュニケーション、あるいは神絵師とそのフォロワーの方々のコミュニケーションという文脈のものであって、すでにある巨大なコミュニティからこぼれ落ちる雫のようなものなのですよね。焦りと「これくらいならやれるかも」という妥協とで完全に見誤ってしまっていました。
それに、シス管系女子というコンテンツの最も大事な価値は解説する事にこそあるわけで、日常の一コマを切り取ってもプレビューになりません。宣伝としてやるなら1枚絵ではなく、本編同様の解説絵にするべきだったのだと今となっては思います。1枚絵で喜んで頂けるのは既存のファンの方だけですし、落描きレベルの雑なイラストではそれすらも……
自分の中で絵を描く事の心理的ハードルが下がったのは、収穫と言えると思います。学生時代にはノートの隅に落描きをしていましたが、社会人になってからはその機会がなくなり、なにか特別な理由がないと絵を描かないようになってしまっていました。1日に1時間だけでも無理やりにでも絵を描く時間を設けたことで、季節のイラストもそれほど気負わず描くことができました。
副次的な効果として、絵描きの方との交流のきっかけになったのも良かったです。絵を描く事のハードルが下がったことで、他の人へのお祝いイラストや、オリジナルキャラの絵等を描きやすくなりました。億劫がっていたら、気に入った絵の描き手の方にご挨拶すらできないままだったでしょう。
また、なるべく手をかけずに見栄えのする絵を描くノウハウが多少は身に付いたとも思っています。例えば、自分の場合はペン入れ工程が最も時間を食うため、それをスキップするやり方を取るようになりました。また、背景や小物などについて、今まではいつも律儀に線画を仕上げてから塗っていたのを、いきなり塗りから始めるというやり方を取れるようになりました。夏に描いたヒマワリや七夕の笹、仙台七夕の飾り等は、このやり方でなければもっと時間がかかっていたでしょう。
総評としては、よく頑張ったけどピントがずれてたね、というあたりでしょうか。
来年以降は、無理の無い範囲までペースを落としつつも、当初の目的である広報に繋がるように、解説を主にしたイラストの割合を増やしていきたいです。
今までアドベントカレンダーには熱心に参加した事はなかったと思うのですが、今年はシス管系女子の草の根広報活動の一環として、自分でもびっくりするくらい力を注いでおりました。
まず、自分で初めてシス管系女子 Advent Calendar 2016というアドベントカレンダーを立てました。IT系のクリスマスといえばアドベントカレンダーが定番という印象があったので、思いつきでのチャレンジです。
とはいえ、現状のWebでの認知度を鑑みるに読者の方のご協力だけで全日程はまず埋まらないだろうと見込んでいたため、全25コマの構成で事前にマンガを用意しておき、最悪の場合でも1日1コマ公開していけば日程は埋められるという準備を整えた上でスタートしました。Webでの試し読み代わりに自由に使える話を増やしたいという動機が元々あったので、「描くつもりだった話を描ければそれでまず成功。アドベントカレンダー関係のブームに乗っかって露出が増えれば一石二鳥。読者の方のご協力を得られれば一石三鳥」というつもりでした。
蓋を開けてみると、およそ3割の日程を読者の方に寄稿して頂けており、予想以上の結果に大変嬉しい思いをしております。自分なんかの思いつきに乗っかってくれた方がこんなにいた事、エールを頂けた事がとても励みになりました。本当にありがとうございます。
一方で、課題も色々あったと思っています。
以上を総合すると、今回誰が一番得をしたかというと自分自身だった(自由に使える特別編を1つ増やす契機になった、読者の方々からのエールを頂けて元気が出た)という気がします。つまり俺得。自己満足に付き合わせるだけになってしまってすみませんでした……こんな風に人の厚意を食んで自分のやる気に変えるような事ばかりしてたら信頼を損なうばかりですよね。ほんとに。
元々、自分から見つけてきてくれる人だけを対象にしていても認知は広がらないので、どこか「外」に出ていく必要があるという事は認識していました。
そんな折、Geek Women Japan 2016の懇親会で、「シス管系女子」を読まれた方から本編で扱っていなかった話についての質問を頂きました。それに対する回答を記事化して公開するにあたり、ちょうどそのタイミングで各アドベントカレンダーの参加募集が始まっていたため、「外」のアドベントカレンダーに参加すればその読者層に認知を広げる機会になるのでは?と思い至りました。ただ、無差別に参加して宣伝をばらまくだけではただのspam行為なので、
といったあたりの事を考えながらエントリーを増やしていった結果、以下の8記事ができました。
結論としては、この試みは一定の成功を見たと思っています。特にShellscript Advent Calendarに投稿した記事がどういうわけか若干バズってくれて、ここからの流入が突出して多かったです。この記事は日を開けて何度か紹介されていて、その度にアクセスが発生するという状況になっていました。
ただ、このアクセス増は狙って起こせたものではなく(記事のタイトルを付ける際に若干挑発的なタイトルを意識したのは事実ですが……)、それ以外の記事のPVはそれほど伸びなかった事、また増加したアクセスも継続的なものではなくあくまでスパイク状の一過性の増加に留まった事から、やるならもっと継続的にやった方が良さそうという事は思っています。そうする事で、「シス管系女子」の内容や技術レベルに対する懐疑的な見方を払拭する材料を増やせれば、という思いもあります。
自分のサイト内でコンテンツを公開するよりも、技術情報であればQiitaのように、多くの人が見ていて且つ情報をシェアしやすい場所で公開するようにした方が良いという事も実感しました。
会社の業務の一環で、Groonga Advent Calendar 2016にもGroonga名義でいくつか記事を投稿しました。
これらはGroongaの知名度向上や盛り上がり感の演出、既存ユーザ・新規ユーザ向けの情報の整備を目的に行いましたが、シス管系女子の場合と同様、これ自体が知名度向上に役立ったという事は言えなさそうです。
以上をまとめると、認知度向上のための手段としてアドベントカレンダーを使う時は、既に人が多く集まっている場所(サイトもそうだし、アドベントカレンダーもそう)に飛び込んでいくのが有効なようです。 という、当たり前といえば実に当たり前の話なのでした。
このエントリはGit Advent Calendarとのクロスポストです。(→Qiitaの方の投稿)
今日の記事では、「SSHって何?」や「SSHは知ってるし時々使うけど、普段そんなに使う機会は無い」くらいのレベルの方を対象に、SSHとGitの組み合わせだけでこんな事もできるんですよ!という事をご紹介します。アドベントカレンダー的には最後の方の日なのに初心者向けの話で恐縮ですが、まあせっかくなのでおつきあい下さい。
このエントリは以下のエントリのフォローアップです。
(また、Qiitaにもクロスポストしています。)
結論を先に書くと、シェルスクリプトの中で普通のプログラミング言語で文字列を区切り文字で分割して配列にする操作、いわゆるsplit()
相当の事はtr '区切り文字' '\n'
でできます。その逆の、配列を結合して1つの文字列にする操作、いわゆるjoin()
相当の事はpaste -s -d '区切り文字' -
と覚えておくのが筆者的にはオススメです。
(ちなみに、GNU coreutilsのコマンドでjoin
という物がありますが、これは配列のjoin()
ではなく、SQLで言うところのINNER JOIN
とかOUTER JOIN
とかの方の文脈の「join」に対応する物です。この記事の話とは関係ないので、忘れて下さい。)
以下、どういう場合にそれが言えるのかという事と、その理由の解説です。