chan(3tcl)

✨ NAME ✨

chan β€” read, write and manipulate channels πŸŽ€


🌸 SYNOPSIS 🌸

chan option ?arg arg ...?

πŸ’– DESCRIPTION πŸ’–

okay so bestie, this is chan(3tcl) and she is the main character of channel handling πŸ’…

This command is your whole toolkit for reading from, writing to, and generally messing with open channels β€” like the ones you get from the open and socket commands, or the default named channels stdin, stdout and stderr, which line up with your process's standard input, output and error streams respectively. Iconic.

option tells chan what you actually want to do with the channel, and ngl, any unique abbreviation for option is totally acceptable. The valid options are, in order, no skips, here we go πŸ¦‹:

  • chan blocked channel β€” this one checks whether the last input operation on the channel called channel flopped because it would've otherwise made the process block. Returns 1 if that was the deal, 0 otherwise. Heads up though: this only ever returns 1 when the channel has been configured to be non-blocking, because all Tcl channels have blocking turned on by default. periodt.
  • chan close channel ?direction? β€” close and destroy the channel called channel. Also, not me forgetting: this deletes all existing file-events registered on the channel πŸ’€.

So if the direction argument is there (and it must be read or write, or any unique abbreviation of them), the channel only gets half-closed, so it can go from read-write to write-only or read-only respectively. If a read-only channel is closed for reading, it's the same as if the channel is fully closed, and respectively similar for write-only channels. Without the direction argument, the channel is closed for both reading and writing β€” but only if those directions are currently open. And it is an error to close a read-only channel for writing, or a write-only channel for reading. πŸ™ˆ

As part of closing the channel, all buffered output is flushed to the channel's output device (only if the channel is ceasing to be writable), any buffered input is discarded (only if the channel is ceasing to be readable), the underlying operating system resource is closed and channel becomes unavailable for future use (both only if the channel is being completely closed). Slay, but like, carefully.

If the channel is blocking and the channel is ceasing to be writable, the command does not return until all output is flushed. If the channel is non-blocking and there is unflushed output, the channel remains open and the command returns immediately; output will be flushed in the background and the channel will be closed when all the flushing is complete. ✨

If channel is a blocking channel for a command pipeline then chan close waits for the child processes to complete.

If the channel is shared between interpreters, then chan close makes channel unavailable in the invoking interpreter but has no other effect until all of the sharing interpreters have closed the channel. When the last interpreter in which the channel is registered invokes chan close (or close), the cleanup actions described above occur. With half-closing, the half-close of the channel only applies to the current interpreter's view of the channel until all channels have closed it in that direction (or completely). See the interp command for a description of channel sharing. 🫢

Channels are automatically fully closed when an interpreter is destroyed and when the process exits. Channels are switched to blocking mode, to ensure that all output is correctly flushed before the process exits.

The command returns an empty string, and may generate an error if an error occurs while flushing output. If a command in a command pipeline created with open returns an error, chan close generates an error (similar to the exec command.)

⚠️ note that half-closes of sockets and command pipelines can have important side effects because they result in a shutdown() or close() of the underlying system resource, which can change how other processes or systems respond to the Tcl program. no cap, be careful.

Channels are automatically closed when an interpreter is destroyed and when the process exits. Nonblocking channels are not switched to blocking mode when exiting; this guarantees a timely exit even when the peer or a communication channel is stalled. To ensure proper flushing of stalled nonblocking channels on exit, one must either (a) actively switch them back to blocking or (b) use the environment variable TCL_FLUSH_NONBLOCKING_ON_EXIT. πŸ’…

  • chan configure channel ?optionName? ?value? ?optionName value?... β€” query or set the configuration options of the channel named channel. the way this one is so versatile, I'm obsessed 😭

If no optionName or value arguments are supplied, the command returns a list containing alternating option names and values for the channel. If optionName is supplied but no value, then the command returns the current value of the given option. If one or more pairs of optionName and value are supplied, the command sets each of the named options to the corresponding value; in this case the return value is an empty string.

