1

From the Bash manual, Section 6.6 Aliases,

  …   Bash always reads at least one complete line of input before executing any of the commands on that line.  Aliases are expanded when a command is read, not when it is executed. Therefore, an alias definition appearing on the same line as another command does not take effect until the next line of input is read.

Here I am trying to find a way to understand Which of the following operations does shell perform when "reading a complete line of input" and when "executing any of the commands in the line"?

Are there some commands and examples which can show the results

  • after Bash reads one complete line of input
  • but before executing any of the commands on that line ?

For example,

  • "one complete line of input" is a compound command which consists of several commands.
  • "one complete line of input" can also be a pipeline of several compound commands, a list of pipelines, etc.
2

The part of the Bash manual you quoted talks about

Aliases are expanded when a command is read, not when it is executed. Therefore, an alias definition appearing on the same line as another command does not take effect until the next line of input is read.

So aliases are actually one of those examples that show the difference between reading and executing a line!

To clarify, here is a compound command. We first try to define an alias, and then we try to use it:

alias echo=uname; echo

Someone who is unfamiliar with Bash would expect that the output of that command is Linux (or whatever OS you're running), but it actually it just outputs nothing. This is because Bash first reads the line and applies alias definitions. So this is what Bash sees:

alias echo=uname; echo

Note that the second echo is not converted to uname. This is because Bash hasn't executed our alias echo=uname command yet, it has only read it. After Bash has read the line, it executes what it has read. Therefore it executes:

alias echo=uname; echo (which is exactly what it read, as described above)

We can even check that Bash executed the command by typing echo. Since we have defined an alias before, echo will now be converted to uname during the read step, so Bash will execute uname.

In short:

$ alias echo=uname; echo

$ echo
Linux

Edit:

After your edit, you asked whether there is a way to show the results after Bash reads one line of input, but before actually executing them. The answer is yes, but it's not very convenient for every-day use (however, it's certainly useful for understanding what's going on inside the shell).

Create a file with the following contents:

#!/bin/bash
shopt -s expand_aliases
trapped() {
    echo "On line ${1}, Bash sees: $BASH_COMMAND"
}
trap 'trapped ${LINENO}' DEBUG
alias echo=uname; echo
echo

Then, execute that file.

Here is an explanation of how that script is working:

  • shopt -s expand_aliases tells Bash to expand aliases even when it is in noninteractive mode (such as in this case)
  • We then define a function trapped(). When called, this function will print the command that is currently executed (along with a line number, that gets passed as parameter).
  • We call trap 'trapped ${LINENO}' DEBUG. This tells Bash "whenever you see a signal called DEBUG, execute trapped ${LINENO} first. DEBUG is a signal that is automatically generated by Bash before any execution of a simple command. In other words, before any command is executed, our function trapped() is called.
  • Lastly, we execute a few commands for demonstration. However, since we have a trap going, trapped() is executed before anything else happens, so Bash prints what is about to be executed. The LINENO variable is just a Bash built-in, that gives us the current linenumber.

If you want to be able to "run" commands without executing them at all and still see what Bash reads, you might want to look into the extdebug option in the Bash manual.

  • Thanks. Is it possible to show the results after Bash reads one complete line of input but before executing any of the commands on that line? Sorry that I didn't make my question clear. – Tim Aug 4 '17 at 1:08
  • @Tim I added an explanation to my answer. That was harder than I thought it would be. Today I learned :) – PawkyPenguin Aug 4 '17 at 2:40
  • Thanks again. Here I am trying to find away to understand unix.stackexchange.com/questions/383796/… – Tim Aug 4 '17 at 7:53
  • Regarding "If you want to be able to 'run' commands without executing them at all and still see what Bash reads, you might want to look into the extdebug option in the Bash manual", after I run shopt -s extdebug, I don't see any information displayed when I run a command in bash. Am I missing something? – Tim Aug 7 '17 at 0:03
  • @Tim Ah, sorry, I didn't make this clear enough. You're doing the right thing. What I meant was that extdebug can be used to surpress execution of commands. The description of extdebug says If the command run by the DEBUG trap returns a non-zero value, the next command is skipped and not executed. So you can use trap and the extdebug option in combination to skip the execution of commands. Additionally, traps themselves can be used to print information about the commands, like I did in my answer. – PawkyPenguin Aug 7 '17 at 0:28
