7

I have some aliases setup (in this order) in .bashrc:

alias ls="lsc"
alias lsc='ls -Flatr --color=always'
alias lscR='ls -FlatrR --color=always'

Confirming them with alias after sourcing:

alias ls='lsc'
alias lsc='ls -Flatr --color=always'
alias lscR='ls -FlatrR --color=always'

I can run the newly aliased ls just fine, and it chains through to the lsc alias, and then executes the command associated with the lsc alias. I can also run lscR and it operates as expected.

If I try to run lsc itself though, I get:

$ lsc
lsc: command not found

Any idea why the shell seems to be shadowing/hiding the lsc alias in this scenario? (I realise it's pointless to run 'lsc' when I can just run 'ls' to get the same result here, but I'm trying to understand the shells behaviour in this scenario).

EDIT: Workarounds below for the (bash) shell behaviour provided in the question answers.

Some really helpful answers have been provided to the original question. In order to short-circuit the expansion behaviour that is explained in the answers, there seems to be at least two ways of preventing a second alias, from trying to expand a command that you have already aliased. For example, if you have alias cmd='cmd --stuff' which is overriding a native command called cmd, you can prevent the 'cmd' alias from being used in place of the native cmd within other aliases, by:

(thanks to wjandrea's comment for this first approach)

  1. prefixing cmd with 'command' in the other alias e.g. alias other-cmd-alias='command cmd --other-stuff'

or,

  1. Similarly, you can escape aliases (as you can also do on the command line), within other aliases by prefixing with a backslash '', e.g. alias other-cmd-alias='\cmd --other-stuff'.
2
  • 3
    Workaround: usecommand in alias lsc='command ls ...' – wjandrea Aug 17 '20 at 16:56
  • 1
    The other thing I discovered one can do, is to escape commands in an alias, the same as one might do to override an alias on the command line. For instance if you alias ls='ls some-other-alias', but want to use 'ls' the command in it's raw form in a different alias, you can use alias ls2='\ls --funky-other-alias' and it seems to prevent the shell from expanding 'ls' within the ls2 alias, and just executes plain old 'ls'. – Chris Aug 17 '20 at 22:23
13

Bash does allow aliases to contain aliases but it has built-in protections against infinite loops. In your case, when you type lsc, bash first expands the alias to:

ls -Flatr --color=always

Since ls is also an alias, bash expands it to:

lsc -Flatr --color=always

lsc is an alias but, quite sensibly, bash refuses to expand it a second time. If there was a program named lsc, bash would run it. But, there is not and that is why you get command not found.

Addendum

It is different when lscR runs. lscR expands to:

ls -FlatrR --color=always

Since ls is an alias, this expands to:

lsc -FlatrR --color=always

Since lsc is an alias, this expands to:

ls -Flatr --color=always -FlatrR --color=always

Since ls has already been expanded once, bash refuses to expand it a second time. Since a real command called ls exists, it is run.

History

As noted by Schily in the comments, bash borrowed the concept of not expanding an alias a second time from ksh.

Aside

Aliases are useful but not very powerful. If you are tempted to do something complex with an alias, such as argument substitution, don't; use a shell function instead.

8
  • 1
    Ah, that is so obvious now you mention it! Somehow in my head I must have been imagining it being processed as a top to bottom flow, so it didn't occur to me that there was a recursive call 'back up' to the ls alias above. Thanks for pointing that out! – Chris Aug 17 '20 at 5:02
  • 1
    Further question in light of your explanation, how come when I call lscR it doesn't expand the ls call in that alias (since there is an alias ls='lsc') and then end up running the lsc alias instead? it definitely runs the command aliased to lscR (verifiable because it uses the recursive ls, not single level ls as used in the ls and lsc aliases)....EDIT: I think the explanation here is tied in with muru's answer below actually. – Chris Aug 17 '20 at 5:08
  • 1
    @Chris That is because, when running lscR, it is ls, not lsc, that bash refuses to expand the second time. Since a real command named ls exists, bash runs it. I added a more detailed explanation of this to the answer. – John1024 Aug 17 '20 at 5:36
  • 1
    You are correct, but the concept you are describing is not from bash but from ksh. – schily Aug 17 '20 at 7:52
  • 1
    @Chris Most command-line utilities (oddities like rsync & find are exceptions) handle repeated options gracefully. If two options contradict each other (such as ls' -U and -t options), the later specified option overrides the one specified first. This makes aliases work sensibly. – John1024 Aug 17 '20 at 18:33
6

From the bash manual:

The first word of the replacement text is tested for aliases, but a word that is identical to an alias being expanded is not expanded a second time. This means that one may alias ls to "ls -F", for instance, and Bash does not try to recursively expand the replacement text.

In the ls alias, ls is expanded to lsc and then again to ls -Flatr --color=always, and there the alias expansion stops, since ls was originally being expanded. So, the command runs fine, ls now being resolved to an external command.

In the lsc alias, lsc is expanded to ls -Flatr --color=always, and then ls is now expanded to lsc and there the alias expansion stops, since lsc was originally being expanded. So, the command fails, since bash doesn't know of any other lsc.

1
  • Thanks muru, this was also a great answer that I could just have easily accepted as the solution. Unfortunately, I can only accept one answer and John1024 got in first, but thanks for taking the time to provide this, it was helpful. – Chris Aug 17 '20 at 23:11

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.