I was writing a bash script, and happened to updated the code (saved the script file to disk) while the script was waiting for some input in a while
loop. After I returned to the terminal and continued with the previous invocation of the script, bash gave an error about the file syntax:
/home/aularon/bin/script: line 58: unexpected EOF while looking for matching `"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file
So I tried doing the following:
1st: create a script, self-update.sh
let's call it:
#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';
What the script does is reading its code, changing the word 'BEFORE' into 'AFTER', then re-write itself with the new code.
2nd Run it:
chmod +x self-update.sh
./self-update.sh
3rd wonder…
aularon@aularon-laptop:~$ ./self-update.sh
String: AFTER!.
Now, I wouldn't have guessed that on the same invocation it would output AFTER!, on the second run for sure, but not on the first one.
So my question is: is it intentional (by design)? or it is because of the way bash runs the script? Line by line or command by command. Is there any good use of such behaviour? Any example of it?
Edit: I tried reformatting the file to put all command in one line, it does not work now:
#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';
Output:
aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.
While when moving the echo
string to the next line, separating it from the rewriting (cp
) call:
#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';
And now it works again:
aularon@aularon-laptop:~$ ./self-update.sh
String: AFTER!.
Best Answer
This is by design. Bash reads scripts in chunks. So it'll read a portion of the script, run any lines that it can, and then read the next chunk.
So you run into something like this:
Where this becomes even more problematic is that if you edit anything before byte 256. Lets say you delete a couple of lines. Then the data in the script that was at byte 256, is now somewhere else, say at byte 156 (100 bytes earlier). Because of this, when bash continues reading, it's going to get what was originally 356.
This is just an example. Bash doesn't necessarily read 256 bytes at a time. I don't know exactly how much it reads at a time, but it doesn't matter, behavior is still the same.