The options described below are supported for all channels. In addition, each channel type may add options that only it supports. See the manual entry for the command that creates each type of channel for the options supported by that specific type of channel. For example, see the manual entry for the socket command for additional options for sockets, and the open command for additional options for serial devices. 🧚

  • -blocking boolean β€” this option determines whether I/O operations on the channel can cause the process to block indefinitely. The value of the option must be a proper boolean value. Channels are normally in blocking mode; if a channel is placed into non-blocking mode it will affect the operation of the chan gets, chan read, chan puts, chan flush, and chan close commands; see the documentation for those commands for details. For non-blocking mode to work correctly, the application must be using the Tcl event loop (e.g. by calling Tcl_DoOneEvent or invoking the vwait command). πŸ‘‘
  • -buffering newValue β€” if newValue is full then the I/O system will buffer output until its internal buffer is full or until the chan flush command is invoked. If newValue is line, then the I/O system will automatically flush output for the channel whenever a newline character is output. If newValue is none, the I/O system will flush automatically after every output operation. The default is for -buffering to be set to full except for channels that connect to terminal-like devices; for these channels the initial setting is line. Additionally, stdin and stdout are initially set to line, and stderr is set to none. πŸ’–
  • -buffersize newSize β€” newSize must be an integer; its value is used to set the size of buffers, in bytes, subsequently allocated for this channel to store input or output. newSize must be a number of no more than one million, allowing buffers of up to one million bytes in size. πŸ“
  • -encoding name β€” this option is used to specify the encoding of the channel as one of the named encodings returned by encoding names, so that the data can be converted to and from Unicode for use in Tcl. For instance, in order for Tcl to read characters from a Japanese file in shiftjis and properly process and display the contents, the encoding would be set to shiftjis. Thereafter, when reading from the channel, the bytes in the Japanese file would be converted to Unicode as they are read. Writing is also supported β€” as Tcl strings are written to the channel they will automatically be converted to the specified encoding on output. 🌈

If a file contains pure binary data (for instance, a JPEG image), the encoding for the channel should be configured to be iso8859-1. Tcl will then assign no interpretation to the data in the file and simply read or write raw bytes. The Tcl binary command can be used to manipulate this byte-oriented data. It is usually better to set the -translation option to binary when you want to transfer binary data, as this turns off the other automatic interpretations of the bytes in the stream as well. πŸ‘€

The default encoding for newly opened channels is the same platform- and locale-dependent system encoding used for interfacing with the operating system, as returned by encoding system.

  • -eofchar char β€” this option supports DOS file systems that use Control-z (\x1A) as an end of file marker. If char is not an empty string, then this character signals end-of-file when it is encountered during input. Otherwise (the default) there is no special end of file character marker. The acceptable range for -eofchar values is \x01 - \x7f; attempting to set -eofchar to a value outside of this range will generate an error. 🚨
  • -profile profile β€” specifies the encoding profile to be used on the channel. The encoding transforms in use for the channel's input and output will then be subject to the rules of that profile. Any failures will result in a channel error. See PROFILES in the encoding(3tcl) documentation for details about encoding profiles.
  • -translation translation
  • -translation {inTranslation outTranslation} β€” okay bestie, buckle up, because this is the option that makes line endings across platforms a non-issue. The tea is: in Tcl scripts, the end of a line is always just one single newline character (\n). Periodt. But in actual files and devices? The end of a line can be represented completely differently depending on the platform β€” or even for different devices on the same platform. πŸ’€ For example, under UNIX newlines are used in files, whereas carriage-return-linefeed sequences are normally used in network connections.

On input (i.e., with chan gets and chan read) the Tcl I/O system automatically translates the external end-of-line representation into newline characters. Upon output (i.e., with chan puts), the I/O system translates newlines to the external end-of-line representation. The default translation mode, auto, handles all the common cases automatically, but the -translation option provides explicit control over the end of line translations. explicit control, we love her πŸ’…

The value associated with -translation is a single item for read-only and write-only channels. The value is a two-element list for read-write channels; the read translation mode is the first element of the list, and the write translation mode is the second element. As a convenience, when setting the translation mode for a read-write channel you can specify a single value that will apply to both reading and writing. When querying the translation mode of a read-write channel, a two-element list will always be returned. no exceptions, she said what she said

The following values are currently supported:

  • auto β€” as the input translation mode, auto treats any of newline (lf), carriage return (cr), or carriage return followed by a newline (crlf) as the end of line representation. The end of line representation can even change from line-to-line, and all cases are translated to a newline. so chill, so flexible As the output translation mode, auto chooses a platform specific representation; for sockets on all platforms Tcl chooses crlf, for all Unix flavors it chooses lf, and for the various flavors of Windows it chooses crlf. The default setting for -translation is auto for both input and output. ✨
  • binary β€” like lf, no end-of-line translation is performed, but in addition, it sets -eofchar to the empty string to disable it, and sets -encoding to iso8859-1. With this one setting, a channel is fully configured for binary input and output: each byte read from the channel becomes the Unicode character having the same value as that byte, and each character written to the channel becomes a single byte in the output. This makes it possible to work seamlessly with binary data as long as each character in the data remains in the range of 0 to 255 so that there is no distinction between binary data and text. For example, a JPEG image can be read from such a channel, manipulated, and then written back to such a channel. πŸ–ΌοΈ one setting, whole vibe configured
  • cr β€” the end of a line in the underlying file or device is represented by a single carriage return character. As the input translation mode, cr mode converts carriage returns to newline characters. As the output translation mode, cr mode translates newline characters to carriage returns.
  • crlf β€” the end of a line in the underlying file or device is represented by a carriage return character followed by a linefeed character. As the input translation mode, crlf mode converts carriage-return-linefeed sequences to newline characters. As the output translation mode, crlf mode translates newline characters to carriage-return-linefeed sequences. This mode is typically used on Windows platforms and for network connections. πŸͺŸ
  • lf β€” the end of a line in the underlying file or device is represented by a single newline (linefeed) character. In this mode no translations occur during either input or output. This mode is typically used on UNIX platforms. 🐧

