The File Filesystem (2021)

https://mgree.github.io/ffs/

The Unix shell is a powerful tool, and the Unix ecosystem provides an incredible array of tools for working with strings. But the shell really only knows how to work with one data structure: the filesystem. Modern systems use all kinds of semi-structured data, like JSON or YAML. These semi-structured formats are essentially trees, and string processing is a bad match—editing JSON with sed is not a very good idea!

Demo of in-place editing, starting from an empty JSON object to building up a compound one; transcript is below

ffs—short for the file filesystem—lets you mount semi-structured data as a filesystem, letting you work with modern formats using your familiar shell tools.

Currently, ffs supports JSON, YAML, and TOML, with more to come.

You can read more about it in the ffs manpage.

Examples

Run ffs [file] to mount file.blah at the mountpoint file. The final, updated version of the file will be outputted on stdout.

$ cat object.json 
{ "name": "Michael Greenberg", "eyes": 2, "fingernails": 10, "human": true }
$ ffs -o object_edited.json object.json &
[1] 60182
$ tree object
object
├── eyes
├── fingernails
├── human
└── name
0 directories, 4 files
$ echo Mikey Indiana >object/name
$ echo 1 >object/nose
$ mkdir object/pockets
$ cd object/pockets/
$ echo keys >pants
$ echo pen >shirt
$ cd ..
$ cd ..
$ umount object
$ 
[1]+  Done                    ffs -o object_edited.json object.json
$ cat object_edited.json 
{"eyes":2,"fingernails":10,"human":true,"name":"Mikey Indiana","nose":1,"pockets":{"pants":"keys","shirt":"pen"}}

Notice a few things: the nose key of the resulting object has a number as its value, not a string; the pockets directory got turned into an object.

You can specify an explicit mountpoint by running ffs -m MOUNT file; you can specify an output file with -o OUTPUT. You can edit a file in place by running ffs -i file (as in the gif above)—when the volume is unmounted, the resulting output will be written back to file. Here’s the transcript:

~/ffs/demo $ echo '{}' >demo.json
~/ffs/demo $ ffs -i demo.json &
[1] 56827
~/ffs/demo $ cd demo
~/ffs/demo/demo $ echo 47 >favorite_number
~/ffs/demo/demo $ mkdir likes
~/ffs/demo/demo $ echo true >likes/dogs
~/ffs/demo/demo $ echo false >likes/cats
~/ffs/demo/demo $ touch mistakes
~/ffs/demo/demo $ echo Michael Greenberg >name
~/ffs/demo/demo $ echo https://mgree.github.io >website
~/ffs/demo/demo $ cd ..
~/ffs/demo $ umount demo
~/ffs/demo $ 
[1]+  Done                    ffs -i demo.json
~/ffs/demo $ cat demo.json 
{"favorite_number":47,"likes":{"cats":false,"dogs":true},"mistakes":null,"name":"Michael Greenberg","website":"https://mgree.github.io"}~/ffs/demo $ 
~/ffs/demo $

Getting ffs

On Linux you need FUSE; on macOS, you need macFUSE. You can then download a single executable. These are the latest development builds:

See the release page for particular releases; the current version is 0.1.2. You can also build ffs from source.

Learn more

Check out the paper “Files-as-Filesystems for POSIX Shell Data Processing” from PLOS 2021. The pre-recorded demo is above; the pre-recorded talk is below.

Tools like jq and gron are meant to help you work with JSON on the command line. They’re great tools!

Why might ffs be the right choice for you?

  • ffs supports multiple formats.

  • ffs lets you edit using familiar shell tools.

  • ffs doesn’t involve learning a new language.

Why might ffs not be the right choice for you?

  • You use Windows. (Sorry. 😥)

  • You can’t use FUSE.

  • You only need to search, not edit.

  • Your files are very large.

License

ffs is licensed under GPLv3.

