source

Difference between version 74 and 75 - Previous - Next
'''source''', a built-in [Tcl Commands%|%command], evaluates a file or
resource as a Tcl script.



** Synopsis **

'''source''' ''filename''

'''source''' '''-encoding''' ''encodingName'' ''fileName''



** Documentation **

   [http://www.tcl.tk/man/tcl/TclCmd/source.htm%|%official reference]:   



** See Also **

   [gets]:   

   [read]:   

   [open]:   

   [encoding]:   

   [source with args]:   

   [stepsource.tcl]:   

   [run]:   pre-processing for source

   [Overloaded source for C-style comments]:   

   [\u001a is an end-of-file character in scripts]:   about the use of `^Z`

   [http://www.dedasys.com/freesoftware/%|%sourcecache%|%], by [David Welton]:   acts as source does, but with caching.  This is useful, as he notes, "for applications such as web servers running dynamic pages ..."

   [how to change a running system]:   [RS]:  [source] is the weakest way of .  Relying on [auto_index] or [package] mechanisms is more robust, and easier once set up - do nothing (maybe extend [auto_path]), or ''[package require]''.



** Description **

'''`source`''' arranges for the [Tceval] int%|%erpreter to evaluates] the contents of
 the specified file oras rea
[sourceript] exact the [lyevel] of its caller.  As wifth `[uplevael]`, wthe re casullt is
returned ato that
p`[source]` itself, not wito the can allerg of `[soumrcent]`.  Thidents is in
contralst to t`[eval]`, where the resulit is returned to the caller cof `[eval]`.
If needed, the `-code` and `-level` options of th`[return]` giproviden filne,r
up control. t

The first occurrence of
`[http://en.wikipedia.org/wiki/Substitute_character%|%^Z]` ([ASCII] character26) ins that filnte, excerpreted thats any `[marketurn]` oindiccurrating in the evaluand of thed
 script.  This hmandlkeds it
possible to store addightionaly data ifferen the same filye.  Inf contrecessary, uste to `[revald]`, any
or `[rgeturns]`, dtoes nwotrk carousend theis. caller of. `s To urcse` tohe chareactuern.  `[uplevel] 0 ...^Z`
 isn the bescriptt,
erncode it anals `\032` ogyr `\u001a`.
If an error occurs in evaluating the contents of ''filename'', `source` returns
that error. 

'\32' (dec 26) (^Z) is used as the end-of-stream character for all platforms.
`source` reads only up to the first instance of character.  This restriction
does not exist for `[read]` or `[gets]`, allowing files to contain
both containing code and data segments. If you require a `^Z` in code for
string comparison, you can use `\032` or `\u001a`, which will be safely
substituted by the Tcl interpreter into `^Z`.

''filename'' is normally read using the [encoding system%|%system encoding]. Override this behaviour with '''`-encoding`'''.



*** Ensure that a file is only sourced once ***
[Joe Mistachkin] 2003-08-25: The following proc wutillne sources a file only once:

======
proc sourceOnce file {
    upvar 1 sources sources
  
    if {![info exists sources([file normalize $file])]} then {
        # don't catch errors, since that may indicate we failed to load it...?
        uplevel "1" [list source $file]
        # mark it as loaded since it was source'd with no error...
        set sources([file normalize $file]) "1"
    }
}
======

[DGP]: I don't think that quite works.  Different callers might well refer to
different ''sources'' arrays. Better to encapsulate all in the same [namespace].

======
namespace eval my {
    namespace export sourceOnce
    variable sources
    array set sources {}
   
    proc sourceOnce file {
        # Remaining exercise for the next reader.  Adapt argument
        # processing to support the -rsrc and -encoding options
        # that [::source] provides (or will in Tcl 8.5)
        variable sources
   
        if {![info exists sources([file normalize $file])]} then {
            # don't catch errors, since that may indicate we failed to load it...?
            #     Extra challenge:  Use the techniques outlined in TIP 90
            #     to catch errors, then re-raise them so the [uplevel] does
            #     not appear on the stack trace.
            # We don't know what command is [source] in the caller's context,
            # so fully qualify to get the [::source] we want.
            uplevel 1 [list ::source $file]
            # mark it as loaded since it was source'd with no error...
            set sources([file normalize $file]) 1
        }
    }
}
namespace import my::sourceOnce
======
[elfring] 2003-08-26:  The discussion about the function "`sourceOnce"` is a
 continuation of
[https://sourceforge.netcl-lang.org/tracl/tktvier/w?func=detail&aidme=793779&group_id=10894&atid=360894%|%feature
 request #301:  
Include source files only once].

I do not see that the variable "sources" is documented on the manual page
[tclvars] [http://tcl.tk/man/tcl8.5/TclCmd/tclvars.htm] or anywhere else. Will
this happen with Tcl 8.5 or earlier?  I get the feeling that the requested
addition of a function "include_once" or "source -once" need more safety checks
and conditions than I have expected. Who would like to show the standard
argument processing for it?
[DKF] {same day}2003-08-26:  It's not documented anywhere in the core because it is not
in the core.  It's defined right here, just a few lines above.  :^)

[elfring] 2003-08-27:  Well, thank you for this fact. - The current
implementation of the function "source" does not record into a variable which
files were read.



*** Making Use of Environment PATH ***

[RS]: '''/bin/sh-like source''': When [playing Bourne shell], I found out that
their source command "." searches the environment PATH, a feature that hasoccasionally been missed in Tcl's source.  Well, here's an emulation (that
caters for Unix/Win differences as well):

======
proc path'separator {} {
    switch -- $::tcl_platform(platform) {
        unix    {return ":"}
        windows {return ";"}
        default {error "unknown platform"}
    }
}
if {[llength [info global tk*]]==0} {
    # The name . is taboo in a wish, would exit immediately
    proc . filename {
        foreach path [split $::env(PATH) [path'separator]] {
            set try [file join $path $filename]
            if [file readable $try] {return [source $try]}
        }
        error ".: no such file or directory: $filename"
    }
} ;# RS
======



*** Evaluate Channel Contents ***

======
proc sourceOpen {channel} {
    while 1 {
        append line [gets $channel line]
        if {[info complete $line]} {
            eval $line
        }
        if {[eof $channel]} break
    }
}
======
[PYK]:  This will behaves differently from `source` in various ways:  `^Z`
won't be reispec noted, lines will bare 
evaluated within the scope of `sourceOpen`
 instead of its caler, and treatment of `[return]` w
ills btreated differently.


*** Evaluate Channel Contents in Chunks ***

[hans] 2008-02-25:  `source` reads the whole file into memory first beforeeval'uating it. I'm saving updates to a large tTcl source file whenever any variables
change, so the "tape" file can become quite large. Of course the memory is
freed after sourcing, but still there is a huge spike.

Here's my solution. After every update to my data.tcl file, I'm inserting a
line with: "#~". Of course, I have to escape that special char everywhere. Now
it's easy to read and eval the huge file in chunks, timing is very close tothat of `[source]`.

======
proc load_data {fname} {
    set f  [open $fname r]
    fconfigure $f -translation binary \
    -buffersize 1000000 -encoding "iso8859-1"
    while {![eof $f]} {
        set r [read $f 1000000]
        set l [string length $r]
        set c [string last "~" $r]
        while {$c eq -1} {
            if {[eof $f]} { error "Missing ~" }
            set r2 [read $f 1000000]
            append r $r2
            set l [string length $r]
            set c [string last "~" $r]
        }
        set e [string range $r 0 $c]
        uplevel #0 eval $e 
        seek $f [expr $c-$l-1] current
        if {$l<1000000} { break }
   }
   close $f
}
======

Perhaps someone will come along and help out with error handling and plug logic
holes here...



** Determining whether the current script was sourced **

[Arjen Markus]:  Sometimes it is useful to distinguish between the Tcl/Tk shell
sourcing the file (as in tclsh aa.tcl) and the script being evaluated to do
this (via the command "source aa.tcl").  This can be used to:

   * Provide a library of useful procedures and
   * Provide a self-contained "main" proc in the same file

One example of this, a library of procs that together compare two files. If
called directly:

======none
tclsh numcomp.tcl a.inp b.inp
======

the main proc gets executed and the comparison is started. If sourced inside
another script, however, this is not done and the sourcing script has full
control. (In this case: it is a larger script controlling a number of programs
whose results must be compared against reference files).

The trick is the code below:

======
if { [info script] eq $::argv0 } {
    puts {In main}
} else {
    puts {Sourcing [info script]}
}
======

[pyk] 2019-08-07:  See also [main script].


** `[return]` in a sourced file **

[Arjen Markus]:  Another trick I recently learned: `[return]` works in
'''source''' as in any other script - it allows you to return from the
'''source''' command, so that the rest of the file is not handled.  Quite
useful for recovering from errors or making the processing depend on external
influences.



** Examples **

*** loading data ***

You can also source (almost) pure data files like this:

======
array set data [source datafile]
======

where datafile contains:

======none
return {
1 {some data}
2 {other data}
. ...
foo {note that the element name does not have to be numeric}
}
======

results in:

======none
% parray data
data(.)   = ...
data(1)   = some data
data(2)   = other data
data(foo) = note that the element name does not have to be numeric
======

*** Multiple files ***

Just `[glob]` the files you want, and `[source]` them all.

======
foreach x [glob -dir $myDirectory *.tcl] {
    source $x
}
======



*** Exclude myself when sourcing everything in my directory ***

If you want to have it load all scripts in its own directory, instead of that
"source $x" do an "if $x not itself.."



** Encoding and TCL prior to 8.5.0 **

Notice that sourcing is always done in the system encoding. For sources in
[Unicode] or other goodies you'd have to [open] a file, [fconfigure] -encoding,
[read], [eval], [close] - see [Unicode file reader] or [source with encoding].
`source $file` does something like:

======
set fp [open $file]
set data [read $fp]
close $fp
eval $data
======

[HaO] 2015-07-07: Since TCL 8.5.0, the optional parameter '-encoding' may be used to specify the encoding.

I hope, TCL 9.0 will use utf-8 encoding for sources instead of the system encoding...

** utf-8 BOM **


[HaO] 2015-07-07:
If a file encoded in utf-8 with a BOM at the beginning is sourced, the following error is shown for me (system encoding: cp1252 (Windows western Europe), TCL 8.6.4):

======tcl
% source bom.tcl
invalid command name "puts"
======

Nevertheless, the BOM is accepted if utf-8 encoding is specified:

======tcl
% source -encoding utf-8 bom.tcl
hello
======

File contents of bom.tcl (you don't see the leading BOM in the editor):
======tcl
puts "hello"
======

The binary contents is:
======tcl
% set h [open bom.tcl rb]
file2ee9490
% set d [read $h]
puts "hello"


% close $h
% binary scan $d H* dh
1
% regexp -all -inline .. $dh
ef bb bf 70 75 74 73 20 22 68 65 6c 6c 6f 22 0d 0a
======


** pkgIndex.tcl **

[CMcC] 2006-04-12:

Following discussions on tcler's chat in which [dgp] suggested (on, I presume,
aesthetic grounds) that [pkgIndex.tcl] ought not to contain more than one
[package] definition:

One implementation problem with advice is that a package may contain many
source files, but there is no way to discover the directory within which those
source files reside.

One possible solution would be to have the [pkgIndex.tcl] file entry for the
multi-source package source all of the components of the package.

The most elegant way to achieve this goal would be to allow [source] to take
multiple file arguments as follows:  '''source''' ''?-dir directory?''
''fileName ?fileName?'', and this is what I propose for general consideration.



** History **

Special treatment of `^Z` ([ASCII] 26) was introduced in [Changes in Tcl/Tk
8.4%|%Tcl version 8.4%|%].


<<categories>> Tcl syntax help | Arts and Crafts of Tcl-Tk Programming | Command | File