0

I have a launchd task that runs a bash script periodically, and this works great. However, if I change the script while the task is running, either by editing it directly, or swapping it with an updated copy, it can cause the running task to spew out a load of syntax errors or worse, have unpredictable behaviour.

The problem appears to be that instead of loading the script into memory, or holding open the file as it was at the time of execution, launchd (or probably bash itself) is loading the live file line-by-line, this means that any changes to the script appear to be reflected in real time, leading to errors.

To try to illustrate the problem, consider the following:

if [ "$foo" = '1' ]; then
    do_something_that_takes_time
fi

Now, imagine the script is running at line 2 (do_something_that_takes_time) and I swap out the script, removing the if/fi block entirely. When the script moves onto line 3, it will no longer find fi, and will continue executing expecting to find a fi but never will, resulting in an eventual error (unexpected end of file). It may also end up skipping commands (that were moved up as a result of removing lines).

Of course I can avoid this issue by unloading the launchd task, updating, then reloading it, but this isn't very convenient, especially for tasks that can take a long time to complete (as I'd prefer to let them finish, but there seems no way to do so and swap in the file at that point). Otherwise I have to add in some mechanism for prevent the task from re-launching, so I can swap in the new script at my leisure, but this seems like an unnecessary extra step when Bash should just be running the script I told it to, at the time it started.

So my question is this; is there some way that I can force launchd (or Bash) to execute the script only as it was when the task began? For example by fully loading it into memory before executing, or loading from an open file-handle that won't change?

I'm posting this here because I'm not entirely sure if this is a quirk of Bash in general, or potentially Mac specific in some way. The volume I'm executing from is using APFS, so should be copy-on-write, so I wouldn't expect this behaviour to occur (as an open file handle should continue to point to the old file, ignoring any changes that are made).

| improve this question | | | | |
  • Run script Atomically? I was going to change it to “Automatically” but that’s not what your asking. Actually, you could make an argument that what your asking roughly parallels atomic covalence. :-). – Allan Mar 29 at 11:46
  • 1a) How to make shell scripts robust to source being changed as they run 1b) Add some auto-update feature in the bash script 1c) Use the watch folder feature of launchctl to reload the launch daemon/agent – klanomath Mar 29 at 11:56
  • Thanks @klanomath, 1a seems to be exactly what I need! Should this question be answered as per that one, or closed, since it's not a Mac-specific quirk? – Haravikk Mar 30 at 13:08
  • @Haravikk Please leave it open and either Allan, you yourself or me myself will make my comment (or better the linked answer at stackoverflow) an answer here. – klanomath Mar 30 at 13:10
  • @Haravikk 1b) and 1c) are meant as non-excluding supplements to 1a). Whether they work depends on your bash script. Example 1a+1b: {if [ "$foo" = '1' ]; then do_something_that_takes_time fi exit}; {if [script_update_folder not empty]; then update_script && clear script_update_folder fi exit} ... – klanomath Mar 30 at 13:18
1

So my question is this; is there some way that I can force launchd (or bash) to execute the script only as it was when the task began?

No. It’s not a quirk of bash or launchd. launchd loads the job (plist) into memory, not the script. The script gets called per it’s “start” definition like RunAtLoad or CalendarInterval.

Also, the nuance here is when the task began. Well, that depends on when the task was configured to be started. Just because the job definition is loaded doesn’t mean the task has started.

If you define the job within the job definition using Program and ProgramArguments it can theoretically, do what you want. However, there’s no script logic; it’s just a command and it’s arguments.

Now if your script takes time or to put it another way, it is still running, modifying it will of course cause errors. This would be like you executing your script, then simultaneously in another bash session modifying it. It’s going to have issues.

launchd just executes the script like you would if you typed the command manually.

| improve this answer | | | | |
  • The easiest solution here, I think, is just to put the commands into an external script file and have launchd execute that, rather than the raw commands. – TJ Luoma Mar 29 at 12:59
  • @TJLuoma the op is editing the bash script not launchd plists – user151019 Mar 29 at 13:55
  • Ah, my apologies. I misread / misunderstood. – TJ Luoma Mar 29 at 20:33
  • @Allan Would you mind adding How to make shell scripts robust to source being changed as they run to your answer (after doing some thorough testing 🤔) as a possible way out. Then I don't have to write an additional answer. – klanomath Mar 30 at 13:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .