source, a built-in
command, evaluates a file or resource as a Tcl script.
Synopsis edit
source filenamesource -encoding encodingName fileName Documentation edit
- official reference
See Also edit
- 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
- 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 edit
source arranges for the
Tcl interpreter to evaluate the contents of the specified file or resource exactly as if
eval were called at that point with an argument identical to the the literal content of the given file, up to the first occurrence of
^Z (
ASCII character 26) in that file, except that any
return occurring in the evaluated script is handled slightly differently. In contrast to
eval, any
return does not cause the caller of
source to return.
uplevel 0 ... is the better analogy.
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
system encoding. Override this behaviour with
-encoding.
Ensure that a file is only sourced once
Joe Mistachkin 2003-08-25: The following proc will source 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
feature request #301: Include source files only once.
I do not see that the variable "sources" is documented on the manual page
tclvars [
1] 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}: 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 has occasionally 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 behave differently from
source in various ways:
^Z won't be respected, lines will be evaluated within the scope of
sourceOpen instead of its caler, and treatment of
return will be different.
Evalate Channel Contents in Chunks
hans 2008-02-25:
source reads the whole file into memory first before eval'ing. I'm saving updates to a large tcl source file whenever any vars 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 to that 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 edit
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:
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]}
}
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 edit
loading data
You can also source (almost) pure data files like this:
array set data [source datafile]
where datafile contains:
return {
1 {some data}
2 {other data}
. ...
foo {note that the element name does not have to be numeric}
}
results in:
% 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 edit
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 edit
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):
% source bom.tcl
invalid command name "puts"
Nevertheless, the BOM is accepted if utf-8 encoding is specified:
% source -encoding utf-8 bom.tcl
hello
File contents of bom.tcl (you don't see the leading BOM in the editor):
puts "hello"
The binary contents is:
% 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 edit
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.
Special treatment of
^Z (
ASCII 26) was introduced in
Tcl version 8.4.