46

There are several ways to execute a script. The ones I know are:

/path/to/script # using the path (absolute or relative)
. script        # using the . (dot)
source script   # using the `source` command

Are there any other way? What are the differences between them? Are there situations that I must use one and not another?

phunehehe
  • 20,240
  • Great to know, thanks to your question and answers below, especially Shawn's. I would like to add something which wasn't very apparent to me until I ran a few tests. Inclusion of a "/" in the second way above will take the command to mode 1 above. That is, while "./myscript.sh" follows mode 1, ". myscript.sh" sticks to mode 2. You did mention "using the path (absolute or relative)", but just wanted to make this apparent. – Arun Jan 12 '15 at 01:26

8 Answers8

31

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.

Shawn J. Goff
  • 46,081
  • 7
    Saying "Ubuntu actually doesn't use the bash shell" is imprecise and technically incorrect. Ubuntu does use the bash shell, the point is that sh corresponds to dash, but not to bash. – Faheem Mitha Apr 30 '11 at 16:16
  • @Shawn In the firts para you wrote "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." what do you mean by the second line here ? Can you please explain. – Geek Dec 06 '12 at 13:47
  • @Geek when you execute a script as a child process (the normal way), any variables and functions it defines (it's environment) go away when the process terminates. If you source the script, those variables and functions are created in the current environment; when the script finishes, the changes to the environment remain. – Shawn J. Goff Dec 06 '12 at 15:52
  • @ShawnJ.Goff thanks for the clarification. +1. – Geek Dec 07 '12 at 05:48
  • 1
    Plot-twist: Bourne Shell (sh) only accepts dot - not source as it is a bash-builtin. http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#dot

    So I'd say the most portable way is a dot.

    – dza Jun 10 '16 at 18:28
  • . works on Z shell, but you have to prepend file with path (./, ~/, /path/to/file/) – Andre Figueiredo Jun 10 '18 at 06:27
9

Most people debug shell scripts by adding the following debuging flags to the script:

set -x     # Print command traces before executing command.
set -v     # Prints shell input lines as they are read.
set -xv    # Or do both

But this means you need to open the file with an editor (assuming you have permissions to edit the file), adding a line like set -x, save the file, then execute the file. Then when you are done you need to follow the same steps and remove the set -x, etc. etc. This can be tedious.

Instead of doing all that, you can set the debugging flags on the commandline:

$ bash -x ~/bin/ducks
+ du -cks -x dir1 dir2 dir3 file1 file2 file3
+ sort -n
+ tail .ducks
123 etc
424 bin
796 total



$ sh -xv ~/bin/ducks  
#!/usr/bin/env bash

# Find the disk hog
# Borrowed from http://oreilly.com/pub/h/15
...
...
Stefan Lasiewski
  • 19,754
  • 24
  • 70
  • 85
  • 3
    A related tip: I've gotten into the habit of putting emulate sh 2>/dev/null at the top of my shell scripts. When run with zsh, this puts it into POSIX compatible mode. When run with other shells, the line has no effect. Then I can run the script with zsh -x /path/to/script. I like zsh here because it provides better traces than bash or ksh. – Gilles 'SO- stop being evil' Nov 03 '10 at 19:03
7

Shawn J. Goff made a lot of good points, but did not include the whole story:

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.

A lot of system scripts (like in init.d, in /etc and so on) have a shebang #!/bin/sh, but /bin/sh is in fact a symbolic link to another shell - in former times /bin/bash, nowadays /bin/dash. But when one of them is invoked as /bin/sh, they behave differently, i.e. they stick to POSIX-compatibility-mode.

How do they do this? Well, they inspect how they were invoked.

Can a shellscript itself test how it was invoked, and do different things, depending on that? Yes, it can. So the way you invoke it can always lead to different results, but of course it is seldom done to annoy you. :)

As a rule of thumb: If you're learning a specific shell like bash, and write commands from a bash tutorial, put #!/bin/bash in the headline, not #!/bin/sh, except where otherwise noted. Else your commands might fail. And if you haven't written a script yourself, invoke it directly (./foo.sh, bar/foo.sh) instead of guessing a shell (sh foo.sh, sh bar/foo.sh). The shebang should invoke the right shell.

And here are two other kinds of invocation:

cat foo.sh | dash
dash < foo.sh
Faheem Mitha
  • 35,108
user unknown
  • 10,482
4

. and source are equivalent in that they don't spawn a subprocess but execute commands in the current shell. This is important when the script sets environment variables or changes current working directory.

Using the path or giving it to /bin/sh creates a new process in which commands are executed.

mouviciel
  • 1,235
2
sh script
bash script

I am pondering if there are more...

. and source are the same. After execution any changes of environment in script would be kept. Usually, it would be used to source a Bash library, so the library can be re-use in many different scripts.

Also it's a good way to keep current directory. If you change directory in script, it won't be applied in the shell which you execute that script. But if you source it to run it, after the script exits, the current directory will be kept.

1

. and source are a bit different in zsh at least(that's what I use) because

source file

Works,while

. file

doesn't,it needs

. ./file
bollovan
  • 523
1

Does "userland exec" count as a different way? Userland exec loads code and gets it to execute without the use of an execve() system call.

1
. ./filename
# ( dot space dot slash filename )

Runs the script in the current shell when the directory is not in the path.

Stefan
  • 25,300