Another way is by calling the interpreter and passing the path to the script to it:
/bin/sh /path/to/script
The dot and source are equivalent. (EDIT: no, they're not: as KeithB points out in a comment on another answer, "." only works in bash related shells, where "source" works in both bash and csh related shells.) It executes the script in-place (as if you copied and pasted the script right there). This means that any functions and non-local variables in the script remain. It also means if the script does a cd into a directory, you'll still be there when its done.
The other ways of running a script will run it in its own subshell. Variables in the script are not still alive when it's done. If the script changed directories, then it doesn't affect the calling environment.
/path/to/script and /bin/sh script are slightly different. Typically, a script has a "shebang" at the beginning that looks like this:
#! /bin/bash
This is the path to the script interpreter. If it specifies a different interpreter than you do when you execute it, then it may behave differently (or may not work at all).
For example, Perl scripts and Ruby scripts begin with (respectively):
#! /bin/perl
and
#! /bin/ruby
If you execute one of those scripts by running /bin/sh script
, then they will not work at all.
Ubuntu actually doesn't use the bash shell, but a very similar one called dash. Scripts that require bash may work slightly wrong when called by doing /bin/sh script
because you've just called a bash script using the dash interpreter.
Another small difference between calling the script directly and passing the script path to the interpreter is that the script must be marked executable to run it directly, but not to run it by passing the path to the interpreter.
Another minor variation: you can prefix any of these ways to execute a script with eval, so, you can have
eval sh script
eval script
eval . script
and so on. It doesn't actually change anything, but I thought I'd include it for thoroughness.
Bash knows nothing about ELF. It simply sees that you asked it to run an external program, so it passes the name you gave it as-is to execve(2)
. Knowledge of things like executable file formats, shebang lines, and execute permissions lives behind that syscall, in the kernel.
(It is the same for other shells, though they may choose to use another function in the exec(3)
family instead.)
In Bash 4.3, this happens on line 5195 of execute_cmd.c
in the shell_execve()
function.
If you want to understand Linux at the source code level, I recommend downloading a copy of Research Unix V6 or V7, and going through that rather than all the complexity that is in the modern Linux systems. The Lions Book is a good guide to the code.
V7 is where the Bourne shell made its debut. Its entire C source code is just a bit over half the size of just that one C file in Bash. The Thompson shell in V6 is nearly half the size of the original Bourne shell. Yet, both of these simpler shells do the same sort of thing as Bash, and for the same reason. (It appears to be an execv(2)
call from texec()
in the Thompson shell and an execve()
call from execs()
in the Bourne shell's service.c
module.)
Best Answer
Well, the exact sequence may vary, as there might be a shell alias or function that first gets expanded/interpreted before the actual program gets executed, and then differences for a qualified filename (
/usr/libexec/foo
) versus something that will be looked for through all the directories of thePATH
environment variable (justfoo
). Also, the details of the execution may complicate matters, asfoo | bar | zot
requires more work for the shell (some number offork(2)
,dup(2)
, and, of course,pipe(2)
, among other system calls), while something likeexec foo
is much less work as the shell merely replaces itself with the new program (i.e., it doesn'tfork
). Also important are process groups (especially the foreground process group, all PIDs of which eatSIGINT
when someone starts mashing on Ctrl+C, sessions, and whether the job is going to be run in the background, monitored (foo &
) or background, ignored (foo & disown
). I/O redirection details will also change things, e.g., if standard input is closed by the shell (foo <&-
), or whether a file is opened as stdin (foo < blah
).strace
or similar will be informative about the specific system calls made along this process, and there should be man pages for each of those calls. Suitable system level reading would be any number of chapters from Stevens's "Advanced Programming in the UNIX Environment" while a shell book (e.g., "From Bash to Z Shell") will cover the shell side of things in more detail.