17

I understand the following error is due to ! used for history expansion:

$ echo "Hello!Tim"
bash: !Tim: event not found

However if I put the command into a script and run the script, there is no problem:

$ cat myscript
echo "Hello!Tim"
$ bash myscript 
Hello!Tim

Why is that? Does the bash manual mention the reason?

Tim
  • 101,790
  • 1
    Good question, simply stated. Very well done! – Wildcard Aug 09 '17 at 02:41
  • Based on what I'm reading here: https://www.gnu.org/software/bash/manual/html_node/History-Interaction.html I believe you would need to use some sort of shopt modifier to get it to work in your script. I know that's not really the answer you are looking for. – jesse_b Aug 09 '17 at 02:51
  • What's the use case? What kind of script would you run that would expand your previous history? Or would it expand previous commands from the script? – Jeff Schaller Aug 09 '17 at 10:58
  • @JeffSchaller, numeric history expansion in a script with positive numbers sounds like a really bad idea, but I could easily imagine use cases for !! or !-2 (negative/relative history numbers) or similar. Mostly they would be solved better in other ways, but it's still a good question about how the shell works (and when you do/don't need to escape your exclams!). – Wildcard Aug 23 '17 at 06:27

1 Answers1

23

Yes, history expansion is enabled by default only for interactive shells.

To enable it for a shell script, write:

set -H

To disable it in an interactive shell, write:

set +H

To determine whether or not history expansion is currently enabled, use some form of the following code:

case $- in (*H*) echo enabled ;; (*) echo disabled ;; esac

In starting to teach a shell class, I dug through the manual extensively to try to establish what an "interactive shell" really is. It's a whirlpool question, so let me save you some trouble:

The shell has MANY options. Some of these options are initialized in different ways when the shell has a controlling terminal (or when started with -i, blah blah, whatever, see below).

ALL of the shell's options can be individually changed.

An "interactive shell" is a deceptive term when you try to define it precisely. It is really just a collection of option settings.

The question about which settings make a shell interactive or not is impossible to answer; it gets ridiculous. It is precisely the same philosophical question as the Ship of Theseus.

If you start an interactive shell, but then disable history expansion, use the --noediting flag, set --norc, turn off expand_aliases, etc., etc., then in what sense is the shell interactive? Or, when does it become not interactive anymore? You can't answer these questions.

The truth is that "interactive" is just a convenient label for a collection of various shell options. Likewise "non-interactive." Same thing; just a collection of behaviors that can each one be changed individually.

Bottom line: the shell behaves differently when it is started "interactively" versus when it is started "non-interactively." Trying to precisely define these terms after start-up is silly. Just look at each individual option of the shell to understand its behavior.


I had forgotten that in addition to my own research, I posted about it extensively on this very site.

Wildcard
  • 36,499
  • Thanks. In the manual, I only find "This option is on by default for interactive shells" for set -H. It is probably your inference that "history expansion is enabled by default only for interactive shells". – Tim Aug 09 '17 at 02:56
  • @Tim, see the rest of my answer. That sentence of the manual is a very slippery one, and trying to get the answer to "what IS an interactive shell?" out of the man page is like talking in circles. It goes nowhere. – Wildcard Aug 09 '17 at 03:00
  • What you CAN say is that the following code snippet will always show you whether history expansion is enabled in the current shell: case $- in (*H*) echo enabled ;; (*) echo disabled ;; esac – Wildcard Aug 09 '17 at 03:01
  • @Tim, by the way, I have no idea what you mean that it's my "inference," because that's the clear meaning of set -H. That IS the histexpand option. – Wildcard Aug 09 '17 at 03:03
  • I meant it is you not the manual which used the word "only" – Tim Aug 09 '17 at 03:05
  • @Tim, ah, but I also used the phrase "by default." :D So my statement is unfalsifiable, since there is no precise definition for an "interactive shell!" That is to say, if you DO something to enable history expansion, that's not by default. And any shell in which you DON'T do something and find that history expansion IS enabled, I have defined to be an "interactive shell." ;) Chew on that. (There really isn't a precise official definition of an interactive shell; I'm not kidding.) – Wildcard Aug 09 '17 at 03:08
  • 1
    I've tried this on a script and it didn't work until I also did set -o history, after verifying with set -o from the script that it was disabled. – Daniel C. Sobral Jun 13 '19 at 17:52
  • I don't understand how the Ship of Theseus is relevant here. What has been removed from what? Whether or not the shell is interactive is determined exactly by a couple bits in a memory location somewhere. –  Mar 22 '23 at 08:23
  • @BorisVerkhovskiy if you start with an interactive shell, but then you disable history expansion, you have an interactive shell with history expansion disabled, right? And if you then turn off alias expansion, you have an interactive shell with history and alias expansion disabled. If you change all such options one by one, then you have a "non-interactive shell." At what point did it change from being an interactive shell to being the Ship of Theseus? – Wildcard Mar 23 '23 at 01:20
  • @Wildcard So is there any absolute threshold between login and non-login shell, if you have any study on this also? – Qiu Yangfan Oct 25 '23 at 03:11
  • @QiuYangfan that is much more straightforward, just search "login shell" in the man page. If you come up with a specific question go ahead and ask it. – Wildcard Oct 25 '23 at 16:36