25

According to the Bash manual, the environment variable BASH_COMMAND contains

The command currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap.

Taking that trap corner case aside, if I understand correctly this means that when I execute a command, the variable BASH_COMMAND contains that command. It's not absolutely clear whether that variable is unset after the command execution (i.e., is only avaialble while the command is running, but not after), though one might argue that since it is "the command currently being executed or about to be executed", it is not the command that was just executed.

But let's check:

$ set | grep BASH_COMMAND=
$ 

Empty. I would have expected to see BASH_COMMAND='set | grep BASH_COMMAND=' or maybe just BASH_COMMAND='set', but empty surprised me.

Let's try something else:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

Well that makes sense. I execute the command echo $BASH_COMMAND and so the variable BASH_COMMAND contains the string echo $BASH_COMMAND. Why did it work this time, but not before?

Let's do the set thing again:

$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

So wait. It was set when I executed that echo command, and it was not unset afterwards. But when I executed set again, BASH_COMMAND was not set to the set command. No matter how often I execute the set command here, the result stays the same. So, is the variable set when executing echo, but not when executing set? Let's see.

$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

What? So the variable was set when I executed echo $BASH_COMMAND, but not when I executed echo Hello AskUbuntu? Where's the difference now? Is the variable only set when the current command itself actually forces the shell to evaluate the variable? Let's try something different. Maybe some external command this time, not a bash builtin, for a change.

$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$

Hmm, ok... again, the variable was set. So is my current guess correct? Is the variable only set when it has to be evaluated? Why? Why? For performance reasons? Let's do one more try. We'll try to grep for $BASH_COMMAND in a file, and since $BASH_COMMAND should then contain a grep command, grep should grep for that grep command (i.e., for itself). so let's make an appropriate file:

$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$

Ok, interesting. The command grep $BASH_COMMAND tmp got expanded to grep grep $BASH_COMMAND tmp tmp (the variable gets expanded just that once, of course), and so I grepped for grep, once in a file $BASH_COMMAND which doesn't exist, and twice in the file tmp.

Q1: Is my current assumption correct that:

  • BASH_COMMAND is only set when a command tries to actually evaluate it; and
  • it is not unset after execution of a command, even though the description may lead us to believe so?

Q2: If yes, why? Performance? If no, how else can the behavior in the above command sequence be explained?

Q3: Lastly, is there any scenario in which this variable could actually be meaningfully used? I was actually trying to use it within $PROMPT_COMMAND to analyze the command being executed (and do some stuff depending on that), but I can't, because as soon as, within my $PROMPT_COMMAND, I execute a command to look at the variable $BASH_COMMAND, the variable gets sets to that command. Even when I do MYVARIABLE=$BASH_COMMAND right at the beginning of my $PROMPT_COMMAND, then MYVARIABLE contains the string MYVARIABLE=$BASH_COMMAND, because an assignment is a command too. (This question is not about how I could obtain the current command within a $PROMPT_COMMAND execution. There are other ways, I know.)

It's a bit like with Heisenberg's uncertainty principle. Just by observing the variable, I change it.

  • 5
    Nice, but probably better suited for unix.stackexchange.com ...there are real bash über-gurus there. – Rmano Aug 20 '14 at 18:35
  • Well, is it possible to move it over there? Mods? – Malte Skoruppa Aug 20 '14 at 18:39
  • I am really not sure how to do it --- I suppose it's a Mod privilege. On the other hand I don't think flagging it is sensible --- let's see if someone acts on the close flag. – Rmano Aug 20 '14 at 18:43
  • 1
    For the third point, this is what you are searching for. Maybe based on that article you can find the answer for the first two points too. – Radu Rădeanu Aug 21 '14 at 8:40
  • 2
    +1 confused the heck out of me, but it was fun to read! – Joe Aug 29 '14 at 8:03
15

Answering to the third question: of course it can be used meaningfully in the way at Bash manual clearly hints – in a trap, e. g.:

$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
‘fgfdjsa’ failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
‘cat /etc/fgfdjsa’ failed with error code 1
  • Ah, nice indeed. So would you say that a trap is the only context in which this variable makes sense? It sounded like a corner case in the manual: "The command being executed (...), unless the shell is executing a command as the result of a trap, ..." – Malte Skoruppa Aug 21 '14 at 8:27
  • After reading the articles linked in the answer by @DocSalvager, it is clear that $BASH_COMMAND can indeed be used meaningfully even in contexts other than a trap. Yet, the use you describe is probably the most typical and straight-forward. So your answer, and the one by DocSalvager, answer my Q3 best, and this was the most important to me. As for Q1 and Q2, as indicated by @zwets, those things are undefined. It may be that $BASH_COMMAND is only given a value if accessed, for performance - or else some weird internal program conditions may lead to that behavior. Who knows? – Malte Skoruppa Sep 20 '14 at 16:02
  • 1
    On a sidenote, I discovered that you can force $BASH_COMMAND to be set always, by forcing evaluation of $BASH_COMMAND before each command. To do this we can use the DEBUG trap, which is executed before every single command (all of em). If we issue, e.g., trap 'echo "$BASH_COMMAND" > /dev/null' DEBUG, then $BASH_COMMAND will always be evaluated before each command (and assigned the value of that $COMMAND). So if I execute set | grep BASH_COMMAND= while that trap is active... what do you know? Now the output is BASH_COMMAND=set, as expected :) – Malte Skoruppa Sep 20 '14 at 16:09
6

Now that Q3 has been answered (correctly, in my opinion: BASH_COMMAND is useful in traps and hardly anywhere else), let's give Q1 and Q2 a shot.