✨ chan copy β€” the glow-up copier ✨

chan copy inputChan outputChan ?-size size? ?-command callback?

Reads characters from inputChan and writes them to outputChan until all characters are copied, blocking until the copy is complete and returning the number of characters copied. it counts them for you, she's thoughtful like that It leverages internal buffers to avoid extra copies and to avoid buffering too much data in main memory when copying large files to slow destinations like network sockets. efficient queen behavior πŸ‘‘

  • -size β€” limits the number of characters copied.
  • -command β€” if given, chan copy returns immediately, works in the background, and calls callback when the copy completes, providing as an additional argument the number of characters written to outputChan. If an error occurs during the background copy, another argument provides message for the error. inputChan and outputChan are automatically configured for non-blocking mode if needed. Background copying only works correctly if events are being processed, e.g. via vwait or Tk. πŸŒ€
During a background copy no other read operation may be performed on inputChan, and no write operation may be performed on outputChan. However, write operations may be performed on inputChan and read operations may be performed on outputChan, as exhibited by the bidirectional copy example below. bidirectional?? she's doing BOTH?? πŸ”₯
If either inputChan or outputChan is closed while the copy is in progress, copying ceases and no callback is made. If inputChan is closed, all data already queued is written to outputChan.
There should be no event handler established for inputChan because it may become readable during a background copy. An attempt to read or write from within an event handler results in the error, "channel busy". Any wrong-sided I/O attempted (by a chan event handler or otherwise) results in a "channel busy" error. πŸ’€ do not do this, bestie, she WILL yell at you

✨ chan create β€” build-your-own-channel ✨

chan create mode cmdPrefix

This subcommand creates a new script level channel using the command prefix cmdPrefix as its handler. Any such channel is called a reflected channel. she's a reflection, she's giving mirror πŸͺž The specified command prefix, cmdPrefix, must be a non-empty list, and should provide the API described in the refchan manual page. The handle of the new channel is returned as the result of the chan create command, and the channel is open. Use either close or chan close to remove the channel.

The argument mode specifies if the new channel is opened for reading, writing, or both. It has to be a list containing any of the strings "read" or "write". The list must have at least one element, as a channel you can neither write to nor read from makes no sense. literally what would it even DO 😭 The handler command for the new channel must support the chosen mode, or an error is thrown.

The command prefix is executed in the global namespace, at the top of call stack, following the appending of arguments as described in the refchan manual page. Command resolution happens at the time of the call. Renaming the command, or destroying it, means that the next call of a handler method may fail, causing the channel command invoking the handler to fail as well. Depending on the subcommand being invoked, the error message may not be able to explain the reason for that failure. rude, but that's the rule

Every channel created with this subcommand knows which interpreter it was created in, and only ever executes its handler command in that interpreter, even if the channel was shared with and/or was moved into a different interpreter. Each reflected channel also knows the thread it was created in, and executes its handler command only in that thread, even if the channel was moved into a different thread. To this end all invocations of the handler are forwarded to the original thread by posting special events to it. This means that the original thread (i.e. the thread that executed the chan create command) must have an active event loop, i.e. it must be able to process such events. Otherwise the thread sending them will block indefinitely. Deadlock may occur. πŸ’€

Note that this permits the creation of a channel whose two endpoints live in two different threads, providing a stream-oriented bridge between these threads. In other words, we can provide a way for regular stream communication between threads instead of having to send commands. threads but make it a friendship bracelet 🫢

When a thread or interpreter is deleted, all channels created with this subcommand and using this thread/interpreter as their computing base are deleted as well, in all interpreters they have been shared with or moved into, and in whatever thread they have been transferred to. While this pulls the rug out under the other thread(s) and/or interpreter(s), this cannot be avoided. it's not personal, it's structural Trying to use such a channel will cause the generation of a regular error about unknown channel handles.

This subcommand is safe and made accessible to safe interpreters. While it arranges for the execution of arbitrary Tcl code, the system also makes sure that the code is always executed within the safe interpreter. πŸ”’ safety first, always


✨ chan eof β€” are we done here? ✨

chan eof channel

Test whether the last input operation on the channel called channel failed because the end of the data stream was reached, returning 1 if end-of-file was reached, and 0 otherwise. one or zero, she keeps it simple βœ…


✨ chan event β€” set up your lil listener ✨

chan event channel event ?script?

