14

I have nested aliases and I want to resolve all of them before executing the command. How do I do that?

If there is a function that is not bound to any keys then M-x foobar is fine for me too. I could even use external command (type, command, which, whatever). I tried everything from the thread Why not use "which"? What to use then? but nothing works.

3 Answers3

10

Note that Ctrl-Alt-E in bash does not only expand aliases. It also expands variables, command substitution (!), process substitution (!), arithmetic expand and removes quotes (it doesn't do filename generation (globbing) or tilde expansion).

It doesn't always manage to expand aliases. So while it has its uses, it's important to realise its outcome potentially changes the meaning of the command line, has side effects and is potentially dangerous.

For instance in:

$ a=';w' b=1
$ alias foo=bar
$ b=2; echo $b $a; cd /tmp/dir && for i do foo $(pwd) <(ls); done

If I press M-C-E here, that gives me:

$ b=2; echo 1 ;w; cd /tmp/dir && for i do foo / /dev/fd/63; done

Which gives me a completely different command line altogether (and imagine what would have happened if I had had rm -rf * instead of pwd above) and doesn't expand the foo alias.

With zsh, to build up on Gilles' note on aliases expanded inside functions, you could do:

expand-aliases() {
  unset 'functions[_expand-aliases]'
  functions[_expand-aliases]=$BUFFER
  (($+functions[_expand-aliases])) &&
    BUFFER=${functions[_expand-aliases]#$'\t'} &&
    CURSOR=$#BUFFER
}

zle -N expand-aliases
bindkey '\e^E' expand-aliases

That will expand the aliases only if the current command line is syntactically valid (so it doubles as a syntax checker).

Contrary to bash's M-C-E, it also resolves the aliases fully. For instance if you have:

$ alias ll='ls -l'; alias ls='ls --color'
$ ll

Will be expanded to:

$ ls --color -l

Note that it also canonicalises the syntax so things like:

$ for i (*) cmd $i; foo

will be changed to:

$ for i in *
        do
                cmd $i
        done
        foo
  • This is buggy. If I have alias ls='ls --color' and type C-x a over ls, I get: \ls --color (so that the new ls isn't misinterpreted as an alias). But with your expand-aliases, I get: ls --color, making the result ambiguous. – vinc17 Aug 18 '14 at 11:12
  • @vinc17, it's not ambiguous in that it resolves the alias fully (in that regards, it's less buggy than the bash equivalent). But it's true if you run the command after that, you'll get another round of alias expansion (like in bash), so ideally you'd want to temporarily disable alias expansion, so for instance wrap that in a (){ setopt localoptions noexpandalias; ...; }. Note that you could say the _expand_alias is buggy as well as it expands the alias when run on \ls. – Stéphane Chazelas Aug 18 '14 at 11:31
  • @vinc17, that backslash escaping done by _expand_alias is also easily fooled like alias 'foo=repeat 3 foo' or alias ls='ls --color'; alias '\ls=echo fooled'. There is no perfect solution here. – Stéphane Chazelas Aug 18 '14 at 11:37
  • Concerning _expand_alias for alias 'foo=repeat 3 foo', I would regard the missing backslash as a bug. And alias '\ls=echo fooled' shouldn't be allowed; here I prefer bash, which says: bash: alias: '\ls': invalid alias name. – vinc17 Aug 18 '14 at 11:48
  • @vinc17, I can't see how that can be seen as anything else but a limitation in bash. If you don't like aliases with backslashes, don't use them, but why would you want the shell to reject them? While aliases are a poor man's functions replacement in csh (where they come from), in Bourne-like shells, they are hacks to do tricks that can't be done with functions, some form of macro expansion that hooks early in the shell parser, I don't see the point in restricting what it can do. – Stéphane Chazelas Aug 18 '14 at 16:21
  • Aliases with backslash are error prone, not portable (e.g. one can also define them in dash, but with a behavior different from zsh). If they are used, this is probably a consequence of a typo, and it is better to report an error. Aliases have some advantages over functions in interactive shells, e.g. to be able to escape them easily with a backslash. – vinc17 Aug 18 '14 at 16:56
  • Interesting, about a year ago, I migrated all aliases from my shell rc file into a custom zsh expansion based on this idea of emulating vim's abbreviations in zsh. I did this so I could preview commands before they were executed, but hadn't considered the issues of the shell performing expansions and substitutions. This seems to validate the idea that if you do the expansions in simple custom functions, you have a better idea of whats happening, 1. because the shell is not "interfering" with the command. 2. because you wrote the code yourself – the_velour_fog May 06 '17 at 08:23
  • How would the non-recursive version of this look? I like being able to hit M-C-e and each time see my alias expand a bit further - helps in debugging. – Tom Hale Jun 19 '18 at 06:18
  • There is a quoting bug. I use: alias ls="LS_BLOCK_SIZE='1 ls" to get thousands grouping in file sizes. Expanding this alias I see: expand-aliases:1: unmatched ' expand-aliases:2: invalid function definition – Tom Hale Jul 03 '18 at 04:17
7

If you stuff a command line into a function definition and then print out the function, the aliases will be expanded. You'll also get normalized whitespace.

% alias foo='bar -1'
% alias bar='qux -2'
% f () foo -3
% which f
f () {
        qux -2 -1 -3
}

To put all this into an interactive command, you can make a zle widget. You can define a function directly by stuffing its code into an entry in the functions array; you'll get the normalization effect when you read back.

normalize-command-line () {
  functions[__normalize_command_line_tmp]=$BUFFER
  BUFFER=${${functions[__normalize_command_line_tmp]#$'\t'}//$'\n\t'/$'\n'}
  ((CURSOR == 0 || CURSOR = #BUFFER)
  unset 'functions[__normalize_command_line_tmp]'
}
zle -N normalize-command-line
bindkey … normalize-command-line

You get the same normalization effect in the preexec hook. Aliases are also expanded at the time a function is autoloaded (autoload -U is commonly used to avoid alias expansion).

The _expand_alias completion function expands the word under the cursor if it's an alias. It uses the aliases array. It is not recursive. You could implement a more general alias expander using aliases, but it is somewhat difficult, because figuring out the locations where aliases are expanded is intimately connected with shell syntax.

  • 3
    I always used autoload -U simply because the zsh documentation recommends it, but I never understood what -U actually did until I read this :). Also for anyone interested, one can invoke the _expand_alias function directly by typing your alias into the command line, hitting <Esc>, x, to launch the minibuffer, then typing _expand_alias<Enter> – the_velour_fog May 06 '17 at 08:45
  • Thank you very much for this trick, but it doesn't always seem to work when non-ASCII characters are involved. A small example:

    $ functions[foo]=á $ echo $functions[foo] ⃪▒ $ echo $functions[foo] | cat -v M-bM-^CM-*M-%

    The latter should produce M-CM-! (the UTF-8 for á). It works fine for many other non-ASCII characters, for example ¬.

    – ak2 Sep 15 '22 at 10:11
  • Sorry I made a complete mess of trying to format that. I hope it's legible anyway. As a workaround, defining the helper function through eval rather than assignment to functions seems to work, e.g. eval "__normalize_command_line_tmp() { $BUFFER }". – ak2 Sep 15 '22 at 10:22
  • 1
    @ak2 Weird. I can reproduce this bug with zsh 5.4.2 under Ubuntu 18.04 and 5.8.1 under Ubuntu 22.04, but not with 5.1.1 under Ubuntu 16.04. It seems to be specific to $functions, since $aliases appears to be fine. And indeed it seems to only affect some characters: áœß→≤ but not ¬…ł — and curiously actually the bug does affect 5.1.1, but with a different set of characters (e.g. ß but not á)! – Gilles 'SO- stop being evil' Sep 16 '22 at 16:11
2

If you have many nested fancy aliases and you are not sure what zsh is actually doing with them and in which order options are passed to command then you can always start zsh with -x option. This will print commands and arguments as they are executed.

Be aware however that this option is intended rather for debugging purpose so it prints a lot of useless stuff just after zsh -x invocation (basically each and every function/widget/plugin of your .zshrc), and during command execution it could be also verbose, especially if you have defined preexec and precmd hook.

I should also mention, that it prints only those commands which are finally executed, and separated commands are printed separately so after

alias a='echo a'
alias b='echo b'
alias c='echo c'
alias d='echo d'
a && b || c; d

You will see

+zsh:1> echo a
a
+zsh:1> echo b
b
+zsh:1> echo d
d
jimmij
  • 47,140