{
"by": "wegwerff",
"descendants": 101,
"id": 40213731,
"kids": [
40220211,
40215710,
40214516,
40216398,
40214962,
40216762,
40214576,
40220409,
40214271,
40214524,
40226898,
40220821,
40225294,
40215763,
40233272,
40237202,
40214230,
40217942,
40216715,
40215480,
40219028,
40215697,
40214314
],
"score": 346,
"time": 1714498992,
"title": "The File Filesystem (2021)",
"type": "story",
"url": "https://mgree.github.io/ffs/"
}
{
"author": "[Michael Greenberg](http://mgree.github.io)",
"date": null,
"description": "mount semi-structured data (like JSON) as a Unix filesystem",
"image": "https://mgree.github.io/ffs/assets/images/inplace_demo.gif",
"logo": null,
"publisher": "ffs",
"title": "ffs: the file fileystem",
"url": "https://mgree.github.io/ffs/"
}
{
"url": "https://mgree.github.io/ffs/",
"title": "ffs: the file fileystem",
"description": "The Unix shell is a powerful tool, and the Unix ecosystem provides an incredible array of tools for working with strings. But the shell really only knows how to work with one data structure: the filesystem....",
"links": [
"https://mgree.github.io/ffs/"
],
"image": "",
"content": "<section>\n <p>The Unix shell is a powerful tool, and the Unix ecosystem provides an\nincredible array of tools for working with strings. But the shell\nreally only knows how to work with one data <em>structure</em>: the\nfilesystem. Modern systems use all kinds of\n<a target=\"_blank\" href=\"https://en.m.wikipedia.org/wiki/Semi-structured_data\"><em>semi-structured</em></a>\ndata, like JSON or YAML. These semi-structured formats are essentially\ntrees, and string processing is a bad match—editing JSON with sed is\nnot a very good idea!</p>\n<p><img src=\"https://mgree.github.io/ffs/assets/images/inplace_demo.gif\" alt=\"Demo of in-place editing, starting from an empty JSON object to building up a compound one; transcript is below\" /></p>\n<p>ffs—short for the <strong>f</strong>ile <strong>f</strong>ile<strong>s</strong>ystem—lets you mount\nsemi-structured data as a filesystem, letting you work with modern\nformats using your familiar shell tools.</p>\n<p>Currently, ffs supports <a target=\"_blank\" href=\"https://www.json.org/\">JSON</a>,\n<a target=\"_blank\" href=\"https://yaml.org/\">YAML</a>, and <a target=\"_blank\" href=\"https://toml.io/en/\">TOML</a>, with more\nto come.</p>\n<p>You can read more about it in <a target=\"_blank\" href=\"https://mgree.github.io/ffs/ffs.1.html\">the <code>ffs</code> manpage</a>.</p>\n<h2 id=\"examples\">Examples</h2>\n<iframe width=\"560\" height=\"315\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"></iframe>\n<p>Run <code>ffs [file]</code> to mount <code>file.blah</code> at the mountpoint <code>file</code>. The\nfinal, updated version of the file will be outputted on stdout.</p>\n<div><pre><code><span>$</span><span> </span><span>cat </span>object.json \n<span>{ \"name\": \"Michael Greenberg\", \"eyes\": 2, \"fingernails\": 10, \"human\": true }\n</span><span>$</span><span> </span>ffs <span>-o</span> object_edited.json object.json &amp;\n<span>[1] 60182\n</span><span>$</span><span> </span>tree object\n<span>object\n├── eyes\n├── fingernails\n├── human\n└── name\n0 directories, 4 files\n</span><span>$</span><span> </span><span>echo </span>Mikey Indiana <span>&gt;</span>object/name\n<span>$</span><span> </span><span>echo </span>1 <span>&gt;</span>object/nose\n<span>$</span><span> </span><span>mkdir </span>object/pockets\n<span>$</span><span> </span><span>cd </span>object/pockets/\n<span>$</span><span> </span><span>echo </span>keys <span>&gt;</span>pants\n<span>$</span><span> </span><span>echo </span>pen <span>&gt;</span>shirt\n<span>$</span><span> </span><span>cd</span> ..\n<span>$</span><span> </span><span>cd</span> ..\n<span>$</span><span> </span>umount object\n<span>$</span><span> \n</span><span>[1]+ Done ffs -o object_edited.json object.json\n</span><span>$</span><span> </span><span>cat </span>object_edited.json \n<span>{\"eyes\":2,\"fingernails\":10,\"human\":true,\"name\":\"Mikey Indiana\",\"nose\":1,\"pockets\":{\"pants\":\"keys\",\"shirt\":\"pen\"}}\n</span></code></pre></div>\n<p>Notice a few things: the <code>nose</code> key of the resulting object has a\nnumber as its value, not a string; the <code>pockets</code> directory got turned\ninto an object.</p>\n<p>You can specify an explicit mountpoint by running <code>ffs -m MOUNT file</code>;\nyou can specify an output file with <code>-o OUTPUT</code>. You can edit a file\nin place by running <code>ffs -i file</code> (as in the gif above)—when the\nvolume is unmounted, the resulting output will be written back to\n<code>file</code>. Here’s the transcript:</p>\n<div><pre><code><span>~/ffs/demo $</span><span> </span><span>echo</span> <span>'{}'</span> <span>&gt;</span>demo.json\n<span>~/ffs/demo $</span><span> </span>ffs <span>-i</span> demo.json &amp;\n<span>[1] 56827\n</span><span>~/ffs/demo $</span><span> </span><span>cd </span>demo\n<span>~/ffs/demo/demo $</span><span> </span><span>echo </span>47 <span>&gt;</span>favorite_number\n<span>~/ffs/demo/demo $</span><span> </span><span>mkdir </span>likes\n<span>~/ffs/demo/demo $</span><span> </span><span>echo true</span> <span>&gt;</span>likes/dogs\n<span>~/ffs/demo/demo $</span><span> </span><span>echo false</span> <span>&gt;</span>likes/cats\n<span>~/ffs/demo/demo $</span><span> </span><span>touch </span>mistakes\n<span>~/ffs/demo/demo $</span><span> </span><span>echo </span>Michael Greenberg <span>&gt;</span>name\n<span>~/ffs/demo/demo $</span><span> </span><span>echo </span>https://mgree.github.io <span>&gt;</span>website\n<span>~/ffs/demo/demo $</span><span> </span><span>cd</span> ..\n<span>~/ffs/demo $</span><span> </span>umount demo\n<span>~/ffs/demo $</span><span> \n</span><span>[1]+ Done ffs -i demo.json\n</span><span>~/ffs/demo $</span><span> </span><span>cat </span>demo.json \n<span>{\"favorite_number\":47,\"likes\":{\"cats\":false,\"dogs\":true},\"mistakes\":null,\"name\":\"Michael Greenberg\",\"website\":\"https://mgree.github.io\"}~/ffs/demo $</span><span> \n</span><span>~/ffs/demo $</span><span>\n</span></code></pre></div>\n<h2 id=\"getting-ffs\">Getting ffs</h2>\n<p>On Linux you need <a target=\"_blank\" href=\"https://github.com/libfuse/libfuse\">FUSE</a>; on\nmacOS, you need <a target=\"_blank\" href=\"https://osxfuse.github.io/\">macFUSE</a>. You can then\ndownload a single executable. These are the <a target=\"_blank\" href=\"https://github.com/mgree/ffs/releases/tag/latest\">latest development builds</a>:</p>\n<ul>\n <li><a target=\"_blank\" href=\"https://github.com/mgree/ffs/releases/download/latest/ffs.linux\">Linux</a></li>\n <li><a target=\"_blank\" href=\"https://github.com/mgree/ffs/releases/download/latest/ffs.macos\">macOS</a></li>\n</ul>\n<p>See the <a target=\"_blank\" href=\"https://github.com/mgree/ffs/releases\">release page</a> for\nparticular releases; the current version is 0.1.2. You can also build\nffs from <a target=\"_blank\" href=\"https://github.com/mgree/ffs\">source</a>.</p>\n<h2 id=\"learn-more\">Learn more</h2>\n<p>Check out the paper <a target=\"_blank\" href=\"https://dl.acm.org/doi/10.1145/3477113.3487265\">“Files-as-Filesystems for POSIX Shell Data\nProcessing”</a> from\n<a target=\"_blank\" href=\"https://plos-workshop.org/2021/\">PLOS 2021</a>. The pre-recorded demo is\nabove; the pre-recorded talk is below.</p>\n<iframe width=\"560\" height=\"315\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"></iframe>\n<p>Tools like <a target=\"_blank\" href=\"https://stedolan.github.io/jq/\">jq</a> and\n<a target=\"_blank\" href=\"https://github.com/tomnomnom/gron\">gron</a> are meant to help you work\nwith JSON on the command line. They’re great tools!</p>\n<p>Why might ffs be the right choice for you?</p>\n<ul>\n <li>\n <p>ffs supports multiple formats.</p>\n </li>\n <li>\n <p>ffs lets you edit using familiar shell tools.</p>\n </li>\n <li>\n <p>ffs doesn’t involve learning a new language.</p>\n </li>\n</ul>\n<p>Why might ffs <em>not</em> be the right choice for you?</p>\n<ul>\n <li>\n <p>You use Windows. (Sorry. 😥)</p>\n </li>\n <li>\n <p>You can’t use FUSE.</p>\n </li>\n <li>\n <p>You only need to search, not edit.</p>\n </li>\n <li>\n <p>Your files are very large.</p>\n </li>\n</ul>\n<h2 id=\"license\">License</h2>\n<p>ffs is licensed under\n<a target=\"_blank\" href=\"https://raw.githubusercontent.com/mgree/ffs/main/LICENSE\">GPLv3</a>.</p>\n </section>",
"author": "[Michael Greenberg](http://mgree.github.io)",
"favicon": "",
"source": "mgree.github.io",
"published": "",
"ttr": 107,
"type": "website"
}