Arrange for the Tcl script script to be installed as a file event handler to be called whenever the channel called channel enters the state described by event (which must be either readable or writable); only one such handler may be installed per event per channel at a time. one at a time, she does not share πŸ‘‘

If script is the empty string, the current handler is deleted (this also happens if the channel is closed or the interpreter deleted). If script is omitted, the currently installed script is returned (or an empty string if no such handler is installed). The callback is only performed if the event loop is being serviced (e.g. via vwait or update). 🌸

okay so a file event handler is literally a binding between a channel and a script β€” like, the script gets evaluated whenever that channel becomes readable or writable. and why do we care? bc file event handlers are most commonly used so data can come in from another process on an event-driven basis, so the receiver can keep vibing with the user or with other channels while it waits for the data to show up. ✨

and here's the thing bestie β€” if an app calls chan gets or chan read on a blocking channel when there's no input data available, the process will just block. and until that input data arrives it cannot service other events, so to the user it just looks like it "freeze up" 😭. with chan event tho, the process can tell when data is present and only invoke chan gets or chan read when they will not block. slay.

so when is a channel "readable" exactly? πŸ’…

  • it's readable if there's unread data available on the underlying device.
  • it's also readable if there's unread data in an input buffer β€” except in the special case where the most recent attempt to read from the channel was a chan gets call that could not find a complete line in the input buffer. (this feature is what lets a file be read a line at a time in non-blocking mode using events πŸŽ€)
  • and it's also readable if an end of file or error condition is present on the underlying file or device.
⚠️ it is important for script to check for these conditions and handle them appropriately. for example, if there's no special check for end of file, an infinite loop may occur where script reads no data, returns, and is immediately invoked again. not the vibe. πŸ’€

and "writable"? a channel is writable if at least one byte of data can be written to the underlying file or device without blocking, or if an error condition is present on the underlying file or device. also note: client sockets opened in asynchronous mode become writable when they become connected or if the connection fails. πŸ¦‹

event-driven I/O works best for channels that have been placed into non-blocking mode with the chan configure command. in blocking mode, a chan puts command may block if you give it more data than the underlying file or device can accept, and a chan gets or chan read command will block if you attempt to read more data than is ready β€” and no events will be processed while the commands block. but in non-blocking mode, chan puts, chan read, and chan gets never block. periodt. πŸ‘‘

the script for a file event is executed at global level (outside the context of any Tcl procedure) in the interpreter in which the chan event command was invoked. and if an error occurs while executing the script, then the command registered with interp bgerror is used to report the error. also also β€” the file event handler is deleted if it ever returns an error. that's on purpose, to prevent infinite loops due to buggy handlers. πŸ™ˆ


chan flush channel

ensures that all pending output for the channel called channel is written. that's it, that's the whole job. πŸ’–

if the channel is in blocking mode the command does not return until all the buffered output has been flushed to the channel. if the channel is in non-blocking mode, the command may return before all buffered output has been flushed β€” the remainder will be flushed in the background as fast as the underlying file or device is able to absorb it. so like, it's giving "we'll get to it when we get to it" ✨


chan gets channel ?varName?

reads a line from the channel, consisting of all characters up to the next end-of-line sequence or until end of file is seen. the line feed character corresponding to end-of-line sequence is not included as part of the line. πŸŽ€

if the varName argument is specified, the line is stored in the variable of that name and the command returns the length of the line. if varName is not specified, the command returns the line itself as the result of the command.

if a complete line is not available and the channel is not at EOF, the command will block in the case of a blocking channel. for non-blocking channels, the command will return the empty string as the result in the case of varName not specified, and -1 if it is.

if a blocking channel is already at EOF, the command returns an empty string if varName is not specified. note that an empty string result can also be returned when a blank line is read (no characters before the next end of line sequence). the two cases can be distinguished by calling the chan eof command to check for end of file. if varName is specified, the command returns -1 on end of file. there's no ambiguity in this case because blank lines result in 0 being returned. 🫢

if a non-blocking channel is already at EOF, the command returns an empty line if varName is not specified. this can be distinguished from an empty line being returned by either a blank line being read or a full line not being available, through the use of the chan eof and chan blocked commands:

  • if chan eof returns true β†’ the channel is at EOF.
  • if chan blocked returns true β†’ a full line was not available.
  • if both commands return false β†’ an empty line was read.

if varName was specified for a non-bocking channel at EOF, the command returns -1. this can be distinguished from a full line not being available either by chan eof or chan blocked as above. note that when varName is specified, there's no need to distinguish between eof and blank lines, since the latter will result in the command returning 0. 😌

⚠️ if the encoding profile strict is in effect for the channel, the command will raise an exception with the POSIX error code EILSEQ if any encoding errors are encountered in the channel input data. the file pointer remains unchanged, and it is possible to introspect β€” and in some cases recover β€” by changing the encoding in use. see ENCODING ERROR EXAMPLES later. πŸ‘€

