The provision of aliases is a fairly basic part of a shell that Fish has lacked until now. I have added a simple form of aliasing that is exemplified by the following example:
fish alias plp 'show -q "has njr/lastpage" /about'
This creates an alias called plp that expands to the text given so that plp will show the about tags for any objects tagged with the njr/lastpage tag. If I run this command now I get:
$ fish plp 1 object matched Object f79d5ea3-50c1-4c9e-b98e-7bbe46b69ee1: /fluiddb/about = "http://www.guardian.co.uk/"
because the page tagged with njr/lastpage is currently the Guardian’s website.
A couple of syntactic details to note about these aliases:
The alias only applies to the first (non-flag) word in the command.
Any arguments that follow the aliased term are added to the substituted command. So, for example, the aliasalias parisrating 'show -a "Paris" rating'
if invoked asfish parisrating /alice/rating
will show both my rating of Paris (as specified in the alias), and Alice’s, as specified in the command, viz:$ fish parisrating /alice/rating Object with about="Paris": /njr/rating = 9 /alice/rating = "smelly"
There is no provision for using positional arguments such as $1 yet. This will change, as surely as night follows day.
So far, so boring. But here’s something slightly more interesting: where should Fish store its aliases?
The question need only be posed for the answer to present itself: obviously, Fish should store aliases in Fluidinfo; as it does. This turns out to be quite interesting.
Fish stores aliases on objects whose about tag is the alias; in other words, the alias parisrating is stored on the object whose about tag is parisrating:
$ fish tags -a parisrating Object with about="parisrating": /fluiddb/about = "parisrating" /njr/.fish/alias = "show -a "Paris" rating"
As you can see, the alias is stored in a tag called njr/.fish/alias, and the value of that tag is simply the expansion text. (I know, I know: the output is confusing, embedding, as it does, double quotes in a double-quoted string, without any escaping. There is an excuse, but not one that’s worth the electrons.)
Both the .fish namespace and the .fish/alias tag are private, by default, as you can see:
$ fish ls -ld .fish .fish/alias nrwc------ njr/.fish/ trwc------ njr/.fish/alias
or if you prefer your permissions Fluidinfo-style:
$ fish ls -Ld .fish .fish/alias njr/.fish/: read: policy: closed; exceptions = [njr] write: policy: closed; exceptions = [njr] control: policy: closed; exceptions = [njr] njr/.fish/alias: read: policy: closed; exceptions = [njr] write: policy: closed; exceptions = [njr] control: policy: closed; exceptions = [njr]
Of course, the user can change this.
Storing the alias this way involves a small leakage of potentially private information, in the sense that people can see that an object with the about tag parisrating exists, though not that anyone is using it as an alias, or who is doing so, or what the alias expands to. I could have avoided this by using anonymous objects and another .fish tag instead of the about tag, but I think the approach I’ve adopted is better overall. If you share my philosphical perspective that objects for every possible about tag already exist, but are lazily instantiated, there is no leakage, but obviously in the non-platonic Amazon server farm where the data is hosted, Plato’s writ does not run. (This is probably a good thing; imagine what Amazon would charge to host Fluidinfo’s data if we admitted that every possible string is stored.)
The principal advantage of storing the alias in Fluidinfo is that it is available from anywhere. So if I have Fish on several machines (and I do) I can create the alias once and use it from everywhere. In principle, I can also use it from the online version of Fish (Shell-Fish), but that isn’t implemented yet, which is one of the reasons I haven’t committed this to GitHub yet.
There is also a downside to storing aliases in Fluidinfo, which is that retrieving the definitions requires a Fluidinfo query. Given that alias expansion precedes command matching (so that built-in Fish commands can be replaced), this lookup is required before each command is evaluated; for Fish’s single-shot mode, where a single Fish command is entered from a Unix or Windows prompt, that introduces a delay I am not keen to accept.
For this reason, the Fish cache has been born.
The Fish cache is simply a dump of a Fish’s internal representation of certain Fluidinfo objects to local storage. Specifically, on Unix, Fish writes a file (a pickle file) to ~/.fishcache.username where username is the authenticated user’s username. When Fish is invoked with arguments, it reads the cache, which includes all the objects used for aliases.
When an alias is created (or deleted), Fish first makes the change in Fluidinfo, then updates the cache.
There is then a new sync command, which updates the cache from Fluidinfo. It is important to be clear that this synchronization does not involve any kind of mediation: the cache is cleared and updated from Fluidinfo; Fluidinfo is the source of truth.
This means that in order to get any aliases (or alias deletions) performed by a different copy of Fish, you need to perform a sync operation.
When Fish is invoked without arguments, Fish performs a sync before accepting any input.
The plan for the online version of Fish is very similar to the interactive version except that instead of using local files, the online version will store its cache in the “local” database (which, in the case of Google App Engine, means the Data Store).
I think this approach holds great promise, not only for aliases, but for other important Fish data, probably including configuration parameters.
The alias command is summarized as follows:
alias [name [expansion-text]]
With no parameters, alias lists all aliases and their expansions.
With a single parameter, alias lists the expansion for the alias specified (if it exists).
With two or more parameters, alias defines (or redefines) an alias. It is best to quote the expansion text as a single parameter to stop Fish from interpreting it, though in simple cases this is strictly unnecessary. Here are some examples:
$ fish alias book 'abouttag book' $ fish book 'Fugitive Pieces' 'Anne Michaels' book:fugitive pieces (anne michaels) $ fish alias book book: njr/.fish/alias = "abouttag book" $ fish alias book: njr/.fish/alias = "abouttag book" parisrating: njr/.fish/alias = "show -a "Paris" rating" plp: njr/.fish/alias = "show -q "has njr/lastpage" /about"
There is also an unalias command. You can probably guess how it works. To remove the three aliases above you would say:
fish unalias book parisrating plp
showcache and sync
Finally, the showcache command can be used to show the contents of the cache, and the sync command can be used to update it from Fluidinfo.
In this particular case, I deleted the parisrating alias on another machine with this result:
$ sync $ showcache Cache: fluiddb/about="plp": njr/.fish/alias = "show -q "has njr/lastpage" /about" fluiddb/about="book": njr/.fish/alias = "abouttag book"
Today, the cache stores only aliases, so the output from showcache tends to look rather similar to that from alias, but as other types of data start to be cached, a sharper distinction will be drawn.
I have a good feeling about this.
If you are using Fluidinfo directly, and are not taking advantage of the /values API, you really should check it out: it is dramatically faster. I have been a bit slow to upgrade Fish to use it, but that is now happening incrementally, and is yielding very significant and welcome performance improvements everywhere. Think of the difference between drinking beer with a straw and glugging it straight from the glass; there is simply no comparison.
[Thanks to @joannescrub for taking the time to point out a number of typos in this post.]