Home > Mobile >  Bash differences in changes to running scripts
Bash differences in changes to running scripts

Time:08-05

I'm scratching my head about two seemingly different behaviors of bash when editing a running script.

This is not a place to discuss WHY one would do this (you probably shouldn't). I would only like to try to understand what happens and why.

Example A:

$ echo "echo 'echo hi' >> script.sh" > script.sh
$ cat script.sh
echo 'echo hi' >> script.sh
$ chmod  x script.sh
$ ./script.sh
hi
$ cat script.sh
echo 'echo hi' >> script.sh
echo hi

The script edits itself, and the change (extra echo line) is directly executed. Multiple executions lead to more lines of "hi".

Example B:

Create a script infLoop.sh and run it.

$ cat infLoop.sh
while true
do
    x=1
    echo $x
done
$ ./infLoop.sh
1
1
1
...

Now open a second shell and edit the file changing the value of x. E.g. like this:

$ sed --in-place 's/x=1/x=2/' infLoop.sh
$ cat infLoop.sh
while true
do
    x=2
    echo $x
done

However, we observe that the output in the first terminal is still 1. Doing the same with only one terminal, interrupting infLoop.sh through Ctrl Z, editing, and then continuing it via fg yields the same result.

The Question

Why does the change in example A have an immediate effect but the change in example B not?

PS: I know there are questions out there showing similar examples but none of those I saw have answers explaining the difference between the scenarios.

CodePudding user response:

There are actually two different reasons that example B is different, either one of which is enough to prevent the change from taking effect. They're due to some subtleties of how sed and bash interact with files (and how unix-like OSes treat files), and might well be different with slightly different programs, etc.

Overall, I'd say this is a good example of how hard it is to understand & predict what'll happen if you modify a file while also running it (or reading etc from it), and therefore why it's a bad idea to do things like this. Basically, it's the computer equivalent of sawing off the branch you're standing on.

Reason 1: Despite the option's name, sed --in-place does not actually modify the existing file in place. What it actually does is create a new file with a temporary name, then when it's finished that it deletes the original and renames the new file into its place. It has the same name, but it's not actually the same file. You can tell this by looking at the file's inode number with ls -li:

$ ls -li infLoop.sh 
88 -rwxr-xr-x 1 pi pi 39 Aug  4 22:04 infLoop.sh
$ sed --in-place 's/x=1/x=2/' infLoop.sh
$ ls -li infLoop.sh 
4073 -rwxr-xr-x 1 pi pi 39 Aug  4 22:05 infLoop.sh

But bash still has the old file open (strictly speaking, it has an open file handle pointing to the old file), so it's going to continue getting the old contents no matter what changed in the new file.

Note that this doesn't apply to all programs that edit files. vim, for example, will rewrite the contents of existing files (unless file permissions forbid it, in which case it switches to the delete&replace method). Appending with >> will always append to the existing file rather than creating a new one.

(BTW, if it seems weird that bash could have a file open after it's been "deleted", that's just part of how unix-like OSes treat their files. Files are not truly deleted until their last directory entry is removed and the last open file handle referring to them is closed. Some programs actually take advantage of this for security by opening(/creating) a file and then immediately "deleting" it, so that the open file handle is the only way to reach the file.)

Reason 2: Even if you used a tool that actually modified the existing file in place, bash still wouldn't see the change. The reason for this is that bash reads from the file (parsing as it goes) until it has something it can execute, runs that, then goes back and reads more until it has another executable chunk, etc. It does not go back and re-read the same chunk to see if it's changed, so it'll only ever notice changes in parts of the file it hasn't read yet.

In example B, it has to read and parse the entire while true ... done loop before it can start executing it. Therefore, changes in that part (or possibly before it) will not be noticed once the loop has been read & started executing. Changes in the file after the loop would be noticed after the loop exited (if it ever did).

See my answer to this question (and Don Hatch's comment on it) for more info about this.

  •  Tags:  
  • bash
  • Related