chan isbinary channel

test whether the channel called channel is a binary channel, returning 1 if it is and, and 0 otherwise. and what even is a binary channel, you ask? a binary channel is a channel with iso8859-1 encoding, -eofchar set to {} and -translation set to lf. no cap. 🌈


chan names ?pattern?

produces a list of all channel names. if pattern is specified, only those channel names that match it (according to the rules of string match) will be returned. so like, it's a vibe check but for channels. πŸ’…


chan pending mode channel

depending on whether mode is input or output, returns the number of bytes of input or output (respectively) currently buffered internally for channel. returns -1 if the channel was not opened for the mode in question.

and why do we care? bc this is especially useful in a readable event callback to impose application-specific limits on input line lengths β€” to avoid a potential denial-of-service attack where a hostile user crafts an extremely long line that exceeds the available memory to buffer it. 😀 protect ur memory bestie.


chan pipe

creates a standalone pipe whose read- and write-side channels are returned as a 2-element list β€” the first element being the read side and the second the write side. 🎁

can be useful e.g. to redirect separately stderr and stdout from a subprocess. to do this, spawn with 2>@ or >@ redirection operators onto the write side of a pipe, and then immediately close it in the parent. this is necessary to get an EOF on the read side once the child has exited or otherwise closed its output.

⚠️ note that the pipe buffering semantics can vary at the operating system level substantially. it is not safe to assume that a write performed on the output side of the pipe will appear instantly to the input side. this is a fundamental difference and Tcl cannot conceal it. the overall stream semantics are compatible, so blocking reads and writes will not see most of the differences, but the details of what exactly gets written when are not. this is most likely to show up when using pipelines for testing; care should be taken to ensure that deadlocks do not occur and that potential short reads are allowed for. 😭 ty for coming to my ted talk.

chan pop channel

removes the topmost transformation from the channel channel, if there is any. if there are no transformations added to channel, this is equivalent to chan close of that channel. 🫢

the result is normally the empty string, but can be an error in some situations (i.e. where the underlying system stream is closed and that results in an error).


chan postevent channel eventSpec

this subcommand is used by command handlers specified with chan create. it notifies the channel represented by the handle channel that the event(s) listed in the eventSpec have occurred. ✨

the argument has to be a list containing any of the strings read and write. the list must contain at least one element, as it does not make sense to invoke the command if there are no events to post. πŸ’…

⚠️ note that this subcommand can only be used with channel handles that were created/opened by chan create. all other channels will cause this subcommand to report an error.

as only the Tcl level of a channel β€” i.e. its command handler β€” should post events to it, we also restrict the usage of this command to the interpreter that created the channel. in other words, posting events to a reflected channel from an interpreter that does not contain it's implementation is not allowed. attempting to post an event from any other interpreter will cause this subcommand to report an error. 🚫

another restriction: it is not possible to post events that the I/O core has not registered an interest in. trying to do so will cause the method to throw an error. see the command handler method watch described in refchan, the document specifying the API of command handlers for reflected channels. πŸ‘‘

This command is safe and made accessible to safe interpreters. It can trigger the execution of chan event handlers, whether in the current interpreter or in other interpreters or other threads β€” even when the event gets posted from a safe interpreter and listened for by a trusted interpreter. And the rule? Chan event handlers are ALWAYS executed in the interpreter that set them up. No exceptions bestie πŸ’…


chan push channel cmdPrefix πŸŽ€

Adds a brand new transformation on top of the channel channel. The cmdPrefix argument describes a list of one or more words that represent a handler which will be used to implement the transformation. That command prefix must provide the API described in the transchan manual page. The result of this subcommand is a handle to the transformation.

⚠️ Not me reminding you: it is important to make sure that the transformation is capable of supporting the channel mode that it is used with β€” otherwise this can make the channel neither readable nor writable. πŸ’€

chan puts ?-nonewline? ?channel? string ✨

Writes string to the channel named channel, followed by a newline character. A trailing newline character is written unless the optional flag -nonewline is given. If channel is omitted, the string is written to the standard output channel, stdout. 🫢

Newline characters in the output are translated by chan puts to platform-specific end-of-line sequences according to the currently configured value of the -translation option for the channel β€” for example, on PCs newlines are normally replaced with carriage-return-linefeed sequences; see chan configure above for details. 🌈

Tcl buffers output internally, so characters written with chan puts may not appear immediately on the output file or device; Tcl will normally delay output until the buffer is full or the channel is closed. You can force output to appear immediately with the chan flush command. Pro tip energy: flush before you panic. ✨

When the output buffer fills up, the chan puts command will normally block until all the buffered data has been accepted for output by the operating system. If channel is in non-blocking mode then the chan puts command will not block even if the operating system cannot accept the data. Instead, Tcl continues to buffer the data and writes it in the background as fast as the underlying file or device can accept it. The application must use the Tcl event loop for non-blocking output to work; otherwise Tcl never finds out that the file or device is ready for more output data. πŸ‘‘

