Yes Virginia, there is a Santa Clause
I know this is rather late to the game, but maybe things have changed since the original answer that was accepted. The reality is that now you can source from a non-bash script while running bash. That script simply needs to print the lines you want sourced, and doesn't need to print them into a temporary file for you to source.
TLDR:
My pathcleaner.rb script (used to remove duplicate entries from my $PATH):
#!/usr/bin/env -S ruby
path = ENV['PATH'].strip.split(/:/).uniq.join(':')
puts "PATH=\"#{path}\""
The command that I run to execute that script, while actually affecting my current environment (instead of just the environment existing within the context of the ruby script):
$ source <(pathcleaner.rb)
The Longer Explanation
So, while it is true that the typical source mechanism of . scriptfile.sh does not work unless the scriptfile is a bash (or other compatible shell) script, at some point bash added the concept of process substitution (see postscript far below for an examination of when):
Process Substitution source
Process substitution allows a process's input or output to be referred
to using a filename. It takes the form of <(list) or >(list). The
process list is run asynchronously, and its input or output appears
as a filename. This filename is passed as an argument to the current
command as the result of the expansion. If the >(list) form is used,
writing to the file will provide input for list. If the <(list) form
is used, the file passed as an argument should be read to obtain the
output of list. Note that no space may appear between the < or > and the
left parenthesis, otherwise the construct would be interpreted as a
redirection. Process substitution is supported on systems that
support named pipes (FIFOs) or the /dev/fd method of naming open
files.
What this all means is that the process you create by running a non-bash script can be used as the source for the "contents" of a fictitious or "virtual" file that bash pretends is the source of input to the source command.
Let's dig into what this is doing behind the scenes. If we use the following command, we can see what bash is actually doing with my pathcleaner.rb input script discussed above:
$ echo <(pathcleaner.rb)
/dev/fd/63
OK, so the <(pathcleaner.rb) argument is replaced with the file-descriptor path /dev/fd/63. What happens when we cat the <(pathcleaner.rb) argument instead?
$ cat <(pathcleaner.rb)
PATH="~/bin:/usr/lib/postgresql/11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:~/.rvm/bin"
It shows us that the virtual file tied to the file descriptor contains the output of the process from my pathcleaner.rb script. The really neat thing about this is that despite its resemblance to simple input redirection, this is a completely different animal and can be used with any command, and in place of a filename, anywhere in your command line. Let me illustrate.
Let's say I've created three ruby scripts: one.rb, two.rb, and three.rb. Now if I wanted to try regular input redirection on all three at once, what happens?
$ cat <./one.rb <./two.rb <./three.rb
#!/usr/bin/env -S ruby
puts 'THREE="Third Script Output"'
Wait a minute! Only the last input redirection is processed. It seems that each input redirection takes over as the input for the command, and that the others are simply ignored. What happens if we actually use the process substitution to get what we really wanted with this command?
$ cat <(cat ./one.rb) <(cat ./two.rb) <(cat ./three.rb)
#!/usr/bin/env -S ruby
puts 'ONE="First Script Output"'
#!/usr/bin/env -S ruby
puts 'TWO="Second Script Output"'
#!/usr/bin/env -S ruby
puts 'THREE="Third Script Output"'
Note: Yes, I know that we could simply accomplish this specific task by simply passing the filenames to cat without redirection. This is just an example demonstrating the functionality.
So what is clear from this is that you can put any command inside the parentheses in the process substitution, and have as many process substitutions as you like (within the OS limit of maximum available file descriptors, which is 9,223,372,036,854,775,807 on my Linux system), and your command line will treat it as though each instance was a file containing the output of the encapsulated command instead.
So, to make a long story long, you can:
- Write a non-bash script to programmatically generate the commands you want bash to execute. For example, my
pathcleaner.rb script.
- Pass that script inside a process substitution argument. For example:
<(pathcleaner.rb), or <(/path/to/pathcleaner.rb) if it is not on the path.
- And have any command treat that argument as a file for it to process. For example telling
source to execute the output of my ruby script with source <(pathcleaner.rb)
PS
After doing a bit of research, it looks like this feature has been in bash since 1992 and was in the korn shell as early as 1988. I'm guessing that it's just not a feature that many people know about, hence the prevalence of suggestions to create an intermediate file with your script and then source that file in the other answers here.
That said, to his credit, rampion, who has the highest rated answer so far (from 2010) does mention this feature briefly in a 2022 comment on the answer provided by seeder, but he refers to it as "shell redirection", and shell redirection is a much more limited functionality than what we actually have here with process substitution as demonstrated with my input redirection example above.