When you leave the kernel to determine the interpreter to be used with a script file using a shebang, the kernel must take a similar "lock" on the script file as it does for binaries. (This is a kernel-internal "lock" that you can only observe from userspace. If the file is open for writing by anyone, the lock fails with ETXTBUSY.)
It is trivially to avoid that. You are free to edit script files even when they are being interpreted, if you execute the interpreter instead with the script name as a parameter.
In OP's case, it means executing bash %s, where %s is replaced with the contents of shFile. This also means the file referred to by shFile does not need to be executable; only readable.
If we assume shFile does not contain any ' characters, then in Linux it is sufficient to use
char *jobcmd = NULL;
FILE *job;
if (asprintf(&jobcmd, "bash '%s'", shFile) == -1) {
/* Typically an out of memory error. */
fprintf(stderr, "%s.\n", strerror(errno));
exit(EXIT_FAILURE);
}
errno = ENOMEM;
job = popen(jobcmd, "r");
if (!job) {
fprintf(stderr, "Cannot execute %s: %s.\n", shFile, strerror(errno));
exit(EXIT_FAILURE);
}
free(jobcmd);
jobcmd = NULL;
In general, you should consider a custom routine that constructs the sh shell command needed to execute the desired interpreter with the file name as a parameter, applying any escaping as necessary. (In POSIX sh shells, you can escape single quotes in a single-quoted string using '"'"'.)
If the script is executed only once, a custom replacement for popen()/getline() or fread() or fgetc()/pclose() can be used to write the script to the standard input of the interpreter (here, bash -s), while also retrieving the output of the script into a single array, line per line (perhaps using a callback function?), or character-by-character. This approach has the benefit of the script never being stored on any filesystem.
In general, it is much more common, and definitely recommended, to install the scripts as part of the binary, and supply any specifics to it via command-line parameters (for example, bash /usr/share/yourapp/somescript.bash 'arg1' 'arg2').
In Linux and BSDs, these scripts are usually put somewhere under /usr/lib/yourapp/ or /usr/share/yourapp/.