Why would I create an alias which creates a functi

2020-06-30 07:23发布

问题:

I see this pattern every once in a while, especially in questions about Bash prompt customization.

alias f='_ () { useful code; }; _'

I can see no reason at all to create an alias here. The obvious refactoring

f () { useful code; }

which avoids declaring an alias altogether, and simply defines the function once and for all, seems simpler, more understandable, less brittle, and more efficient. (In case it's not obvious, the alias ends up redeclaring the function every time you invoke the alias.)

For example, Make a Bash alias that takes a parameter? has several answers which exhibit this technique. bash script to run lftp with ftp path is a question which has code like this in a question about the actual functionality inside the function, and the OP doesn't explain why even though I prodded gently.

Is this just plainly an antipattern, or is there an actual reason to do this? Under what circumstances would this design make sense?

This is not about aliases with a space after them, or about code obfuscation (the examples I have found are generally entirely readable, apart from this mystifying technique).

回答1:

I found this answer too [U&L] In Bash, when to alias, when to script, and when to write a function? which explains the benefit of defining a function in an alias.

The benefit of doing so over declaring a function is that your alias cannot be simply overwritten by source-ing (or using .) a script which happens to declare a same-named function.



回答2:

Here are my 2 cents on this and it represents my personal opinion as well as understanding on the topic.

  • Using aliases with functions is to some extent a personal preference of developers. I will add some differences between the two approaches, which may also account for personal preferences of using aliases vs functions
  • There are times when most of the things I want to do are possible with aliases itself but only a few require to take a parameter. So instead of mixing aliases with functions, I use an alias with the function itself

Example:

alias kgps='kubectl get pods --all-namespaces | grep '

This works great and I can search my kubernetes pods. Now for deleting these pods, I need to pass the same parameter but in between the command, so I use an alias with a function inside

alias kdp="_(){ kubectl get pods --all-namespaces  | grep \$1 | awk '{print \$2}' | xargs kubectl delete pod; }; _"

So most of my shortcut commands are possible to execute through aliases and only few which needs such things I use aliases with functions.

Aliases vs Functions

Now there are few differences between aliases and functions which I would like to highlight

Aliases can override system commands much more easily compared to functions

If I need to override ls, I can do that much easier with alias

alias ls='ls -altrh'

While a function equivalent of the same would be like below

ls() { command ls -altrh "$@";}
ls() { /bin/ls -altrh "$@";}

Aliases intention is mostly for shortcuts

Aliases are majorly used to create shortcut commands while functions are used for a lot of things, complex combinations of commands, auto-completion, bash prompts

Aliases are easier to manage

Run alias command you get a list of currently active aliases

$ alias
....
vs='vagrant ssh'
vu='vagrant up'
vus='vu && vs'
....

To get the list of functions we need to use declare -f or another similar command

$ declare -f | wc -l
  8226
$ alias | wc -l
  217

Now if I post a partial output of declare -f I get

$ declare -f
...
vi_mode_prompt_info () {
    return 1
}
virtualenv_prompt_info () {
    return 1
}
work_in_progress () {
    if $(git log -n 1 2>/dev/null | grep -q -c "\-\-wip\-\-")
    then
        echo "WIP!!"
    fi
}
zle-line-finish () {
    echoti rmkx
}
zle-line-init () {
    echoti smkx
}
zsh_stats () {
    fc -l 1 | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}' | grep -v "./" | column -c3 -s " " -t | sort -nr | nl | head -n20
}

As you can see there are lots of functions which are used but are not relevant to me. While the alias command gives me a very concise output and I can easily see what all is there. In my case, 100% of them are shortcut commands

Escaping aliases and functions syntax is different for system commands

To escape a defined alias you need to prefix it with \ while for functions you need to either use command <originalcommand> or absolute path of the command /bin/originalcommand

Aliases have higher priority over function

Look at the below example

alias ls='echo alias && ls'
$ ls() { /bin/ls -al }
alias
$ ls
alias
total 23173440
drwxrwxr-x+ 255 tarunlalwani  staff        8160 Jul 30 22:39 .
drwxr-xr-x+ 113 tarunlalwani  staff        3616 Jul 30 23:12 ..
...

As you can see when we run the ls command, first the alias is used and then the next ls is calling the function.

This becomes also a way of wrapping an exiting function with the same name and re-using the original function inside as well, which can only be done using alias and promotes the format in the question



回答3:

I found an Ask Ubuntu question about a related topic where one of the answers alleges that this is a misunderstanding of a different design principle: give the function a long and descriptive name, and create a shorter alias for convenience.

This still offers no insight into why you would have the alias redeclare the function every time.



回答4:

You can use the alias for turning on and off a function that you don't want to change. Suppose you have have code that calls the function _. You can switch the implementation of the function for another one with

alias f='_ () { echo "useful code"; }; _'
alias g='_ () { echo "Other useful code"; }; _'
alias h='_ () { echo "Special code"; }; _'

And now you can call

f
_
g
_
h
_
f

@DavidC.Rankin commented correctly, that it looked terrible.
I agree.
I thought of some way to use it. You might use it for testing software, something like

alias ok='commitTransaction () { echo "commited"; return 0; }'
alias nok='commitTransaction () { echo "unknown error"; return 1; }'
alias locked='commitTransaction () { echo "locked"; return 2; }'
alias slow='commitTransaction () { sleep 20; echo "commited"; return 0;  }'

And now the tester can run his testcases:

ok
# And now start ok test
nok
# And now start nok test

Still hacking, why not make a better teststub?



回答5:

Is this just plainly an antipattern (sic)...

I think its prevalence may just be cargo cult programming. Aliases are easy to understand, so users often learn them first. As users' skill and needs increase, they discover aliases lack flexible argument processing. So they do a web search, like "shell alias parameter passing", and find posts suggesting the pattern:

alias foo='_() { echo $2 $3 $1; }; _'

Lo and behold, it works. Users are happy, and they move on.

But because the _() sequence looks a lot like a shell incantation (2>&1, >>, etc.), users never think that _() is just compact syntax for function _ and never go to the next step of learning functions. With this alias pattern, they get all the benefit of functions and don't have to learn "new" syntax. Most probably never even notice the nasty side effect: overwriting any prior functions named _.

I searched through Usenet from 1981 to 1991, but I didn't find any direct evidence of this theory, however.

... or is there an actual reason to do this? Under what circumstances would this design make sense?

In the five days I drafted this answer, every reason I conjured came back to an argument against it. The selected answer - that aliases can't be masked in subshells - is a solid reason, though I've never thought to be that paranoid: I don't go around sourc'ing code I've not fully vetted.