3

A good way to understand this would be to take a look at this example: https://stackoverflow.com/questions/38526612/bash-unable-to-set-and-use-alias-in-the-same-line

For example, you want to execute the following command:

$ alias foo="echo bac" ; foo;
-bash: foo: command not found
$ foo
bac

Now bash will read the whole line and since foo is not aliased yet it will not expand (aliases are expanded while reading). Now while executing the line it'll set the alias and throw an error.

  • Thanks. Is it possible to show the results after Bash reads one complete line of input but before executing any of the commands on that line? Sorry that I didn't make my question clear. – Tim Aug 4 '17 at 1:09
2

Your question isn’t crystal clear, but your juxtaposition of the sentences

Aliases are expanded when a command is read, not when it is executed.

and

Bash always reads at least one complete line of input before executing any of the commands on that line.

suggests that the focus of your question is alias expansion.  (I say “suggests” because, aside from the Bash manual excerpt, your question doesn't mention aliases at all.)

The original version of your question asked

How can I show that Bash always reads at least one complete line of input before executing any of the commands on that line?

The only real way to do this is to use some sort of debugging / monitoring / trace tool to examine the shell’s internal state.  That’s somewhat impractical.  PawkyPenguin’s answer shows an approach to making a hand-wavy argument to support what the manual says.  Here’s a variation of their answer that (IMO) is a little clearer:

$ alias myalias=date

$ alias myalias="ls -ld"; myalias
Fri, Aug 04, 2017  3:33:33 AM

We can argue that the shell must have read the (second) complete line in its entirety before it started to execute it  because, when it executed myalias, it executed it as date.  By the time the myalias alias was executed, it had been redefined to be ls -ld.  Therefore, the shell must have expanded myalias to date before it executed the

alias myalias="ls -ld"

command, so it must have read the complete line of input before executing any of the commands on that line.


Your revised question,

How can I show the results after Bash reads one complete line of input but before executing any of the commands on that line?

makes a little more sense in this context.  I guess you want to see the command line with aliases expanded  before executing any of the commands on that line; i.e., see the results of alias expansion.  I guess you want to see something like

alias myalias="ls -ld"; date

As far as I know, there isn’t any way to get that output — the complete line with aliases expanded.  But the xtrace (-x) option comes close.

$ set -x

$ alias myalias=date
+ alias myalias=date

$ alias myalias="ls -ld"; myalias
+ alias 'myalias=ls -ld'
+ date                                                         ← The expansion of the alias
Fri, Aug 04, 2017  3:33:42 AM

Of course, this outputs each expanded command only just before it is executed, so the results of alias expansion are intermixed with the output from the commands.  Also, this shows all expansions:

$ animal=cat
+ animal=cat

$ echo Hello; echo world; myalias "$animal" R*
+ echo Hello
Hello
+ echo world
world
+ ls -ld cat README                                            ← The expansion of the alias
ls: cannot access cat: No such file or directory
-rw-r--r-- 2 myusername  mygroupname  489 Feb 22  2015 README

We don’t get to see ls -ld "$animal" R*; we see it only after variable expansion and pathname (glob) expansion.  Neither does it “play well” with pipelines:

$ alias myalias=date
+ alias myalias=date

$ myalias | od -cb
+ date                                                         ← The expansion of the alias
+ od -cb
0000000   F   r   i   ,       A   u   g       0   4   ,       2   0   1
        106 162 151 054 040 101 165 147 040 060 064 054 040 062 060 061
0000020   7           9   :   4   2   :   1   7       P   M  \n
        067 040 040 071 072 064 062 072 061 067 040 120 115 012
0000036

where the trace output shows us date and od -cb on two separate lines.  (You would need to look at the original command line to figure out that it was doing date | od -cb, if it wasn’t obvious from the output.)

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.