9

This question was inspired in part by this one.

alias foo='ls -1 $1'
foo /etc

displays the contents of /etc, one item per line.

ls -1 /etc | tail

displays the last ten items in /etc.

But

alias foo='ls -1 $1 | tail'
foo /etc

displays: tail: error reading `/etc': Is a directory

Community
  • 1
  • 1
brec
  • 259
  • 3
  • 10

4 Answers4

15

I have found variable expansion in aliases to be flaky, and not recommended:
http://www.gnu.org/software/bash/manual/bashref.html#Aliases

Use a function instead: function foo() { ls -1 $1; }

speeves
  • 1,358
  • 9
  • 10
  • This is the definitively correct answer. To quote from the bash manual @speeves referenced: " There is NO MECHANISM [my emphasis] for using arguments in the replacement text, as in csh. If arguments are needed, a shell function should be used (see Shell Functions)." I've found that the only places that a variable "works" is if it's at the end of the alias text, which implies that your additional text is just getting added to the end of the alias command, rather than being replaced in the text. In other words, if you have used a variable and it worked, you're just lucky. – JESii May 19 '13 at 15:21
3

Aliases done this way will only expand from the set of parameters:

$ alias foo='ls -1 $1 | tail'
$ foo .
# Type Esc-C-e: this expands aliases/globs/environment variables...
# ... And the result of the expansion is:
$ ls -1  | tail .
# $1 has disappeared
$ set bar  # set $1...
$ foo . # again, Esc-C-e
$ ls -1 bar | tail .
fge
  • 119,121
  • 33
  • 254
  • 329
  • Thanks; why does the variable work in the simple first alias without the pipe but disappear in the second alias? – brec Jan 04 '12 at 21:40
  • I have fixed my answer: actually the `$` in aliases will try and expand from `$*`, ie the set of arguments the shell was launched with. See post edit. – fge Jan 04 '12 at 21:41
  • Thanks again, but I am still looking for an explanation of the why the first alias works OK but not the second. – brec Jan 04 '12 at 21:47
  • It is because `$1` is undefined -- try and `set xxxxx`, then your first alias again: you'll get `ls: xxxxx: no such file or directory` – fge Jan 04 '12 at 21:54
  • I guess I don't understand: if in my first code in the original question I insert `set xxxxx` between the alias definition and usage, there is no change in the output. – brec Jan 04 '12 at 22:05
  • If you have a file named `xxxx`, no. If there is there will definitely be. Try and redirect stdout to `/dev/null` to see. – fge Jan 04 '12 at 22:07
2

The direct answer to your question: it is luck that the first case matches what you expect.

You have written your aliases assuming that $1 will represent the first "argument" to your alias. In fact, aliases in bash do not accept arguments, they merely replace the alias text with the stuff in quotes that you assigned it. So what does your $1 actually do?

Separate from aliases, in bash, $0 expands to the first thing typed to being the script or shell you are in (the command). $1 is the first argument (or second thing typed). Since you are typing in a shell at the command line, your shell was likely started by a terminal or window manager running the command bash with no arguments.

Try this from the command line:

$ echo $0
bash
$ echo $1
# prints nothing

So in your first case, foo /etc expands foo to get ls -1 $1 /etc and since $1 is null,

ls -1 /etc

also, if you pipe that to tail by adding | tail it works fine.

In your 2nd case foo /etc expands foo to get ls -1 $1 | tail /etc and since $1 is null,

ls -1 | tail /etc

which gives that error you got, because the command after the pipe is in error: tail cannot operate on the directory /etc

Starman
  • 336
  • 2
  • 11
0

Because $1 expands to nothing, it doesn't hold anything. In both cases the parameter /etc is appended to the last command. So, the first case doesn't result in ls -1 /etc, but in ls -1 $1 /etc, which is never the less meaningful. And the second string results in tail /etc, which is erroneous.

Multifix
  • 131
  • 1
  • 8