I use the command line heavily and over the years have migrated from bash
to zsh
as a daily driver shell. I usually use a slightly customized oh-my-zsh environment, but some systems are on prezto; the differences are not large.
The most productive plugins I've been using for zsh are zsh-syntax-highlighting
and history-substring-search
, and lately I've been using the very powerful fzf
plugins for pulling up history.
Now, I'm finding one of the biggest pain points that remain for me in the command line is command argument reordering. Quite often I try to run a command
command very/long/filesystem/path/to/argumentA another/filesystem/or/network/path/argumentB
and realize I've got the order backwards.
Another even more common situation is when we do any "manual deployment" workflow: First you compare the new stuff with the real stuff, e.g.
diff /opt/app/static/www/a.html /home/user/docs/dev/src/a.html
cp /home/user/docs/dev/src/a.html /opt/app/static/www/a.html
Ok, ok, last example (this one has several steps), no more I promise. Perfect real world example right here. Let's get cracking with some file listing with sweet human sizes:
find /pool/backups -type f -print0 | xargs -0 ls -lh > filelisting
I want to size sort and pick some out interactively:
sort -rhk5 filelisting | fzf -m > /these/are/the_chosen
Nice, that works! Oh but I need just the paths now, but don't want to re-run find
:
cut -d ' ' -f 10 /these/are/the_chosen
The output is garbage, because we encountered a setback with ls -lh
getting frisky with spaces. But I've got a strategy: Join the contiguous spaces. Let's opt for tr -s
to squeeze space chars, no need for a regex here. Though, tr
requires stdin:
cat /these/are/the_chosen | tr -s ' ' | cut -d ' ' -f 9- > filelist
By this point, we're feeling the pain of cutting and pasting arguments around in commands. My choices here are always between awkward alternatives: I can move the long path or i can move the commands. With either move, I have to either reach over for the mouse to copy it, or i have to type it again in the new spot. I can't win. With a command line, even navigating around is cumbersome, and extremely so without word hopping hotkeys set up.
I can't even use my mouse to jump around rapidly on a shell prompt! (Hey, does anyone know of a shell that supports mouse events?) So, this is the inefficiency that I want to abolish. I want to eliminate the friction of grabbing a shell object (such as a valid path string) and move it freely left and right while I prototype out monster pipelines. If I had that feature I could spam that 5 times to shove the path to the left of the cut
& flags, then construct the rest of the pipeline organically.
I believe the lack of line editing power is what the issue is here. In the very first example where I want to transpose the first 2 args, I can create a trivial shell script that perhaps I'd call cpto
that inverts the arguments and delegates to the cp
command. But I don't want to have to do that, and it would not help me in the general case, like in the third example.
I'd like to be able to reorder the arguments that I've entered using a simple key combination, like I can do for various types of lists if I'm in Vim with plugins like sideways.
Does such a plugin exist for zsh? Does such a plugin exist for any other shells?
If not, how difficult would it be to implement for zsh? I think that the zsh-syntax-highlighting plugin proves that it should be possible to tokenize arguments. Indeed the shell knows how to fetch individual arguments from history: https://stackoverflow.com/a/21439621/340947
The pain point is so severe and common that I'm liable to write a simple script to bind to a hotkey that grabs the last entry in history and swaps the last 2 args for me, and runs that. But that would not be as ideal as having a line editor operation so that the swap can be done interactively rather than committing to run the command.
Perhaps an improvement on that could be injecting !:0 !:2 !:1
(which zsh nicely auto expands for me) but there are plenty of problems with this also: (1) it won't work without already having attempted to run the wrong command. More than half the time I want to swap args after catching myself after having typed an incorrect command, and (2) often there are flags that were used which that snippet would fail to account for.
I've also seen the approach shown here which is fine but remains tremendously unsatisfying as the keystrokes need to be repeated a lot for long paths, and the Ctrl+Y behavior only recalls the most recent item that was cut, rather than hold a stack of them. It's good to know, but practically useless to me.
For completeness' sake, the tactic taken now is to use whatever suitable key combo to delete words to erase the shorter of the commands to reorder, reposition the cursor, use the mouse to copy the deleted argument from terminal output, and paste it back in. Ordinary folk don't bat an eye at this but it makes me die a little every time I do it because I cannot stop thinking about how easy it would be for the computer to do this task for me, and the injustice that I feel having to reach my hand over to the mouse.
This is cool for simple args like flags (which are usually order independent! ha!), but when given a path, this does the (possibly useful, but largely not) transposition of the last 2 dirs in the path, not the entire paths themselves. And even if it could group by actual Word, it'd probably fail to properly abide by paths with spaces in them entered with escaped spaces.
– Steven Lu Jan 09 '20 at 21:41system()
, where the system will actually just spawn the default shell (/bin/sh
) to tokenize the string and call back to the proper process API. – Lie Ryan Jan 10 '20 at 06:35zsh -f
meaning oh-my-zsh also appears to be breaking its intended behavior. These issues with omz are piling up and starting to be somewhat annoying. I will renew efforts to switch completely to prezto. And... I'm sure it all works fine with bash too. I just don't spend much time in bash at interactive shell. – Steven Lu Jan 10 '20 at 20:22fzf
user: would it be enough to have a function (also bound to a keystroke?) that takes all the arguments from the last command, and pipes them throughfzf
, and lets you multi-select the ones you want to keep, in the order you want to keep them? (In my experience, the output offzf
is in the order that the items were selected.) – iconoclast Jan 10 '20 at 21:04