It is possible for an arbitrarily large amount of data to be buffered for a channel in non-blocking mode, which could consume a large amount of memory. To avoid wasting memory, non-blocking I/O should normally be used in an event-driven fashion with the chan event command β€” do not invoke chan puts unless you have recently been notified via a file event that the channel is ready for more output data. 🧚

πŸ’€ The command will raise an error exception with POSIX error code EILSEQ if the encoding profile strict is in effect for the channel and the output data cannot be encoded in the encoding configured for the channel. Data may be partially written to the channel in this case.

chan read 🌸

chan read channel ?numChars?

chan read ?-nonewline? channel

In the first form, the result will be the next numChars characters read from the channel named channel; if numChars is omitted, all characters up to the point when the channel would signal a failure β€” whether an end-of-file, blocked or other error condition β€” are read. In the second form (i.e. when numChars has been omitted) the flag -nonewline may be given to indicate that any trailing newline in the string that has been read should be trimmed. πŸ’…

If channel is in non-blocking mode, chan read may not read as many characters as requested: once all available input has been read, the command will return the data that is available rather than blocking for more input. If the channel is configured to use a multi-byte encoding, then there may actually be some bytes remaining in the internal buffers that do not form a complete character. These bytes will not be returned until a complete character is available or end-of-file is reached. The -nonewline switch is ignored if the command returns before reaching the end of the file. 😭

Chan read translates end-of-line sequences in the input into newline characters according to the -translation option for the channel β€” see chan configure above for a discussion on the ways in which chan configure will alter input. 🌈

When reading from a serial port, most applications should configure the serial port channel to be non-blocking, like this:

chan configure channel -blocking 0

Then chan read behaves much like described above. Note that most serial ports are comparatively slow; it is entirely possible to get a readable event for each character read from them. Care must be taken when using chan read on blocking serial ports: πŸ™ˆ

  • chan read channel numChars β€” In this form chan read blocks until numChars have been received from the serial port.
  • chan read channel β€” In this form chan read blocks until the reception of the end-of-file character, see chan configure -eofchar. If there no end-of-file character has been configured for the channel, then chan read will block forever. πŸ’€

If the encoding profile strict is in effect for the channel, the command will raise an exception with the POSIX error code EILSEQ if any encoding errors are encountered in the channel input data. If the channel is in blocking mode, the error is thrown after advancing the file pointer to the beginning of the invalid data. The successfully decoded leading portion of the data prior to the error location is returned as the value of the -data key of the error option dictionary. If the channel is in non-blocking mode, the successfully decoded portion of data is returned by the command without an error exception being raised. A subsequent read will start at the invalid data and immediately raise a EILSEQ POSIX error exception. Unlike the blocking channel case, the -data key is not present in the error option dictionary. In the case of exception thrown due to encoding errors, it is possible to introspect, and in some cases recover, by changing the encoding in use. See ENCODING ERROR EXAMPLES later. ✨


chan seek channel offset ?origin? πŸ¦‹

Sets the current access position within the underlying data stream for the channel named channel to be offset bytes relative to origin. Offset must be an integer (which may be negative) and origin must be one of the following:

  • start β€” The new access position will be offset bytes from the start of the underlying file or device.
  • current β€” The new access position will be offset bytes from the current access position; a negative offset moves the access position backwards in the underlying file or device.
  • end β€” The new access position will be offset bytes from the end of the file or device. A negative offset places the access position before the end of file, and a positive offset places the access position after the end of file.

The origin argument defaults to start. πŸ’–

Chan seek flushes all buffered output for the channel before the command returns, even if the channel is in non-blocking mode. It also discards any buffered and unread input. This command returns an empty string. An error occurs if this command is applied to channels whose underlying file or device does not support seeking. 😭

🌸 Note that offset values are byte offsets, not character offsets. Both chan seek and chan tell operate in terms of bytes, not characters, unlike chan read. iykyk

chan tell channel πŸ‘‘

Returns a number giving the current access position within the underlying data stream for the channel named channel. This value returned is a byte offset that can be passed to chan seek in order to set the channel to a particular position. Note that this value is in terms of bytes, not characters like chan read. The value returned is -1 for channels that do not support seeking. πŸ’…


chan truncate channel ?length? ⭐

Sets the byte length of the underlying data stream for the channel named channel to be length (or to the current byte offset within the underlying data stream if length is omitted). The channel is flushed before truncation. ✨


πŸ’– EXAMPLES πŸ’–

🌸 SIMPLE CHANNEL OPERATION EXAMPLES 🌸

Instruct Tcl to always send output to stdout immediately, whether or not it is to a terminal:

chan configure stdout -buffering none

