Yes. Use a non-interactive shell to source the script. Or, if you must use an interactive shell, invoke set +m
(but this is unusual and may be a sign that you're going about things in the wrong way).
The issue has absolutely nothing whatsoever to do with sourcing versus not-sourcing. The issue is one of running an interactive shell with job control enabled versus a non-interactive shell without job control. You can source from either, you just happen to be sourcing from an interactive shell.
How does the system decide if a shell is interactive? An interactive shell is any shell that's invoked with the -i option, or that is not given any script to execute and has stdin and stderr pointing to a terminal (presumably, there's a human at the helm). Otherwise, the shell is considered non-interactive.
Interactive: bash
Non-interactive: bash my-script.sh
When a shell is interactive, each pipeline is considered an individual job. A human (presumably) interacting with the shell can suspend and resume these jobs, and put foreground jobs in the background and bring background jobs to the foreground. But how does the interactive shell implement such job control?
Imagine, for example, that you wanted to sort a file and then write the first 10 lines of the result to a file. You might run the following pipeline:
sort datafile | head > output
What if you then decide to terminate that job? You type Control-C (^C), which works by sending the foreground job the SIGINT signal. To properly terminate a job requires terminating each and every process that is part of that job. This is facilitated by putting all of those processes in the same process group and sending the signal to every member of that process group.
A process group id (pgid) is nothing but an integer in a process-related data structure, just like the process id (pid) and the parent's process id (ppid). A process group is a set of processes that share a common pgid value.
If there are many process groups on a system, how does the terminal know which process group to signal? The terminal itself is associated with a process group id, and this pgid is the terminal's foreground process group. In an interactive shell, when you run a command (or pipeline), it is run in its own process group and that process group becomes the terminal's foreground process group. You can change the foreground process group using job control (^Z, bg, fg).
In the ps
output you posted, the +
in the STAT column indicates that a process is a member of the foreground process group. Only these processes will receive keyboard-generated signals.
When using a non-interactive shell, each individual pipeline in a script is no longer considered a distinct job (unlike when they are entered at a command prompt). This makes sense since the commands would not have been grouped into a script if they were not part of a single task. Since the script itself is considered the only job, job control in non-interactive shells is seldom needed and is therefore disabled by default.
With job control disabled, the non-interactive shell does not create any process groups. Every command run by that shell inherits the shell's process group. This allows manipulating every process of every pipeline that the script runs by signaling a single process group. If this where not the case, if the non-interactive shell created a process group for each command/pipeline, using ^C to kill a script would be impossible because only the current command would receive the signal. As soon as that command exits, the next runs. The parent shell would be unaffected (sound familiar?).
Process groups also play a role in whether an individual process is allowed to interact with the terminal. Chaos would ensue if any process could unexpectedly take over and consume characters from a terminal. To prevent this, only processes in the foreground process group are allowed to read from the terminal (sometimes, the same is true of writing).
If you're still a bit lost, you're not alone. In my experience, most people do not have a solid grasp on how process groups, jobs, signals, and terminals are used to implement the interactive environment. The only way to get a handle on it is to research and experiment.
Your shell's man page covers its job control features. For a deeper dive, refer to POSIX - General Terminal Interface and glibc job control.
Regards,
Alister