33

I was looking for the difference between the "." and "source" builtin commands and a few sources (e.g., in this discussion, and the bash manpage) suggest that these are just the same.

However, following a problem with environment variables, I conducted a test. I created a file testenv.sh that contains:

#!/bin/bash
echo $MY_VAR

In the command prompt, I performed the following:

> chmod +x testenv.sh
> MY_VAR=12345
> ./testenv.sh

> source testenv.sh
12345
> MY_VAR=12345 ./testenv.sh
12345

[note that the 1st form returned an empty string]

So, this little experiment suggests that there is a difference after all, where for the "source" command, the child environment inherits all the variables from the parent one, where for the "." it does not.

Am I missing something, or is this is an undocumented/deprecated feature of bash?

[ GNU bash, version 4.1.5(1)-release (x86_64-pc-linux-gnu) ]

62

Short Answer

In your question, the second command uses neither the . shell built-in nor the source built-in. Instead, you are actually running the script in a separate shell, by invoking it by name (like any other executable file). This does give it a separate set of environment variables (though if you export an environment variable in its parent shell, it'll be included). If you change the / to a space, then that would run it with the . built-in, which is equivalent to source.

Extended Explanation

This is the syntax of the source shell built-in, which executes the contents of a script in the current shell (and thus with the current shell's environment variables):

source testenv.sh

This is the syntax of the . built-in, which does do the same thing as source:

. testenv.sh

However, this syntax runs the script as an executable file, launching a new shell to run it:

./testenv.sh

That is not using the . built-in. Rather, . is part of the path to the file you are executing. Generally speaking, you can run any executable file in a shell by invoking it with a name that contains at least one / character. To run a file in the current directory, preceding it by ./ is thus the easiest way. Unless the current directory is in your PATH, you cannot run the script with the command testenv.sh. This is to prevent people from accidentally executing files in the current directory when they intend to execute a system command or some other file that exists in some directory listed in the PATH environment variable.

Since running a file by name (rather than with source or .) runs it in a new shell, it will have its own set of environment variables. The environment variables do inherit from the environment variables of the calling process (which in this case is your interactive shell). However, for an environment variable to be passed to the new shell, one of the following must be the case:

  • The environment variable has been exported. Use the export shell built-in for this. In your example, you can use export MY_VAR=12345 to set and export the variable in one step, or if it is already set you can simply use export MY_VAR.

  • The environment variable is explicitly set and passed for the command you're running. This usually accomplishes that:

    MY_VAR=12345 ./testenv.sh
    

./ Syntax for Scripts Requires a Hashbang Line to Work (Correctly)

By the way, please note that, when you invoke an executable by name as above (and not with the . or source shell built-ins), what shell program is used to run it is not usually determined by what shell you're running it from. Instead:

  • For binary files, the kernel may be configured to run files of that particular type. It examines the first two bytes of the file for a "magic number" that indicates what sort of binary executable it is. This is how executable binaries are able to run.

    This is, of course, extremely important, because a script can't run without a shell or other interpreter, which is an executable binary! Plus, many commands and applications are compiled binaries rather than scripts.

    (#! is the text representation of the "magic number" indicating a text executable.)

  • For files that are supposed to run in a shell or other interpreted language, the first line looks like:

    #!/bin/sh
    

    /bin/sh may be replaced with whatever other shell or interpreter is intended to run the program. For example, a Python program might start with the line:

    #!/usr/bin/python
    

    These lines are called hashbang, shebang, and a number of other similar names. See this FOLDOC entry, this Wikipedia article and Is #!/bin/sh read by the interpreter? for more information.

  • If a text file is marked executable and you run it from your shell (like ./filename) but it doesn't begin with #!, the kernel fails to execute it. However, seeing that this has happened, your shell will try to run it by passing its name to some shell. There are few requirements placed on what shell that is ("the shell shall execute a command equivalent to having a shell invoked..."). In practice, some shells--including bash*--run another instance of themselves, while others use /bin/sh. I highly recommend you avoid this and use a hashbang line instead (or run the script by passing it to the desired interpreter, e.g., bash filename).

    *GNU Bash manual, 3.7.2 Command Search and Execution: "If this execution fails because the file is not in executable format, and the file is not a directory, it is assumed to be a shell script and the shell executes it as described in Shell Scripts."

  • 2
    What i found useful on source is that the functions became available from bash without need to load or launch again. Example #!/bin/bash function olakease {echo olakease;}. Once you load it with source file.sh you can directly call olakease from bash. I really like that. Source executes then loads lot of things, the dot . is only for execution and is something like use bash file.sh – erm3nda Jun 9 '15 at 3:49
  • 2
    @erm3nda . has this behavior as well: . file.sh and source file.sh do exactly the same thing, including retaining functions defined in file.sh. (Perhaps you are thinking of ./file.sh, which is different. But that does not use the . builtin; instead, . is part of the path.) – Eliah Kagan Feb 11 '17 at 12:28
  • Oh!, i didn't read carefully the .[space]file. Thank you so mach. – erm3nda Feb 11 '17 at 13:30
13

Yes, you're missing something.

I think you're confusing the '.' that means current directory, as in ./testenv.sh and the '.' that means source (which is a built-in command). So in the case when '.' means source it would be . ./testenv.sh. Make sense?

So try this:

MY_VAR=12345 
. ./testenv.sh
  • 2
    The ./ tells it exactly where the file it, without it, bash will look through PATH first, then try the current dir if it did not find it. If bash is running in POSIX mode, and you don't provide a path to the file (like ./), it will only search in PATH, and fail to find the file if the current dir is not in PATH. – geirha Aug 30 '12 at 8:00
  • @geirha Yes, you're right, source (and .) actually will check $PATH first, even though they are not really running the script in the usual sense. My (former) comment was incorrect. – Eliah Kagan Aug 30 '12 at 9:31
  • Short and to the point +1 – David Morales Aug 25 '16 at 18:35

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.