The answer to Q1 is: the correctness of your assumption is undecidable. The truth of neither of the bullet points can be established, as they ask about unspecified behaviour. By its specification, the value of BASH_COMMAND is set to the text of a command for the duration of the execution of that command. The spec does not state what its value must be in any other situation, i.e. when no command is being executed. It could have any value or none at all.

The answer to Q2 "If no, how else can the behavior in the above command sequence be explained?" then follows logically (if somewhat pedantically): it is explained by the fact that the value of BASH_COMMAND is undefined. Since its value is undefined, it can have any value, which is exactly what the sequence shows.

Postscript

There is one point where I think you're indeed hitting a soft spot in the spec. It's where you say:

Even when I do MYVARIABLE=$BASH_COMMAND right at the beginning of my $PROMPT_COMMAND, then MYVARIABLE contains the string MYVARIABLE=$BASH_COMMAND, because an assignment is a command too.

The way I read the bash man page, the bit in italics is not true. The section SIMPLE COMMAND EXPANSION explains how first the variable assignments on the command line are set aside, and then

If no command name results [in other words, there were only variable assignments], the variable assignments affect the current shell environment.

This suggests to me that variable assignments are not commands (and therefore exempt from showing up in BASH_COMMAND), like in other programming languages. This would then also explain why there isn't a line BASH_COMMAND=set in the output of set, set essentially being a syntactic 'marker' for variable assignment.

OTOH, in the final paragraph of that section it says

If there is a command name left after expansion, execution proceeds as described below. Otherwise, the command exits.

... which suggests otherwise, and variable assignments are commands too.

  • 1
    Just because some assumption can't be established does not mean it is incorrect. Einstein assumed that the speed of light is the same for any observer (at any speed) as one fundamental hypothesis for the theory of relativity; that cannot be established, yet this does not mean it's incorrect. The properties "can be established" and "is correct" are orthogonal. At best you could say "We don't know if it's correct, since it can't be established". Also, it is specified: it's the current command during execution. So if I type set I should see BASH_COMMAND='set' somewhere in that output, which – Malte Skoruppa Aug 21 '14 at 14:07
  • ...however is not the case. Even though that is during execution of the set command. Hence, according to specs it should work. So is it that the implementation does not faithfully obey the specs, or do I misunderstand the specs? Finding the answer to that question is kind of the purpose of Q1. +1 for your postscript :) – Malte Skoruppa Aug 21 '14 at 14:10
  • @MalteSkoruppa Good points. Fixed the answer accordingly, and added a remark about set. – zwets Aug 22 '14 at 13:58
  • 1
    It's possible that in the bash interpreter, set is not a "command". Just as = is not a "command". Processing of the text following/surrounding those tokens could have entirely different logic than the processing of a "command." – DocSalvager Aug 27 '14 at 6:37
  • Good points from both of you. :) It may well be that set does not count as a "command". I wouldn't have thought that $BASH_COMMAND would turn out to be so underspecified in many circumstances. I guess at the very least we have established that the man page definitely could be clearer about it ;) – Malte Skoruppa Aug 27 '14 at 9:41
2

An inventive use for $BASH_COMMAND

Recently found this impressive use of $BASH_COMMAND in implementing a macro-like functionality.

This is the core trick of the alias and replaces the use of the DEBUG trap. If you read the part in the previous post about the DEBUG trap, you’ll recognize the $BASH_COMMAND variable. In that post I said that it was set to the text of the command before each call to the DEBUG trap. Well, it turns out it’s set before the execution of every command, DEBUG trap or no (e.g. run ‘echo “this command = $BASH_COMMAND”‘ to see what I’m talking about). By assigning it a variable (just for that line) we capture BASH_COMMAND at the outermost scope of the command, which will contain the entire command.

The author's previous article also provides some good background while implementing a technique using a DEBUG trap. The trap is eliminated in the improved version.

  • +1 I finally got around to read those two articles. Wow! What a read! Very enlightening. That guy is a bash guru! Kudos! This also answers my comment on Dmitry's answer on whether $BASH_COMMAND can be used meaningfully in a context other than a trap. And what a use that is! Using it to implement macro commands in bash -- who would have thought it possible? Thanks for the pointer. – Malte Skoruppa Sep 20 '14 at 15:51
0

A seemingly common use for the trap...debug is to improve the titles that appear in the windowlist (^A") when you are using "screen".

I was trying to make the "screen", "windowlist" more usable so I started to find articles referencing "trap...debug".

I found that the method using PROMPT_COMMAND to send the "null title sequence" did not work so well, so I reverted to the trap...debug method.

Here is how you do it:

1 Tell "screen" to look for the escape sequence (enable it) by putting "shelltitle '$|bash:'" into "$HOME/.screenrc".

2 Turn off the propagation of the debug trap into sub-shells. This is important, because if it is enabled, everything gets mucked up, use: "set +o functrace".

3 Send the title escape sequence for "screen" to interpret: trap 'printf "\ek$(date +%Y%m%d%H%M%S) $(whoami)@$(hostname):$(pwd) ${BASH_COMMAND}\e\"' "DEBUG"

It's not perfect, but it helps, and you can use this method to put literally anything you like into the title

See the following "windowlist" (5 screens):

Num Name Flags

1 bash:20161115232035 mcb@ken007:/home/mcb/ken007 RSYNCCMD="sudo rsync" myrsync.sh -R -r "${1}" "${BACKUPDIR}${2:+"/${2}"}" $ 2 bash:20161115230434 mcb@ken007:/home/mcb/ken007 ls --color=auto -la $ 3 bash:20161115230504 mcb@ken007:/home/mcb/ken007 cat bin/psg.sh $ 4 bash:20161115222415 mcb@ken007:/home/mcb/ken007 ssh ken009 $ 5 bash:20161115222450 mcb@ken007:/home/mcb/ken007 mycommoncleanup $

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.