1

similar to the existing $_ which I learned stands for !-1:$, I would like to create aliases for $__, $___ and so on which refer to the 2nd or 3rd -last command. I have tried adding

alias "$__"='!-2:$'

in my .zshrc.local. If possible, I would like to write a zsh-function which gives back the 1st argument of the n-th last command based on the amount of underscores.

arch linux kernel 5.1.4-arch
zsh 5.7.1 (x86_64-pc-linux-gnu)

1 Answer 1

1

This functionality already exists

You don't need anything complicated to access the last word of previous commands. Just press ESC-. (i.e. Alt+.) or ESC-_ (i.e. Alt+_). This invokes the editor command insert-last-word, which inserts the last word from the previous command line. Press the key again to get the last word from the command line before that, and so on. If you press ESC-. one time too many, use C-_ (undo) to go back to the word you had just before.

This command isn't bound to a key by default in vi mode, but you can bind it with bindkey.

You can pass a numerical argument to get a different word: positive to start on the right (1 is the last word), zero or negative to start on the left (0 is the first word which is generally the command name, 1 is the word after that which is the first argument, etc.). For example ESC . ESC - ESC 1 ESC . inserts the first argument of the next-to-last command.

Many variations on this command are possible by defining your own widget around zle insert-last-word. Zsh comes with copy-earlier-word and smart-insert-last-word which you may find useful either to use as is or as code examples.

If you really want $__ to expand to the last word of the previous-but-one command, I'll give some solutions below, but first I need to explain what's going on.

Why your attempt isn't working

First, you aren't defining what you think you're defining. alias "$__"=… defines an alias whose name is the current value of the variable __ at the time the alias definition is executed. This is probably empty, so you're executing alias ='!-2:$' which looks for a command called '!-2:$' on the search path (the = expansion part of filename expansion). To define an alias called $__, you need to pass $__ to the alias command, e.g. with alias '$__'=… or alias \$__=….

Second, an alias is only expanded in command position, i.e. as the first word of a command (after any leading variable assignments and redirections). In order for this alias to be useful, it would need to be a global alias: alias -g '$__'=…

Third, this alias wouldn't do anything useful, because alias expansion happens after history expansion.

darkstar
darkstar% alias -g '$__'='!-2:$'
darkstar% echo $__
!-2:$

$_ does not “stand for” !-1:$. $_ and !-1:$ are two ways to access the same information in common cases. You can say that $_ “is an alias” of !-1:$, or conversely that !-1:$ “is an alias” of $_, but that's using “alias” in its general English sense, not in the technical sense of shell aliases, and it's imprecise because the two don't always have the same value. !-1:$ is a history expansion (!) construct which expands to the last word (:$) of the previous command line (-1). $_ is a parameter expansion using the parameter _ which the shell sets to the last argument of the previous command. It makes a difference if you run command lines that aren't exactly one simple command, for example:

darkstar% for x in 1 2 3; do echo $x; done
1
2
3
darkstar% echo $_ is not !-1:$
echo $_ is not done
3 is not done
darkstar% echo $_ and !-1:$ are different; echo $_ and !-1:$ are different
echo $_ and done are different; echo $_ and done are different
done and done are different
different and done are different

Defining $__ per command

You can define a trap function called TRAPDEBUG which runs before executing each command. Remember the current value of $_ (note that you have to do this first, because the first command inside the trap will overwrite _), then “shift” the multiple-underscore variables.

darkstar% TRAPDEBUG () { _0=$_; ___=$__; __=$_1; _1=$_0; }
darkstar% echo one        
one
darkstar% echo two        
two
darkstar% echo three      
three
darkstar% echo $_,$__,$___
three,two,one

$_1 won't always be the same as $_, because the debug trap doesn't run in exactly the same circumstances that cause _ to be set, but it's pretty close.

Defining $__ per command line

You can register a hook function to run before or after entering a command line. In this case, either precmd or preexec. They run before and after executing a command respectively.

preexec_set_underscore_variables () {
  ___=$__
  __=$_1
  _1=$historywords[1]
}
preexec_functions+=(preexec_set_underscore_variables)

I use historywords to get the last word from the command line. I store it in _1 because _ is already taken. And the function “shifts” the last-word-history variables by one.

darkstar% echo one
one
darkstar% echo two
two
darkstar% echo three
three
darkstar% echo $_ $__ $___
three two one
1
  • Hello Gilles, thanks for answering my question. I have one more: what is the difference between defining for command and command line? I just want this feature to reduce typework in an interactive shell, so I will look into the mentioned zsh-module. The exact syntax isn't important for me, I just thought $__ and so on are easier to remember (and I never heard of the plugin)
    – siryx
    May 31, 2019 at 8:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.