5.22 Strings

GNU troff supports strings primarily for user convenience. Conventionally, if one would define a macro only to interpolate a small amount of text, without invoking requests or calling any other macros, one defines a string instead. Only one string is predefined by the language.

String: \*[.T]

Contains the name of the output device (for example, ‘utf8’ or ‘pdf’).

The ds request creates a string with a specified name and contents and the \* escape sequence dereferences its name, interpolating its contents. If the string named by the \* escape sequence does not exist, it is defined as empty, nothing is interpolated, and a warning in category ‘mac’ is emitted. See Warnings, for information about the enablement and suppression of warnings.

Request: .ds name [contents]
Request: .ds1 name [contents]
Escape sequence: \*n
Escape sequence: \*(nm
Escape sequence: \*[name [arg1 arg2 …]]

Define a string called name with contents contents. If name already exists as an alias, the target of the alias is redefined; see als and rm below. If ds is called with only one argument, name is defined as an empty string. Otherwise, GNU troff stores contents in copy mode.87

The \* escape sequence interpolates a previously defined string variable name (one-character name n, two-character name nm). The bracketed interpolation form accepts arguments that are handled as macro arguments are; recall Calling Macros. In contrast to macro calls, however, if a closing bracket ‘]’ occurs in a string argument, that argument must be enclosed in double quotes. \* is interpreted even in copy mode. When defining strings, argument interpolations must be escaped if they are to reference parameters from the calling context; See Parameters.

.ds cite (\\$1, \\$2)
Gray codes are explored in \*[cite Morgan 1998].
    ⇒ Gray codes are explored in (Morgan, 1998).

Caution: Unlike other requests, the second argument to the ds request consumes the remainder of the input line, including trailing spaces. This means that comments on a line with such a request can introduce unwanted space into a string when they are set off from the material they annotate, as is conventional.

.ds H2O H\v'+.3m'\s'-2'2\v'-.3m'\s0O \" water

Instead, place the comment on another line or put the comment escape sequence immediately adjacent to the last character of the string.

.ds H2O H\v'+.3m'\s'-2'2\v'-.3m'\s0O\" water

Ending string definitions (and appendments) with a comment, even an empty one, prevents unwanted space from creeping into them during source document maintenance.

.ds author Alice Pleasance Liddell\"
.ds empty \" might be appended to later with .as

An initial neutral double quote " in contents is stripped to allow embedding of leading spaces. Any other " is interpreted literally, but it is wise to use the special character escape sequence \[dq] instead if the string might be interpolated as part of a macro argument; see Calling Macros.

.ds salutation "         Yours in a white wine sauce,\"
.ds c-var-defn "  char mydate[]=\[dq]2020-07-29\[dq];\"

Strings are not limited to a single input line of text. \RET works just as it does elsewhere. The resulting string is stored without the newlines. Care is therefore required when interpolating strings while filling is disabled.

.ds foo This string contains \
text on multiple lines \
of input.

It is not possible to embed a newline in a string that will be interpreted as such when the string is interpolated. To achieve that effect, use \* to interpolate a macro instead; see Punning Names.

Because strings are similar to macros, they too can be defined so as to suppress AT&T troff compatibility mode when used; see Writing Macros and Compatibility Mode. The ds1 request defines a string such that compatibility mode is off when the string is later interpolated. To be more precise, a compatibility save input token is inserted at the beginning of the string, and a compatibility restore input token at the end.

.nr xxx 12345
.ds aa The value of xxx is \\n[xxx].
.ds1 bb The value of xxx is \\n[xxx].
.
.cp 1
.
\*(aa
    error→ warning: register '[' not defined
    ⇒ The value of xxx is 0xxx].
\*(bb
    ⇒ The value of xxx is 12345.
Request: .as name [contents]
Request: .as1 name [contents]

The as request is similar to ds but appends contents to the string stored as name instead of redefining it. If name doesn’t exist yet, it is created. If as is called with only one argument, no operation is performed (beyond dereferencing the string).

.as salutation " with shallots, onions and garlic,\"

The as1 request is similar to as, but compatibility mode is switched off when the appended portion of the string is later interpolated. To be more precise, a compatibility save input token is inserted at the beginning of the appended string, and a compatibility restore input token at the end.

Several requests exist to perform rudimentary string operations. Strings can be queried (length) and modified (chop, substring, stringup, stringdown), and their names can be manipulated through renaming, removal, and aliasing (rn, rm, als).

Request: .length reg anything

Compute the number of characters of anything and store the count in the register reg. If reg doesn’t exist, it is created. anything is read in copy mode.

.ds xxx abcd\h'3i'efgh
.length yyy \*[xxx]
\n[yyy]
    ⇒ 14
Request: .chop object

Remove the last character from the macro, string, or diversion named object. This is useful for removing the newline from the end of a diversion that is to be interpolated as a string. This request can be used repeatedly on the same object; see gtroff Internals, for details on nodes inserted additionally by GNU troff.

Request: .substring str start [end]

Replace the string named str with its substring bounded by the indices start and end, inclusively. The first character in the string has index 0. If end is omitted, it is implicitly set to the largest valid value (the string length minus one). Negative indices count backward from the end of the string: the last character has index −1, the character before the last has index −2, and so on.

.ds xxx abcdefgh
.substring xxx 1 -4
\*[xxx]
    ⇒ bcde
.substring xxx 2
\*[xxx]
    ⇒ de
Request: .stringdown str
Request: .stringup str

Alter the string named str by replacing each of its bytes with its lowercase (stringdown) or uppercase (stringup) version (if one exists). Special characters in the string will often transform in the expected way due to the regular naming convention for accented characters. When they do not, use substrings and/or catenation.

.ds resume R\['e]sum\['e]
\*[resume]
.stringdown resume
\*[resume]
.stringup resume
\*[resume]
    ⇒ Résumé résumé RÉSUMÉ

(In practice, we would end the ds request with a comment escape \" to prevent space from creeping into the definition during source document maintenance.)

Request: .rn old new

Rename the request, macro, diversion, or string old to new.

Request: .rm name

Remove the request, macro, diversion, or string name. GNU troff treats subsequent invocations as if the name had never been defined.

Request: .als new old

Create an alias new for the existing request, string, macro, or diversion object named old, causing the names to refer to the same stored object. If old is undefined, a warning in category ‘mac’ is produced, and the request is ignored. See Warnings, for information about the enablement and suppression of warnings.

To understand how the als request works, consider two different storage pools: one for objects (macros, strings, etc.), and another for names. As soon as an object is defined, GNU troff adds it to the object pool, adds its name to the name pool, and creates a link between them. When als creates an alias, it adds a new name to the name pool that gets linked to the same object as the old name.

Now consider this example.

.de foo
..
.
.als bar foo
.
.de bar
.  foo
..
.
.bar
    error→ input stack limit exceeded (probable infinite
    error→ loop)

In the above, bar remains an alias—another name for—the object referred to by foo, which the second de request replaces. Alternatively, imagine that the de request dereferences its argument before replacing it. Either way, the result of calling bar is a recursive loop that finally leads to an error. See Writing Macros.

To remove an alias, call rm on its name. The object itself is not destroyed until it has no more names.

When a request, macro, string, or diversion is aliased, redefinitions and appendments “write through” alias names. To replace an alias with a separately defined object, you must use the rm request on its name first.