In the following example a file is opened using the encoding CP1252, which is common on Windows, searches for a string, rewrites that part, and truncates the file two lines later. πŸŽ€

set f [open somefile.txt r+]
chan configure $f -encoding cp1252
set offset 0

# Search for string "FOOBAR" in the file
while {[chan gets $f line] >= 0} {
    set idx [string first FOOBAR $line]
    if {$idx >= 0} {
        # Found it; rewrite line

        chan seek $f [expr {$offset + $idx}]
        chan puts -nonewline $f BARFOO

        # Skip to end of following line, and truncate
        chan gets $f
        chan gets $f
        chan truncate $f

        # Stop searching the file now
        break
    }

    # Save offset of start of next line for later
    set offset [chan tell $f]
}
chan close $f

This example illustrates flushing of a channel. The user is prompted for some information. Because the standard input channel is line buffered, it must be flushed for the user to see the prompt. 🫢

chan puts -nonewline "Please type your name: "
chan flush stdout
chan gets stdin name
chan puts "Hello there, $name!"

okay bestie, this first example is literally just reading a file line by line and printing each line with its line number slapped on the front. iconic. πŸ’…

set chan [open "some.file.txt"]
set lineNumber 0
while {[chan gets $chan line] >= 0} {
    chan puts "[incr lineNumber]: $line"
}
chan close $chan

next up, event-driven reads! πŸ’… GetData gets called with the channel as an argument whenever $chan becomes readable. the read call will read whatever binary data is available right now without blocking. here the channel has the fileevent removed when an end of file happens so it doesn't get called over and over (see above). or you could just close the channel on that condition, your call.

proc GetData {chan} {
    set data [chan read $chan]
    chan puts "[string length $data] $data"
    if {[chan eof $chan]} {
        chan event $chan readable {}
    }
}

chan configure $chan -blocking 0 -translation binary
chan event $chan readable [list GetData $chan]

this next one is similar but uses chan gets to read line-oriented data.

proc GetData {chan} {
    if {[chan gets $chan line] >= 0} {
        chan puts $line
    }
    if {[chan eof $chan]} {
        chan close $chan
    }
}

chan configure $chan -blocking 0 -buffering line -translation crlf
chan event $chan readable [list GetData $chan]

a network server that echoes its input line-by-line without blocking other connections at the same time? yes please. πŸ‘‘

# This is a very simple logger...
proc log {message} {
    chan puts stdout $message
}

# This is called whenever a new client connects to the server
proc connect {chan host port} {
    set clientName [format <%s:%d> $host $port]
    log "connection from $clientName"
    chan configure $chan -blocking 0 -buffering line
    chan event $chan readable [list echoLine $chan $clientName]
}

# This is called whenever either at least one byte of input
# data is available, or the channel was closed by the client.
proc echoLine {chan clientName} {
    chan gets $chan line
    if {[chan eof $chan]} {
        log "finishing connection from $clientName"
        chan close $chan
    } elseif {![chan blocked $chan]} {
        # Didn't block waiting for end-of-line
        log "$clientName - $line"
        chan puts $chan $line
    }
}

# Create the server socket and enter the event-loop to wait
# for incoming connections...
socket -server connect 12345
vwait forever

the following example reads a PPM-format image from a file that mixes ASCII and binary content. fancy. 🎨

# Open the file and put it into Unix ASCII mode
set f [open teapot.ppm]
chan configure $f -encoding ascii -translation lf

# Get the header
if {[chan gets $f] ne "P6"} {
    error "not a raw-bits PPM"
}

# Read lines until we have got non-comment lines
# that supply us with three decimal values.
set words {}
while {[llength $words] < 3} {
    chan gets $f line
    if {[string match "#*" $line]} continue
    lappend words {*}[join [scan $line %d%d%d]]
}

# Those words supply the size of the image and its
# overall depth per channel. Assign to variables.
lassign $words xSize ySize depth

# Now switch to binary mode to pull in the data,
# one byte per channel (red,green,blue) per pixel.
chan configure $f -translation binary
set numDataBytes [expr {3 * $xSize * $ySize}]
set data [chan read $f $numDataBytes]

close $f

πŸŒ€ FILE SEEK EXAMPLES πŸŒ€

read a file twice? sure, why not. πŸ”

set f [open file.txt]
set data1 [chan read $f]
chan seek $f 0
set data2 [chan read $f]
chan close $f
# $data1 eq $data2 if the file wasn't updated

read the last 10 bytes from a file? lowkey useful. πŸ”

set f [open file.data]
# This is guaranteed to work with binary data but
# may fail with other encodings...
chan configure $f -translation binary
chan seek $f -10 end
set data [chan read $f 10]
chan close $f

read a line from a file channel only if it starts with foobar? okay, specific but we love it. πŸ’…

# Save the offset in case we need to undo the read...
set offset [tell $chan]
if {[read $chan 6] eq "foobar"} {
    gets $chan line
} else {
    set line {}
    # Undo the read...
    seek $chan $offset
}

