Thanks for the bug report. This stuff about "the future" has been there for years so it's time to remove any predictions. I installed the attached revamp of the tr documentation to try to address the problems you mentioned, plus some others I noticed while in the neighborhood.
From 8d3dce9861c15f06a014c91fa29c15143fd27127 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 14 Feb 2022 12:00:16 -0800
Subject: [PATCH 1/2] tr: improve multibyte etc. doc

Problem reported by Dan Jacobson (Bug#48248).
* doc/coreutils.texi (tr invocation): Improve documentation for
tr's failure to support multibyte characters POSIX-style.
* doc/coreutils.texi (tr invocation), src/tr.c (usage):
Use terminology closer to POSIX's.
---
 doc/coreutils.texi | 205 ++++++++++++++++++++++++---------------------
 src/tr.c           |  30 +++----
 2 files changed, 123 insertions(+), 112 deletions(-)

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 7ae5ab8e3..8d2974bde 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -300,7 +300,7 @@ Operating on characters
 
 @command{tr}: Translate, squeeze, and/or delete characters
 
-* Character sets::               Specifying sets of characters
+* Character arrays::             Specifying arrays of characters
 * Translating::                  Changing one set of characters to another
 * Squeezing and deleting::       Removing characters
 
@@ -6888,7 +6888,7 @@ These commands operate on individual characters.
 Synopsis:
 
 @example
-tr [@var{option}]@dots{} @var{set1} [@var{set2}]
+tr [@var{option}]@dots{} @var{string1} [@var{string2}]
 @end example
 
 @command{tr} copies standard input to standard output, performing
@@ -6905,9 +6905,11 @@ delete characters,
 delete characters, then squeeze repeated characters from the result.
 @end itemize
 
-The @var{set1} and (if given) @var{set2} arguments define ordered
-sets of characters, referred to below as @var{set1} and @var{set2}.  These
-sets are the characters of the input that @command{tr} operates on.
+The @var{string1} and @var{string2} operands define arrays of
+characters @var{array1} and @var{array2}.  By default @var{array1}
+lists input characters that @command{tr} operates on, and @var{array2}
+lists corresponding translations.  In some cases the second operand is
+omitted.
 
 The program accepts the following options.  Also see @ref{Common options}.
 Options must precede operands.
@@ -6920,34 +6922,29 @@ Options must precede operands.
 @opindex -c
 @opindex -C
 @opindex --complement
-This option replaces @var{set1} with its
-complement (all of the characters that are not in @var{set1}).
-Currently @command{tr} fully supports only single-byte characters.
-Eventually it will support multibyte characters; when it does, the
-@option{-C} option will cause it to complement the set of characters,
-whereas @option{-c} will cause it to complement the set of values.
-This distinction will matter only when some values are not characters,
-and this is possible only in locales using multibyte encodings when
-the input contains encoding errors.
+Instead of @var{array1}, use its complement (all characters not
+specified by @var{string1}), in ascending order.  Use this option with
+caution in multibyte locales where its meaning is not always clear
+or portable; see @ref{Character arrays}.
 
 @item -d
 @itemx --delete
 @opindex -d
 @opindex --delete
-Delete characters in @var{set1}, do not translate
+Delete characters in @var{array1}; do not translate.
 
 @item -s
 @itemx --squeeze-repeats
 @opindex -s
 @opindex --squeeze-repeats
 Replace each sequence of a repeated character that is listed in
-the last specified @var{set}, with a single occurrence of that character.
+the last specified @var{array}, with a single occurrence of that character.
 
 @item -t
 @itemx --truncate-set1
 @opindex -t
 @opindex --truncate-set1
-First truncate @var{set1} to length of @var{set2}.
+Truncate @var{array1} to the length of @var{array2}.
 
 @end table
 
@@ -6955,23 +6952,41 @@ First truncate @var{set1} to length of @var{set2}.
 @exitstatus
 
 @menu
-* Character sets::              Specifying sets of characters.
-* Translating::                 Changing one set of characters to another.
+* Character arrays::            Specifying arrays of characters.
+* Translating::                 Changing characters to other characters.
 * Squeezing and deleting::      Removing characters.
 @end menu
 
 
-@node Character sets
-@subsection Specifying sets of characters
-
-@cindex specifying sets of characters
-
-The format of the @var{set1} and @var{set2} arguments resembles
-the format of regular expressions; however, they are not regular
-expressions, only lists of characters.  Most characters simply
-represent themselves in these strings, but the strings can contain
-the shorthands listed below, for convenience.  Some of them can be
-used only in @var{set1} or @var{set2}, as noted below.
+@node Character arrays
+@subsection Specifying arrays of characters
+
+@cindex arrays of characters in @command{tr}
+
+The @var{string1} and @var{string2} operands are not regular
+expressions, even though they may look similar.  Instead, they
+merely represent arrays of characters.  As a GNU extension to POSIX,
+an empty string operand represents an empty array of characters.
+
+The interpretation of @var{string1} and @var{string2} depends on locale.
+GNU @command{tr} fully supports only safe single-byte locales,
+where each possible input byte represents a single character.
+Unfortunately, this means GNU @command{tr} will not handle commands
+like @samp{tr @U{7530} @U{68EE}} the way you might expect,
+since (assuming a UTF-8 encoding) this is equivalent to
+@samp{tr '\347\224\260' '\346\243\256'} and GNU @command{tr} will
+simply transliterate all @samp{\347} bytes to @samp{\346} bytes, etc.
+POSIX does not clearly specify the behavior of @command{tr} in locales
+where characters are represented by byte sequences instead of by
+individual bytes, or where data might contain invalid bytes that are
+encoding errors.  To avoid problems in this area, you can run
+@command{tr} in a safe single-byte locale by using a shell command
+like @samp{LC_ALL=C tr} instead of plain @command{tr}.
+
+Although most characters simply represent themselves in @var{string1}
+and @var{string2}, the strings can contain shorthands listed below,
+for convenience.  Some shorthands can be used only in @var{string1} or
+@var{string2}, as noted below.
 
 @table @asis
 
@@ -6982,38 +6997,42 @@ The following backslash escape sequences are recognized:
 
 @table @samp
 @item \a
-Control-G.
+Bell (BEL, Control-G).
 @item \b
-Control-H.
+Backspace (BS, Control-H).
 @item \f
-Control-L.
+Form feed (FF, Control-L).
 @item \n
-Control-J.
+Newline (LF, Control-J).
 @item \r
-Control-M.
+Carriage return (CR, Control-M).
 @item \t
-Control-I.
+Tab (HT, Control-I).
 @item \v
-Control-K.
+Vertical tab (VT, Control-K).
 @item \@var{ooo}
-The 8-bit character with the value given by @var{ooo}, which is 1 to 3
-octal digits.  Note that @samp{\400} is interpreted as the two-byte
-sequence, @samp{\040} @samp{0}.
+The eight-bit byte with the value given by @var{ooo}, which is the longest
+sequence of one to three octal digits following the backslash.
+For portability, @var{ooo} should represent a value that fits in eight bits.
+As a GNU extension to POSIX, if the value would not fit, then only the
+first two digits of @var{ooo} are used, e.g., @samp{\400}
+is equivalent to @samp{\0400} and represents a two-byte sequence.
 @item \\
 A backslash.
 @end table
 
-While a backslash followed by a character not listed above is
-interpreted as that character, the backslash also effectively
-removes any special significance, so it is useful to escape
-@samp{[}, @samp{]}, @samp{*}, and @samp{-}.
+It is an error if no character follows an unescaped backslash.
+As a GNU extension, a backslash followed by a character not listed
+above is interpreted as that character, removing any special
+significance; this can be used to escape the characters
+@samp{[} and @samp{-} when they would otherwise be special.
 
 @item Ranges
 @cindex ranges
 
-The notation @samp{@var{m}-@var{n}} expands to all of the characters
+The notation @samp{@var{m}-@var{n}} expands to the characters
 from @var{m} through @var{n}, in ascending order.  @var{m} should
-collate before @var{n}; if it doesn't, an error results.  As an example,
+not collate after @var{n}; if it does, an error results.  As an example,
 @samp{0-9} is the same as @samp{0123456789}.
 
 GNU @command{tr} does not support the System V syntax that uses square
@@ -7023,38 +7042,37 @@ to themselves.  However, they should be avoided because they sometimes
 behave unexpectedly.  For example, @samp{tr -d '[0-9]'} deletes brackets
 as well as digits.
 
-Many historically common and even accepted uses of ranges are not
+Many historically common and even accepted uses of ranges are not fully
 portable.  For example, on EBCDIC hosts using the @samp{A-Z}
 range will not do what most would expect because @samp{A} through @samp{Z}
 are not contiguous as they are in ASCII@.
-If you can rely on a POSIX compliant version of @command{tr}, then
-the best way to work around this is to use character classes (see below).
+One way to work around this is to use character classes (see below).
 Otherwise, it is most portable (and most ugly) to enumerate the members
 of the ranges.
 
 @item Repeated characters
 @cindex repeated characters
 
-The notation @samp{[@var{c}*@var{n}]} in @var{set2} expands to @var{n}
+The notation @samp{[@var{c}*@var{n}]} in @var{string2} expands to @var{n}
 copies of character @var{c}.  Thus, @samp{[y*6]} is the same as
 @samp{yyyyyy}.  The notation @samp{[@var{c}*]} in @var{string2} expands
-to as many copies of @var{c} as are needed to make @var{set2} as long as
-@var{set1}.  If @var{n} begins with @samp{0}, it is interpreted in
-octal, otherwise in decimal.
+to as many copies of @var{c} as are needed to make @var{array2} as long as
+@var{array1}.  If @var{n} begins with @samp{0}, it is interpreted in
+octal, otherwise in decimal.  A zero-valued @var{n} is treated as if
+it were absent.
 
 @item Character classes
 @cindex character classes
 
-The notation @samp{[:@var{class}:]} expands to all of the characters in
-the (predefined) class @var{class}.  The characters expand in no
-particular order, except for the @code{upper} and @code{lower} classes,
-which expand in ascending order.  When the @option{--delete} (@option{-d})
+The notation @samp{[:@var{class}:]} expands to all characters in
+the (predefined) class @var{class}.  When the @option{--delete} (@option{-d})
 and @option{--squeeze-repeats} (@option{-s}) options are both given, any
-character class can be used in @var{set2}.  Otherwise, only the
+character class can be used in @var{string2}.  Otherwise, only the
 character classes @code{lower} and @code{upper} are accepted in
-@var{set2}, and then only if the corresponding character class
+@var{string2}, and then only if the corresponding character class
 (@code{upper} and @code{lower}, respectively) is specified in the same
-relative position in @var{set1}.  Doing this specifies case conversion.
+relative position in @var{string1}.  Doing this specifies case conversion.
+Except for case conversion, a class's characters appear in no particular order.
 The class names are given below; an error results when an invalid class
 name is given.
 
@@ -7100,10 +7118,13 @@ Hexadecimal digits.
 @item Equivalence classes
 @cindex equivalence classes
 
-The syntax @samp{[=@var{c}=]} expands to all of the characters that are
-equivalent to @var{c}, in no particular order.  Equivalence classes are
-a relatively recent invention intended to support non-English alphabets.
-But there seems to be no standard way to define them or determine their
+The syntax @samp{[=@var{c}=]} expands to all characters equivalent to
+@var{c}, in no particular order.  These equivalence classes are
+allowed in @var{string2} only when @option{--delete} (@option{-d}) and
+@option{--squeeze-repeats} @option{-s} are both given.
+
+Although equivalence classes are intended to support non-English alphabets,
+there seems to be no standard way to define them or determine their
 contents.  Therefore, they are not fully implemented in GNU @command{tr};
 each character's equivalence class consists only of that character,
 which is of no particular use.
@@ -7116,13 +7137,14 @@ which is of no particular use.
 
 @cindex translating characters
 
-@command{tr} performs translation when @var{set1} and @var{set2} are
+@command{tr} performs translation when @var{string1} and @var{string2} are
 both given and the @option{--delete} (@option{-d}) option is not given.
-@command{tr} translates each character of its input that is in @var{set1}
-to the corresponding character in @var{set2}.  Characters not in
-@var{set1} are passed through unchanged.  When a character appears more
-than once in @var{set1} and the corresponding characters in @var{set2}
-are not all the same, only the final one is used.  For example, these
+@command{tr} translates each character of its input that is in @var{array1}
+to the corresponding character in @var{array2}.  Characters not in
+@var{array1} are passed through unchanged.
+
+As a GNU extension to POSIX, when a character appears more than once
+in @var{array1}, only the final instance is used.  For example, these
 two commands are equivalent:
 
 @example
@@ -7140,17 +7162,17 @@ tr '[:lower:]' '[:upper:]'
 @end example
 
 @noindent
-But note that using ranges like @code{a-z} above is not portable.
+However, ranges like @code{a-z} are not portable outside the C locale.
 
-When @command{tr} is performing translation, @var{set1} and @var{set2}
-typically have the same length.  If @var{set1} is shorter than
-@var{set2}, the extra characters at the end of @var{set2} are ignored.
+When @command{tr} is performing translation, @var{array1} and @var{array2}
+typically have the same length.  If @var{array1} is shorter than
+@var{array2}, the extra characters at the end of @var{array2} are ignored.
 
-On the other hand, making @var{set1} longer than @var{set2} is not
+On the other hand, making @var{array1} longer than @var{array2} is not
 portable; POSIX says that the result is undefined.  In this situation,
-BSD @command{tr} pads @var{set2} to the length of @var{set1} by repeating
-the last character of @var{set2} as many times as necessary.  System V
-@command{tr} truncates @var{set1} to the length of @var{set2}.
+BSD @command{tr} pads @var{array2} to the length of @var{array1} by repeating
+the last character of @var{array2} as many times as necessary.  System V
+@command{tr} truncates @var{array1} to the length of @var{array2}.
 
 By default, GNU @command{tr} handles this case like BSD @command{tr}.
 When the @option{--truncate-set1} (@option{-t}) option is given,
@@ -7166,13 +7188,12 @@ tr -cs A-Za-z0-9 '\012'
 
 @noindent
 because it converts only zero bytes (the first element in the
-complement of @var{set1}), rather than all non-alphanumerics, to
+complement of @var{array1}), rather than all non-alphanumerics, to
 newlines.
 
 @noindent
 By the way, the above idiom is not portable because it uses ranges, and
-it assumes that the octal code for newline is 012.
-Assuming a POSIX compliant @command{tr}, here is a better
+it assumes that the octal code for newline is 012.  Here is a better
 way to write it:
 
 @example
@@ -7188,20 +7209,20 @@ tr -cs '[:alnum:]' '[\n*]'
 @cindex removing characters
 
 When given just the @option{--delete} (@option{-d}) option, @command{tr}
-removes any input characters that are in @var{set1}.
+removes any input characters that are in @var{array1}.
 
 When given just the @option{--squeeze-repeats} (@option{-s}) option
 and not translating, @command{tr} replaces each input sequence of a
-repeated character that is in @var{set1} with a single occurrence of
+repeated character that is in @var{array1} with a single occurrence of
 that character.
 
 When given both @option{--delete} and @option{--squeeze-repeats}, @command{tr}
-first performs any deletions using @var{set1}, then squeezes repeats
-from any remaining characters using @var{set2}.
+first performs any deletions using @var{array1}, then squeezes repeats
+from any remaining characters using @var{array2}.
 
 The @option{--squeeze-repeats} option may also be used when translating,
 in which case @command{tr} first performs translation, then squeezes
-repeats from any remaining characters using @var{set2}.
+repeats from any remaining characters using @var{array2}.
 
 Here are some examples to illustrate various combinations of options:
 
@@ -7225,7 +7246,7 @@ tr -cs '[:alnum:]' '[\n*]'
 
 @item
 Convert each sequence of repeated newlines to a single newline.
-I.e., delete blank lines:
+I.e., delete empty lines:
 
 @example
 tr -s '\n'
@@ -7279,16 +7300,6 @@ Or you can use @samp{--} to terminate option processing:
 tr -d -- -axM
 @end example
 
-More generally, use the character class notation @code{[=c=]}
-with @samp{-} (or any other character) in place of the @samp{c}:
-
-@example
-tr -d '[=-=]axM'
-@end example
-
-Note how single quotes are used in the above example to protect the
-square brackets from interpretation by a shell.
-
 @end itemize
 
 
diff --git a/src/tr.c b/src/tr.c
index 16dff94a6..0bfe8024b 100644
--- a/src/tr.c
+++ b/src/tr.c
@@ -285,25 +285,26 @@ usage (int status)
   else
     {
       printf (_("\
-Usage: %s [OPTION]... SET1 [SET2]\n\
+Usage: %s [OPTION]... STRING1 [STRING2]\n\
 "),
               program_name);
       fputs (_("\
 Translate, squeeze, and/or delete characters from standard input,\n\
-writing to standard output.\n\
+writing to standard output.  STRING1 and STRING2 specify arrays of\n\
+characters ARRAY1 and ARRAY2 that control the action.\n\
 \n\
-  -c, -C, --complement    use the complement of SET1\n\
-  -d, --delete            delete characters in SET1, do not translate\n\
+  -c, -C, --complement    use the complement of ARRAY1\n\
+  -d, --delete            delete characters in ARRAY1, do not translate\n\
   -s, --squeeze-repeats   replace each sequence of a repeated character\n\
-                            that is listed in the last specified SET,\n\
+                            that is listed in the last specified ARRAY,\n\
                             with a single occurrence of that character\n\
-  -t, --truncate-set1     first truncate SET1 to length of SET2\n\
+  -t, --truncate-set1     first truncate ARRAY1 to length of ARRAY2\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       fputs (_("\
 \n\
-SETs are specified as strings of characters.  Most represent themselves.\n\
+ARRAYs are specified as strings of characters.  Most represent themselves.\n\
 Interpreted sequences are:\n\
 \n\
   \\NNN            character with octal value NNN (1 to 3 octal digits)\n\
@@ -318,7 +319,7 @@ Interpreted sequences are:\n\
      fputs (_("\
   \\v              vertical tab\n\
   CHAR1-CHAR2     all characters from CHAR1 to CHAR2 in ascending order\n\
-  [CHAR*]         in SET2, copies of CHAR until length of SET1\n\
+  [CHAR*]         in ARRAY2, copies of CHAR until length of ARRAY1\n\
   [CHAR*REPEAT]   REPEAT copies of CHAR, REPEAT octal if starting with 0\n\
   [:alnum:]       all letters and digits\n\
   [:alpha:]       all letters\n\
@@ -338,13 +339,12 @@ Interpreted sequences are:\n\
 "), stdout);
      fputs (_("\
 \n\
-Translation occurs if -d is not given and both SET1 and SET2 appear.\n\
--t may be used only when translating.  SET2 is extended to length of\n\
-SET1 by repeating its last character as necessary.  Excess characters\n\
-of SET2 are ignored.  Only [:lower:] and [:upper:] are guaranteed to\n\
-expand in ascending order; used in SET2 while translating, they may\n\
-only be used in pairs to specify case conversion.  -s uses the last\n\
-specified SET, and occurs after translation or deletion.\n\
+Translation occurs if -d is not given and both STRING1 and STRING2 appear.\n\
+-t may be used only when translating.  ARRAY2 is extended to length of\n\
+ARRAY1 by repeating its last character as necessary.  Excess characters\n\
+of ARRAY2 are ignored.  Character classes expand in unspecified order;\n\
+while translating, [:lower:] and [:upper:] may be used in pairs to\n\
+specify case conversion.  Squeezing occurs after translation or deletion.\n\
 "), stdout);
       emit_ancillary_info (PROGRAM_NAME);
     }
-- 
2.32.0

From 0b0f1965fba57f1c75d92e36ac3cbc0d58396810 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 14 Feb 2022 12:16:07 -0800
Subject: [PATCH 2/2] tr: mention multibyte problem in man page

* man/tr.x: Document tr problem.
---
 man/tr.x | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/man/tr.x b/man/tr.x
index f28f1b0d6..b53103288 100644
--- a/man/tr.x
+++ b/man/tr.x
@@ -2,3 +2,12 @@
 tr \- translate or delete characters
 [DESCRIPTION]
 .\" Add any additional description here
+[BUGS]
+.PP
+Full support is available only for safe single-byte locales,
+in which every possible input byte represents a single character.
+The C locale is safe in GNU systems, so you can avoid this issue
+in the shell by running
+.B "LC_ALL=C tr"
+instead of plain
+.BR tr .
-- 
2.32.0

Reply via email to