2 minutes
Written: 2024-07-22 00:09 +0000
Updated: 2024-08-06 00:53 +0000
xonsh Excursions
Background
Recently I found myself writing a bunch of search and replace one-liners.
1export FROM='Matrix3d'; export TO='Matrix3S'; ag -l $FROM | xargs -I {} sd $FROM $TO {}
Which works, especially since both ag
and sd
are rather good, but it is still:
- Slightly non-ergonomic to type
- Difficult to keep track of
- Modulo dumping everything in a
.sh
file
- Modulo dumping everything in a
These reminded me of the rich set of alternate shells1.
Reaching for xonsh
Although nushell
, elvish
and even oil
seemed promising, I settled on the
Python based xonsh.
1pipx install xonsh[full]
2pipx inject xonsh xcontrib-sh
3echo 'xontrib load sh' >> ~/.xonshrc
Where we use the injection mechanism to add the xcontrib-sh plugin for running
shell commands without using xonsh
, since we might not want to translate every
command to be compliant with xonsh
.
Additions
xonsh-mode
works well withemacs
and is on MELPA- xxh makes it pretty trivial to take into foreign SSH machines
Scripting substitutions
Directly leveraging xonsh
we can rewrite the earlier bash
script into:
1from pathlib import Path
2
3def replace_make_shared():
4 FROM_MAKE = 'Matter'
5 TO_MAKE = 'Matter'
6
7 files = $(ag -l @(FROM_MAKE)).splitlines()
8
9 for f in files:
10 if Path(f).exists():
11 sd @(FROM_MAKE) @(TO_MAKE) @(f)
12 else:
13 echo "File not found: @(file)"
14
15replace_make_shared()
Leveraging Python
It is instructive to recall how this would work in pure python
.
1import subprocess
2from pathlib import Path
3
4def replace_make_shared():
5 FROM_MAKE = 'std::shared_ptr<Matter>'
6 TO_MAKE = 'Matter*'
7
8 result = subprocess.run(['ag', '-l', FROM_MAKE],
9 capture_output=True, text=True)
10 files = result.stdout.splitlines()
11 actionable_files = [x for x in files if 'migrator' not in x]
12
13 for f in actionable_files:
14 file_path = Path(f)
15 if file_path.exists():
16 subprocess.run(['sd', FROM_MAKE, TO_MAKE,
17 str(file_path)], check=True)
18 else:
19 print(f"File not found: {file_path}")
20
21replace_make_shared()
With sh, shell calls become more ergonomic, however, it is still easier to
interoperate between the shell outputs and Python code (e.g. using echo
) using
xonsh
.
Conclusions
Personally, xonsh
hits the sweet-spot of being slightly less finicky than
POSIX shells while being less verbose than the pure python
variant. It isn’t
perfect though:
- The
python
dependence, even withpipx
can be an issue 2- Packages which are needed have to be injected into the same environment..
nix
maybe…
- Packages which are needed have to be injected into the same environment..
- There is no good testing framework
- Ugly
subprocess
basedpytest
tests could be written
- Ugly
For now, though, this is sufficiently advantageous.
True of all python packages ↩︎