List of File-System - text.Baldanders.info
tag:text.Baldanders.info,2020-09-06:/tags
2020-09-06T16:18:45+09:00
帰ってきた「しっぽのさきっちょ」
https://text.baldanders.info/images/avatar.jpg
https://text.baldanders.info/images/avatar.jpg
次期 Go 言語で導入される(かもしれない) io/fs パッケージについて予習する
tag:text.Baldanders.info,2020-09-06:/golang/file-system-interface-in-go-2/
2020-09-06T07:18:45+00:00
2022-08-18T12:26:25+00:00
ツリー型のディレクトリ・ファイル構成のファイルシステムを操作するパッケージに対して統一した interface 型を提供して互換性を高めようというわけだ。
Spiegel
https://baldanders.info/profile/
<p>先日行われた “<a href="https://gocon.connpass.com/event/186317/">Go 1.15 Release Party in Japan</a>” で<a href="https://gist.github.com/tenntenn/fe8995c347a5e1000832d3c6942f1fbe" title="Draft designを読む · GitHub">紹介</a>されていた File System Interfaces のドラフト案について予習がてら覚え書きとして記しておく。</p>
<ul>
<li><a href="https://go.googlesource.com/proposal/+/master/design/draft-iofs.md">File System Interfaces for Go — Draft Design</a></li>
</ul>
<figure style='margin:0 auto;text-align:center;'>
<div style="position: relative; margin: 0 2rem; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;">
<iframe class="youtube-player" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" allowfullscreen frameborder="0" src="https://www.youtube-nocookie.com/embed/yx7lmuwUNv8" allowfullscreen></iframe>
</div>
<figcaption><div><a href="https://www.youtube.com/watch?v=yx7lmuwUNv8">io/fs draft design - YouTube</a></div></figcaption>
</figure>
<p>たとえば <code>/path/to/filename.txt</code> のようにツリー型のディレクトリ・ファイル構成のファイルシステムは多い。
メジャーな OS のファイルシステムは大抵そうだし Web のパスや書庫ファイル(<code>*.tar</code> や <code>*.zip</code> など)もツリー型のディレクトリ・ファイル構成になっている。</p>
<p>たとえば <a href="https://go.dev/">Go</a> の標準パッケージ</p>
<ul>
<li><a href="https://pkg.go.dev/archive/zip" title="zip package · pkg.go.dev"><code>archive/zip</code></a></li>
<li><a href="https://pkg.go.dev/html/template" title="template package · pkg.go.dev"><code>html/template</code></a></li>
<li><a href="https://pkg.go.dev/net/http" title="http package · pkg.go.dev"><code>net/http</code></a></li>
<li><a href="https://pkg.go.dev/os" title="os package · pkg.go.dev"><code>os</code></a></li>
<li><a href="https://pkg.go.dev/text/template" title="template package · pkg.go.dev"><code>text/template</code></a></li>
</ul>
<p>などは(ほぼ)同じツリー型だが使い方やメソッド名などが微妙に異なっている。
またサードパーティ製のパッケージでは, <a href="https://github.com/rakyll/statik" title="rakyll/statik: Embed files into a Go executable"><code>rakyll/statik</code></a> のように,実行モジュールにディレクトリ・ファイルをまるっと埋め込めるものもあったりする<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p>こういったパッケージに対して統一した interface 型を提供して互換性を高めようというわけだ。
したら,テストとかもやり易くなるしね(笑)</p>
<h2>fs.FS 型と fs.File 型</h2>
<p><a href="https://go.googlesource.com/proposal/+/master/design/draft-iofs.md" title="File System Interfaces for Go — Draft Design">ドラフト案</a>では <code>io/fs</code> パッケージを新たに作ってファイルシステムの汎化を定義するようだ。</p>
<p>まず,ファイルシステムの汎化型 <code>fs.FS</code> は以下のように定義するらしい。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">FS</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Open</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">File</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>また <code>fs.FS.Open()</code> メソッドの返り値になっている <code>fs.File</code> 型は</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">File</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Stat</span><span class="p">()</span> <span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">FileInfo</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Read</span><span class="p">([]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Close</span><span class="p">()</span> <span class="kt">error</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>と定義される。</p>
<p>たとえば,通常のファイルの読み書きについて</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">myFS</span> <span class="kd">struct</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">NewFS</span><span class="p">()</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">FS</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="o">&</span><span class="nx">myFS</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">fsys</span> <span class="o">*</span><span class="nx">myFS</span><span class="p">)</span> <span class="nf">Open</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">File</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">os</span><span class="p">.</span><span class="nf">Open</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>みたいに定義すれば</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">f</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">NewFS</span><span class="p">().</span><span class="nf">Open</span><span class="p">(</span><span class="s">"no-exist.txt"</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="k">defer</span> <span class="nx">f</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="c1">//Output:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="c1">//open no-exist.txt: no such file or directory
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>てな感じに書ける。
どやさ!</p>
<p>ちなみにディレクトリ区切り文字はスラッシュ “<code>/</code>” で(実際のファイルシステムに関わらず)統一するらしい。
また相対パス指定で “<code>.</code>” や “<code>..</code>” は使えないようにするようだ。
まぁ,実際にはパス変換関数とか必要になるかもしれないね。</p>
<h2>ファイルシステム・インタフェースの拡張</h2>
<p>上述の説明だと「<a href="https://pkg.go.dev/net/http" title="http package · pkg.go.dev"><code>http</code></a><code>.FileSystem</code> 型を使えばええんちゃうん?」となる。
実際 <a href="https://pkg.go.dev/net/http" title="http package · pkg.go.dev"><code>http</code></a><code>.FileSystem</code> 型は</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">File</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">io</span><span class="p">.</span><span class="nx">Closer</span>
</span></span><span class="line"><span class="cl"> <span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span>
</span></span><span class="line"><span class="cl"> <span class="nx">io</span><span class="p">.</span><span class="nx">Seeker</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Readdir</span><span class="p">(</span><span class="nx">count</span> <span class="kt">int</span><span class="p">)</span> <span class="p">([]</span><span class="nx">os</span><span class="p">.</span><span class="nx">FileInfo</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Stat</span><span class="p">()</span> <span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">FileInfo</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">FileSystem</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Open</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">File</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>と定義されているため <code>fs.FS</code> / <code>fs.File</code> 型とほぼ変わらない<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</p>
<p>駄菓子菓子。</p>
<p><code>io/fs</code> パッケージでは拡張機能を定義した型も用意するらしい。</p>
<p>たとえばファイル情報を取得する <code>Stat()</code> メソッドを含む</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">StatFS</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">FS</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Stat</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">FileInfo</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>や,ディレクトリエントリを読む機能を含む</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">ReadDirFS</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">FS</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ReadDir</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">([]</span><span class="nx">os</span><span class="p">.</span><span class="nx">FileInfo</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>といった interface 型も用意されている。</p>
<p>他にもファイルの内容を一括で読み込める</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">ReadFileFS</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">FS</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ReadFile</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>や <code>Glob()</code> メソッドが使える</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">GlobFS</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">FS</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Glob</span><span class="p">(</span><span class="nx">pattern</span> <span class="kt">string</span><span class="p">)</span> <span class="p">([]</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>も用意するようだ。
実際にはこれらの interface 型を組み合わせて使うことになると思われる。</p>
<h2>【2020-12-14 追記】</h2>
<p>2021年2月リリース予定の <a href="https://go.dev/">Go</a> 1.16 で実行モジュールへの任意のファイルの埋め込み機能が公式にサポートされるらしい。</p>
<ul>
<li><a href="https://mattn.kaoriya.net/software/lang/go/20201030092005.htm">Big Sky :: Go に go:embed が入った。</a></li>
<li><a href="https://qiita.com/convto/items/4b43072b05e6efdf8dd7">Go1.16で追加されるembedとio/fsパッケージについてざっと調べた - Qiita</a></li>
<li><a href="https://qiita.com/cia_rana/items/e5758816393498d2c50f">go:embed 詳解 - 使用編 - - Qiita</a></li>
</ul>
<p>うまくすればこの記事の <code>io/fs</code> パッケージとも将来的に統合されるかもね。
楽しみ!</p>
<h2>ブックマーク</h2>
<ul>
<li><a href="https://gist.github.com/tenntenn/fe8995c347a5e1000832d3c6942f1fbe">Draft designを読む · GitHub</a></li>
<li><a href="https://text.baldanders.info/golang/using-statik-package/">rakyll/statik でシングルバイナリにまとめる</a></li>
<li><a href="https://text.baldanders.info/golang/abstract-filesystem/">fs.FS を使ってディレクトリ・ファイルを参照する</a></li>
</ul>
<h2>参考図書</h2>
<div class="hreview">
<div class="photo"><a href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1"><img src="https://m.media-amazon.com/images/I/416Stewy0NS._SL160_.jpg" width="123" alt="photo"></a></div>
<dl>
<dt class="item"><a class="fn url" href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1">プログラミング言語Go</a></dt>
<dd>アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)</dd>
<dd>丸善出版 2016-06-20 (Release 2021-07-13)</dd>
<dd>Kindle版</dd>
<dd>B099928SJD (ASIN)</dd>
<dd>評価<abbr class="rating fa-sm" title="5"> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i></abbr></dd>
</dl>
<p class="description">Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想は<a href="https://text.baldanders.info/remark/2016/07/go-programming-language/" >こちら</a>。</p>
<p class="powered-by">reviewed by <a href='#maker' class='reviewer'>Spiegel</a> on <abbr class="dtreviewed" title="2021-05-22">2021-05-22</abbr> (powered by <a href="https://affiliate.amazon.co.jp/assoc_credentials/home">PA-APIv5</a>)</p>
</div> <!-- プログラミング言語Go -->
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>実行モジュールにディレクトリ・ファイルを埋め込めるパッケージとしては <a href="https://github.com/jteeuwen/go-bindata" title="jteeuwen/go-bindata: Hard fork of jteeuwen/go-bindata because it disappeared, Please refer to issues#5 for details."><code>jteeuwen/go-bindata</code></a> や <a href="https://github.com/jessevdk/go-assets" title="jessevdk/go-assets: Simple embedding of assets in go"><code>jessevdk/go-assets</code></a> が有名だが,これらは最早メンテナンスされていないので使わないほうがいい。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:2">
<p>たとえば <a href="https://github.com/rakyll/statik" title="rakyll/statik: Embed files into a Go executable"><code>rakyll/statik</code></a> パッケージではファイルシステムの生成・初期化関数 <code>fs.New()</code> の返り値は <a href="https://pkg.go.dev/net/http" title="http package · pkg.go.dev"><code>http</code></a><code>.FileSystem</code> 型である。 <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>