15

UPDATE: this is a repost of How to make shell scripts robust to source being changed as they run

This is a little thing that bothers me every now and then:

  1. I write a shell script (bash) for a quick and dirty job
  2. I run the script, and it runs for quite a while
  3. While it's running, I edit a few lines in the script, configuring it for a different job
  4. But the first process is still reading the same script file and gets all screwed up.

Apparently, the script is interpreted by loading each line from the file as it is needed. Is there some way that I can have the script indicate to the shell that the entire script file should be read into memory all at once? For example, Perl scripts seem to do this: editing the code file does not affect a process that's currently interpreting it (because it's initially parsed/compiled?).

I understand that there are many ways I could get around this problem. For example, I could try something like:

cat script.sh | sh

or

sh -c "`cat script.sh`"

... although those might not work correctly if the script file is large and there are limits on the size of stream buffers and command-line arguments. I could also write an auxiliary wrapper that copies a script file to a locked temporary file and then executes it, but that doesn't seem very portable.

So I was hoping for the simplest solution that would involve modifications only to the script, not the way in which it is invoked. Can I just add a line or two at the start of the script? I don't know if such a solution exists, but I'm guessing it might make use of the $0 variable...

Community
  • 1
  • 1
  • 3
    This is a duplicate of http://stackoverflow.com/questions/2285403/how-to-make-shell-scripts-robust-to-source-being-changed-as-they-run – camh Feb 27 '10 at 23:16
  • Thanks, camh! This is perfect. – A Real Live Operator Mar 01 '10 at 19:26
  • You can delete the script while running, to avoid editing. (This is probably what [ephemient](http://stackoverflow.com/users/20713/ephemient) and [Ryan Thompson](http://stackoverflow.com/users/125921/ryan-thompson) meant.) See [this post](http://stackoverflow.com/questions/8335747/emacs-workflow-to-edit-bash-scripts-while-they-run) for the reason and how to automate it. – teika kazura Mar 31 '12 at 02:26
  • As mentioned in the question, this is the same question as: [How to make shell scripts robust to source being changed as they run](https://stackoverflow.com/questions/2285403/how-to-make-shell-scripts-robust-to-source-being-changed-as-they-run) – giraffesyo Dec 08 '21 at 18:32

7 Answers7

17

The best answer I've found is a very slight variation on the solutions offered to How to make shell scripts robust to source being changed as they run. Thanks to camh for noting the repost!

#!/bin/sh
{
    # Your stuff goes here
    exit
}

This ensures that all of your code is parsed initially; note that the 'exit' is critical to ensuring that the file isn't accessed later to see if there are additional lines to interpret. Also, as noted on the previous post, this isn't a guarantee that other scripts called by your script will be safe.

Thanks everyone for the help!

Community
  • 1
  • 1
4

Use an editor that doesn't modify the existing file, and instead creates a new file then replaces the old file. For example, using :set writebackup backupcopy=no in Vim.

ephemient
  • 198,619
  • 38
  • 280
  • 391
2

How about a solution to how you edit it.

If the script is running, before editing it, do this:

mv script script-old
cp script-old script
rm script-old

Since the shell keep's the file open as long as you don't change the contents of the open inode everything will work okay.

The above works because mv will preserve the old inode while cp will create a new one. Since a file's contents will not actually be removed if it is opened, you can remove it right away and it will be cleaned up once the shell closes the file.

R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
  • Thanks, this is very helpful to know! It's pretty painless for me to keep in mind, but not the most ideal solution: it would be good to have something contained in the script file itself which requires the entire file be read at once. For example, if multiple developers are maintaining the same script, and it can run for a very long time, then you'd need all of the developers to ensure that they do this mv-cp-rm trick every time they edit the file... – A Real Live Operator Feb 25 '10 at 20:02
  • 1
    Slightly simplified: `cp script script-old; mv script-old script` –  Feb 25 '10 at 20:46
  • You can simply delete the original file. See my comment to the question. – teika kazura Mar 31 '12 at 02:27
  • My answer has something like this contained within the script: https://stackoverflow.com/a/70426690/1887014 – Acorn Dec 20 '21 at 18:56
1

According to the bash documentation if instead of

#!/bin/bash
body of script

you try

#!/bin/bash
script=$(cat <<'SETVAR'
body of script
SETVAR)
eval "$script"

then I think you will be in business.

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
  • Thanks, Norman! This works perfectly if you ensure that your script ends in 'exit'. It has the downside, however, that you lose you pretty-print color formatting in emacs... – A Real Live Operator Mar 01 '10 at 19:28
0

Consider creating a new bang path for your quick-and-dirty jobs. If you start your scripts with: #!/usr/local/fastbash

or something, then you can write a fastbash wrapper that uses one of the methods you mentioned. For portability, one can just create a symlink from fastbash to bash, or have a comment in the script saying one can replace fastbash with bash.

swestrup
  • 4,079
  • 3
  • 22
  • 33
0

If you use Emacs, try M-x customize-variable break-hardlink-on-save. Setting this variable will tell Emacs to write to a temp file and then rename the temp file over the original instead of editing the original file directly. This should allow the running instance to keep its unmodified version while you save the new version.

Presumably, other semi-intelligent editors would have similar options.

Ryan C. Thompson
  • 40,856
  • 28
  • 97
  • 159
  • Thanks, this is a great tip for emacs users, and I will probably make it my default setting from now on. But there's still the problem of defensively protecting my running script from being screwed up if someone else edits it... – A Real Live Operator Mar 01 '10 at 19:01
  • It doesn't work. Probably it's meant only literally for breaking hard links, not for replacing the file with a new save. – teika kazura Mar 31 '12 at 02:24
0

A self contained way to make a script resistant to this problem is to have the script copy and re-execute itself like this:

#!/bin/bash

if [[ $0 != /tmp/copy-* ]] ; then
    rm -f /tmp/copy-$$
    cp $0 /tmp/copy-$$
    exec /tmp/copy-$$ "$@"
    echo "error copying and execing script"
    exit 1
fi

rm $0

# rest of script...

(This will not work if the original script begins with the characters /tmp/copy-)

(This is inspired by R Samuel Klatchko's answer)

Acorn
  • 768
  • 6
  • 15