|
Oh, YUCK. I've always wondered what aliases in Bourne-type shells were actually for. Shell functions can do all the same things, right? What's the advantage of typing ‘alias l="ls -l"’ when you could just as easily have typed ‘l() { ls -l "$@"; }’, and got the same result? Better still, shell functions allow you to process your arguments first, or postfix things to the argument list as well as prefixing, so they can do more. And shell functions are perfectly portable across all Bourne shells, so what on earth is the point of inventing a second mechanism that does a subset of the same things? I've never understood it. The other day I had a horrid thought about this, and a bit of experimentation has just confirmed it. Alias functionality is not in fact a subset of shell-function functionality: there are things aliases can do which shell functions fundamentally can't. All shell functions operate under the same basic rules of shell syntax: the arguments provided to the shell function have already had variable expansion, globbing and so forth done to them by the time the function begins to execute. Hence, if I give the argument ‘*’ to a shell function, then no matter what the function does I can be confident that the wildcard will be expanded in terms of the files in the directory I ran the command in. At first glance this appears true of aliases too; but it's not. More particularly, it's not true for aliases containing more than one command. A simple example is: thisway() { cd subdir; ls -l "$@" } alias thatway='cd subdir; ls -l'
Without any arguments, or with ordinary non-wildcard arguments, both these functions do the same thing. But pass the argument ‘*’ and the behaviour will be very different – and in this particular case, the alias's behaviour will probably be more useful. You can go one step further, by having the alias define a shell function to do the actual work which then undefines itself when it's finished. This enables the alias to both start and finish in the same directory you thought you were in, but to interpret wildcard arguments as if you were in a different directory. For example: alias globelsewhere=' f() { echo "$@"; cd $olddir; unset olddir; unset f; } olddir=$PWD; cd /etc; f'
(This isn't of production quality, of course. You'd want to cache $OLDPWD as well, so that ‘cd –’ still worked afterwards; and you'd want to use unlikely names for ‘f’ and ‘oldpwd’ to avoid clashing with the user's variables.) This trick can affect things other than the current directory, of course. You could define shell variables in the alias and have them unset before it terminated, and then those variables would be expanded on the alias's command line but nowhere else. You could change the value of ‘$GLOBIGNORE’, or disable globbing completely. You could fiddle with ‘$IFS’, although I can't immediately work out what effect that would have. As Tom Duff said about the legendary Duff's device, ‘I feel a combination of pride and revulsion at this discovery’. In some sense the whole point of the Bourne shell syntax, for me, is its absolute consistency in the way it expands arguments; for example, unlike DOS, you never need to worry about which commands accept wildcards and which don't, because the wildcard expansion is done by the shell and always done the same way, and the command doesn't have a say in it at all. The idea that you can deliberately use this sort of alias to break that complete consistency makes me feel extremely uneasy. I started this investigation because I was curious as to why aliases weren't a complete misfeature in the Bourne shell, and it's possible that what I've discovered forms the best argument yet for why they are. And yet … I can think of a variety of interesting uses for it, and I'm not entirely sure I might not end up adding one or two of them to my standard bash configuration. For example, you could turn globbing completely off for the duration of a single command, which might actually be useful since almost every time I use ‘find’ I need to single-quote a wildcard argument. Replacing ‘find’ with an alias that relieved me of the need to do so might actually save me time; and I could always explicitly write ‘/usr/bin/find’ if I wanted to bypass that behaviour. |