@@ -44,6 +44,8 @@ func transformerSubUsage(
4444 fmt .Fprintf (o , "Usage: %s %s [options]\n " , "mlr" , verbNameSub )
4545 fmt .Fprintf (o , "Replaces old string with new string in specified field(s), with regex support\n " )
4646 fmt .Fprintf (o , "for the old string and not handling multiple matches, like the `sub` DSL function.\n " )
47+ fmt .Fprintf (o , "The replacement string supports C-style backslash escapes such as \\ n, \\ t,\n " )
48+ fmt .Fprintf (o , "and \\ x1f. Write \\ \\ to get a literal backslash.\n " )
4749 fmt .Fprintf (o , "See also the `gsub` and `ssub` verbs.\n " )
4850 fmt .Fprintf (o , "Options:\n " )
4951 fmt .Fprintf (o , "-f {a,b,c} Field names to convert.\n " )
@@ -58,6 +60,8 @@ func transformerGsubUsage(
5860 fmt .Fprintf (o , "Usage: %s %s [options]\n " , "mlr" , verbNameGsub )
5961 fmt .Fprintf (o , "Replaces old string with new string in specified field(s), with regex support\n " )
6062 fmt .Fprintf (o , "for the old string and handling multiple matches, like the `gsub` DSL function.\n " )
63+ fmt .Fprintf (o , "The replacement string supports C-style backslash escapes such as \\ n, \\ t,\n " )
64+ fmt .Fprintf (o , "and \\ x1f. Write \\ \\ to get a literal backslash.\n " )
6165 fmt .Fprintf (o , "See also the `sub` and `ssub` verbs.\n " )
6266 fmt .Fprintf (o , "Options:\n " )
6367 fmt .Fprintf (o , "-f {a,b,c} Field names to convert.\n " )
@@ -71,7 +75,10 @@ func transformerSsubUsage(
7175) {
7276 fmt .Fprintf (o , "Usage: %s %s [options]\n " , "mlr" , verbNameSsub )
7377 fmt .Fprintf (o , "Replaces old string with new string in specified field(s), without regex support for\n " )
74- fmt .Fprintf (o , "the old string, like the `ssub` DSL function. See also the `gsub` and `sub` verbs.\n " )
78+ fmt .Fprintf (o , "the old string, like the `ssub` DSL function.\n " )
79+ fmt .Fprintf (o , "Both the search and replacement strings support C-style backslash escapes such\n " )
80+ fmt .Fprintf (o , "as \\ n, \\ t, and \\ x1f. Write \\ \\ to get a literal backslash.\n " )
81+ fmt .Fprintf (o , "See also the `gsub` and `sub` verbs.\n " )
7582 fmt .Fprintf (o , "Options:\n " )
7683 fmt .Fprintf (o , "-f {a,b,c} Field names to convert.\n " )
7784 fmt .Fprintf (o , "-r {regex} Regular expression for field names to convert.\n " )
@@ -98,7 +105,7 @@ func transformerSubParseCLI(
98105 opts * cli.TOptions ,
99106 doConstruct bool , // false for first pass of CLI-parse, true for second pass
100107) (RecordTransformer , error ) {
101- return transformerSubsParseCLI (pargi , argc , args , opts , doConstruct , transformerSubUsage , NewTransformerSub )
108+ return transformerSubsParseCLI (pargi , argc , args , opts , doConstruct , transformerSubUsage , NewTransformerSub , false )
102109}
103110
104111func transformerGsubParseCLI (
@@ -108,7 +115,7 @@ func transformerGsubParseCLI(
108115 opts * cli.TOptions ,
109116 doConstruct bool , // false for first pass of CLI-parse, true for second pass
110117) (RecordTransformer , error ) {
111- return transformerSubsParseCLI (pargi , argc , args , opts , doConstruct , transformerGsubUsage , NewTransformerGsub )
118+ return transformerSubsParseCLI (pargi , argc , args , opts , doConstruct , transformerGsubUsage , NewTransformerGsub , false )
112119}
113120
114121func transformerSsubParseCLI (
@@ -118,10 +125,12 @@ func transformerSsubParseCLI(
118125 opts * cli.TOptions ,
119126 doConstruct bool , // false for first pass of CLI-parse, true for second pass
120127) (RecordTransformer , error ) {
121- return transformerSubsParseCLI (pargi , argc , args , opts , doConstruct , transformerSsubUsage , NewTransformerSsub )
128+ return transformerSubsParseCLI (pargi , argc , args , opts , doConstruct , transformerSsubUsage , NewTransformerSsub , true )
122129}
123130
124131// transformerSubsParseCLI is a shared CLI-parser for the sub, gsub, and ssub verbs.
132+ // When unbackslashOldText is true (ssub only), the search string is also unescaped;
133+ // for sub/gsub the search string is a regex and Go's regexp engine handles \n/\t/etc.
125134func transformerSubsParseCLI (
126135 pargi * int ,
127136 argc int ,
@@ -130,6 +139,7 @@ func transformerSubsParseCLI(
130139 doConstruct bool , // false for first pass of CLI-parse, true for second pass
131140 usageFunc TransformerUsageFunc ,
132141 constructorFunc subConstructorFunc ,
142+ unbackslashOldText bool ,
133143) (RecordTransformer , error ) {
134144
135145 // Skip the verb name from the current spot in the mlr command line
@@ -192,6 +202,18 @@ func transformerSubsParseCLI(
192202 oldText = args [argi ]
193203 newText = args [argi + 1 ]
194204
205+ // Interpret C-style backslash escapes ("\n", "\t", "\x1f", etc.) in the
206+ // replacement string the same way the DSL string-literal parser does, so
207+ // that e.g. `mlr sub -a r "\n"` matches `sub($x, "r", "\n")` in the DSL.
208+ // For sub/gsub the search string is a regex and Go's regexp engine already
209+ // handles \n/\r/\t inside patterns; pre-unescaping would corrupt user-
210+ // supplied regex metachars like \d or \s. For ssub the search string is a
211+ // literal, so we unescape it too.
212+ newText = lib .UnbackslashStringLiteral (newText )
213+ if unbackslashOldText {
214+ oldText = lib .UnbackslashStringLiteral (oldText )
215+ }
216+
195217 argi += 2
196218
197219 * pargi = argi
0 commit comments