6

What will happen if the executing bash script file is overwritten? Will it continue execute the old file or overwritten file ?

For example /etc/test.sh has the following contents

cp /test.sh /etc/test.sh

user3190975
  • 71
  • 1
  • 2

3 Answers3

20

bash generally does not load the entire script in advance, so overwriting the script can have unpredictable consequences. For example, I just created a script containing this:

#!/bin/bash
echo start old file
sleep 20
echo end old file

...and ran it. While it was sleeping, I overwrote it with this slightly different script:

#!/bin/bash
echo started new file
sleep 20
echo end new file

Resulting in this output:

$ ./test.sh 
start old file
./test.sh: line 4: 0: command not found
end new file

What happened was that bash had read through the "sleep 20\n" line (including the line terminating newline) before sleeping. When it continued after that, it read the next command from the next byte position in the file, and since the second line had gotten two bytes longer ("start" -> "started"), it wound up reading the last two bytes of the new sleep line ("sleep 20\n" -> "0\n"), trying to execute "0" as a command, and getting an error. It then runs the final line of the new contents. Pretty messy, right?

Fortunately, there's a way to force bash to load the entire script into memory so it won't get confused if it gets changed while running:

#!/bin/bash
{
    echo start old file
    sleep 20
    echo end old file
    exit
}

The { forces the shell to read at least through the matching } in order to properly parse the command, and the exit then makes sure it'll never try to execute anything that gets added after that }.

BTW, I was testing this with bash version 3.2.48(1)-release (on OS X 10.8.5); other versions may behave differently...

UPDATE: Other versions do indeed behave differently. I tried the same test with version 4.3.0(1)-release (under NetBSD 6.1.4), and it continued to run the old code after the file's contents had been replaced. Apparently it now buffers the file in 8KB blocks (see this unix.se answer).

Community
  • 1
  • 1
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • 3
    Under bash 5..0.3(1)-release on linux, it reads in 8KB blocks... but this doesn't prevent the error (in the version without the {}s), because, after the sleep is done, it seeks back to the position in the file just after the sleep! Strange behavior. {}s still protect it, good answer. – Don Hatch Dec 25 '19 at 09:25
  • So on recent bash versions this trick no longer prevents runtime script modification? Do other shells still allow this trick or have they all switched to partial buffering? While script modification during runtime should not be done anyways it still feels like a regression to remove the ability to prevent runtime modification via this trick. – user2280032 Apr 10 '21 at 10:56
  • 1
    @user2280032 As far as I know the `{ ... exit; }` trick still works, but it really is a *trick*, not something the shell is required to allow. I'd consider modifying a script during execution to be a case of [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior), meaning that the shell can legitimately do *anything* and you're not really allowed to complain about it. Or, to put it another way, you can't expect the shell to react in a reasonable way when you do something unreasonable to it, and modifying a script during execution is unreasonable. – Gordon Davisson Apr 10 '21 at 17:24
-1

The shell loads the script into memory and spawns a separate process, so the old script will continue to run unmodified.

Obviously, if you try to re-run it you'll get the new version.

If you're trying to introduce some new functionality during runtime, I would suggest writing to a temporary file and then executing that file using either the $() or `` (backtick) syntax to spawn a new process.

Alternatively, you can use exec to replace the currently running program completely.

Donovan
  • 15,917
  • 4
  • 22
  • 34
  • 1
    Good advice in general, but it seems that there's no guarantee that bash will have read the *entire* original file up front; rather, it appears to read scripts in chunks of fixed size - see http://stackoverflow.com/a/20722200/45375. Thus, overwritten content *could* get executed. – mklement0 Jan 13 '14 at 17:06
  • 1
    This answer starts with an incorrect statement and seems rather muddled. "The shell loads the script into memory" - no, it effectively reads as it goes; run Gordon Davisson's example to demonstrate that. "and spawns a separate process" - After it has started reading the script? No, bash doesn't do that. – Don Hatch Dec 25 '19 at 09:36
-3

Another mechanism, similar to what Donovan mentioned, is to just cat the file to stdout and pipe that into bash:

cat my_script.sh | bash

However, I don't know if there are any gotchas with pipe buffering on a given OS. (I'm on Ubuntu 18.04).

And of course, this only works for scripts that only depend on one source file that's being modified.

Eric Cousineau
  • 1,944
  • 14
  • 23
  • From here, it looks like at least GNU `cat` is unbuffered: https://superuser.com/a/896684/733549 – Eric Cousineau Aug 03 '20 at 18:28
  • 2
    This doesn't solve the problem in general (although it will probably work if the script is small enough, in which case the whole file will be read into cat's input buffer and/or the pipe's buffer all at once). If the file is overwritten before cat is done reading it, then the problem happens. – Don Hatch Sep 18 '21 at 19:10