A rust binary and shell function to make exporting/aliasing less painful
How often do you need to export something or make an alias and decide you really want that globally available? If you are like me, you do this quite often, but are just too lazy to add it to your alias or export file after you have it working in your shell. With Assimilate you can alias/export to your current shell and/or directly to your alias/export file.
Assimilate is comprised of two parts, a rust
binary and a shell function
. The rust
binary handles all the heavy lifting and provides the nice cli
interface, while the shell function
enables you to alias/export to the current shell. You can use just the rust
binary, but you will not be able to alias/export to the current shell.
$> assimilate_bin --help
> USAGE:
assimilate_bin [FLAGS] --name <name> [item]...
FLAGS:
-a, --alias
-e, --export
--help Prints help information
-h, --here
-s, --save
-V, --version Prints version information
OPTIONS:
-n, --name <name>
ARGS:
<item>...
The default behavior is to export and you must provide either -h
or -s
for any action to be taken.
Additionally, if you want to --save
to your alias/export file, you will need to have already exported one or both of the following ...
EXPORT_FILE=<path to export file>
ALIAS_FILE=<path to alias file>
$> assimilate -ahn foo echo bar
# --> result: alias foo='echo bar'
$> assimilate -ehn MY_OTHER_HOME '$HOME/subdir'
# --> result: export MY_OTHER_HOME='$HOME/subdir'
$> assimilate -ehn EXPANDED_HOME $HOME/subdir
# --> result: export EXPANDED_HOME='<path_to_your_$HOME>/subdir'
# see discussion subsection Shell Expansion
$> dig +short myip.opendns.com @resolver1.opendns.com -4
# <your ip address>
$> assimilate -asn myip "!!"
# alternatively: assimilate -asn myip -- !!
# see discussion subsection Shell Expansion
# --> result alias myip='dig +short myip.opendns.com @resolver1.opendns.com -4'
Because you are specifying arguments on the command line you will want to pay attention to shell expansion.
Be aware that the shell function
utilizes eval
in order to alias/export in the current shell.
Currently you must clone and build from source ...
$> git clone https://github.com/fvhockney/assimilate.git
$> cd assimilate
# Make sure you have a rust toolchain installed
$> cargo build --release
# make sure that target/release/assimilate_bin is in your $PATH
if you want to use the shell function ...
--- in someplace which will run on shell startup
source <path to assimilate git dir>
alternatively if you want to just use the rust binary
$> ln -s assimilate_bin /usr/sbin/assimilate
This boils down to three issues:
First is Process context. If you want to alias/export in the current shell, you must perform that opertion within the context of that shell. Running the command in a seperate binary causes a subprocess which can not affect the parent/starting process. However, executing alias
/export
from within a shell function
is successful because it is running in the same process context.
Second is Process pollution. Writing everything within the shell function
is certainly feasible (in fact, I did just this in one iteration), but this leads very easily to polluting the running process with lots of variables/functions. Sure, I could probably get around the function problem by using declarative programming, but functions are just sooooo much better. The variable problem is much more insidious, you need variables for parsing the command line arguments plus quite a number of holder variables. Cleaning them up with unset is generally possible, but since you are in a function, you can't really utilize traps to capture errors and ensure clean up. Additionally, unsetting varialbes from a sourced function is tricky as they may not reliably be re-instantiated when the function is called again. Putting the bulk of the work load in a seperate binary prevents a great deal of process pollution.
Third is Modularity. Maybe you don't care about setting it in the current process. Fine, you can just use the binary and not worry about sourcing the shell function
.
Third the second, Portablity. The shell function is, to the best of my abilities, POSIX
compliant. Writing the whole shebang (pun intended) in a POSIX
manner without requiring the user to install any dependencies (ok, fine, most people probably have sed
, tr
, cut
and the like already installed), is quite the challenge. Plus, I would really like to expand this to allow history navigation and other functionality which would have just been a nightmare in pure shell
. Git* et al pipelines coupled with releases and packaging files makes it easy for users to install and use, even without having to install the rust
toolchain.
I really don't like eval
. It's kind of like seeing unsafe
. Even if it is safe, something just turns in my stomach, but alas, because of items 1 and 2, I could find no other way of handling this problem. Essentially, if the rust
binary exits with 0
, then the shell function eval
s the result. If it exits with anything else, it echo
s the result and returns
with the original exit code. This means we only have to save two variables in the current scope (__assimilate_exit_code
and __assimilate_output
).
will get you every time. Know how shell expansion works on your shell and don't let it bite you in the rear. The rust
binary will always '
the right hand side of the expression so that the eval
statement does not expand if you did not want it to.
- interactive history lookup
-
parse
option to handle commands likeexport foo='bar'; assimilate -se "!!"
- tests of binary
- package (if there is enough interest)
- CI/CD with releases
- better help documentation
- man pages?