😱 ENCODING ERROR EXAMPLES 😱

this example shows how to handle an encoding error when reading from a channel. first, we make a test file with an invalid UTF-8 sequence (A \xC3 B):

% set f [open test_A_195_B.txt wb]; chan puts -nonewline $f A\xC3B; chan close $f

trying to read that file will throw an encoding error, which we then inspect by switching the channel to binary mode. note that when the error is reported, the file position stays the same, so the chan gets during recovery returns the full line. slay. πŸ’β€β™€οΈ

% set f [open test_A_195_B.txt r]
file384b6a8
% chan configure $f -encoding utf-8
% catch {chan gets $f} e d
1
% set d
-code 1 -level 0
-errorstack {INNER {invokeStk1 gets file384b6a8}}
-errorcode {POSIX EILSEQ {invalid or incomplete multibyte or wide character}}
-errorinfo {...} -errorline 1
% chan tell $f
0
% chan configure $f -translation binary
% chan gets $f
AÃB

this next one is similar but shows recovery after a blocking read. the successfully decoded data "A" is returned in the error options dictionary under the key -data. the file position moves forward to the encoding error position 1. so the data at the error position gets recovered by the next chan read command. big brain. 🧠

% set f [open test_A_195_B.txt r]
file35a65a0
% chan configure $f -encoding utf-8 -blocking 1
% catch {chan read $f} e d
1
% set d
-data A -code 1 -level 0
-errorstack {INNER {invokeStk1 read file35a65a0}}
-errorcode {POSIX EILSEQ {invalid or incomplete multibyte or wide character}}
-errorinfo {...} -errorline 1
% chan tell $f
1
% chan configure $f -translation binary
% chan read $f
ΓƒB
% chan close $f

finally, same example but with a non-blocking channel.

% set f [open test_A_195_B.txt r]
file35a65a0
% chan configure $f -encoding utf-8 -blocking 0
% chan read $f
A
% chan tell $f
1
% catch {chan read $f} e d
1
% set d
-code 1 -level 0
-errorstack {INNER {invokeStk1 read file384b228}}
-errorcode {POSIX EILSEQ {invalid or incomplete multibyte or wide character}}
-errorinfo {...} -errorline 1

πŸ”„ CHANNEL COPY EXAMPLES πŸ”„

first up, transferring the contents of one channel exactly to another. but note: if you're copying one file to another, it's better to use file copy because it also copies file metadata like access permissions when possible. just saying. πŸ“

chan configure $in -translation binary
chan configure $out -translation binary
chan copy $in $out

this second one shows how the callback gets the number of bytes transferred. it also uses vwait to drop the app into the event loop. obviously this simplified example could be done without the command callback, but where's the fun in that? πŸ€·β€β™€οΈ

proc Cleanup {in out bytes {error {}}} {
    global total
    set total $bytes
    chan close $in
    chan close $out
    if {$error ne ""} {
        # error occurred during the copy
    }
}

set in [open $file1]
set out [socket $server $port]
chan copy $in $out -command [list Cleanup $in $out]
vwait total

third example copies in chunks and checks for end of file inside the command callback.

proc CopyMore {in out chunk bytes {error {}}} {
    global total done
    incr total $bytes
    if {($error ne "") || [chan eof $in]} {
        set done $total
        chan close $in
        chan close $out
    } else {
        chan copy $in $out -size $chunk \
            -command [list CopyMore $in $out $chunk]
    }
}

set in [open $file1]
set out [socket $server $port]
set chunk 1024
set total 0
chan copy $in $out -size $chunk \
    -command [list CopyMore $in $out $chunk]
vwait done

fourth example kicks off an asynchronous, bidirectional copy between two sockets. those could also be pipes from two bidirectional pipelines (like [open "|hal 9000" r+]). the conversation stays basically secret to the script because all four chan event slots are busy, but any transforms that are chan pushed on the channels can still observe the passing traffic. sneaky. πŸ•΅οΈβ€β™€οΈ

proc Done {dir args} {
    global flows done
    chan puts "$dir is over."
    incr flows -1
    if {$flows <= 0} {
        set done 1
    }
}

set flows 2
chan copy $sok1 $sok2 -command [list Done UP]
chan copy $sok2 $sok1 -command [list Done DOWN]
vwait done

✨ SEE ALSO ✨

you might also wanna peek at these, bestie:

πŸ”‘ KEYWORDS πŸ”‘

  • blocking
  • channel
  • end of file
  • events
  • input
  • non-blocking
  • offset
  • output
  • readable
  • seek
  • stdio
  • tell
  • writable

and that's a wrap on chan(3tcl) from Tcl 8.5! πŸ’– you're literally a channel wizard now. go forth and slay, bestie. 🫢✨

πŸ“¦ where this page comes from