From 7132537ba3ec17872cf2f9098d4d0374605081e1 Mon Sep 17 00:00:00 2001 From: Andy Chu Date: Thu, 19 Mar 2020 10:25:17 -0700 Subject: [PATCH] [builtins/printf] Implement printf %b for 'echo -e' escaping With special \c behavior - Also refactor 'echo -e' a bit. Addresses issue #357. --- osh/builtin_printf.py | 28 ++++++++++++++++++++++++++-- osh/builtin_pure.py | 20 +++++++++++--------- spec/builtin-io.test.sh | 4 ++-- spec/builtin-printf.test.sh | 12 +++++++----- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/osh/builtin_printf.py b/osh/builtin_printf.py index 8012046182..e5fe0217cd 100755 --- a/osh/builtin_printf.py +++ b/osh/builtin_printf.py @@ -103,8 +103,6 @@ def _ParseFormatStr(self): if part.type.val in 'eEfFgG': p_die("osh printf doesn't support floating point", token=part.type) # These two could be implemented. %c needs utf-8 decoding. - if part.type.val == 'b': - p_die("osh printf doesn't support backslash escaping (try $'\\n')", token=part.type) if part.type.val == 'c': p_die("osh printf doesn't support single characters (bytes)", token=part.type) @@ -201,6 +199,7 @@ def Run(self, cmd_val): out = [] arg_index = 0 num_args = len(varargs) + backslash_c = False while True: for part in parts: @@ -284,8 +283,30 @@ def Run(self, cmd_val): if typ == 's': if precision is not None: s = s[:precision] # truncate + elif typ == 'q': s = string_ops.ShellQuoteOneLine(s) + + elif typ == 'b': + # Process just like echo -e, except \c handling is simpler. + + parts = [] # type: List[str] + lex = match.EchoLexer(s) + while True: + id_, value = lex.Next() + if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator + break + + p = word_compile.EvalCStringToken(id_, value) + + # Unusual behavior: '\c' aborts processing! + if p is None: + backslash_c = True + break + + parts.append(p) + s = ''.join(parts) + elif typ in 'diouxX' or part.type.id == Id.Format_Time: try: d = int(s) @@ -378,6 +399,9 @@ def Run(self, cmd_val): else: raise AssertionError() + if backslash_c: # 'printf %b a\cb xx' - \c terminates processing! + break + if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to diff --git a/osh/builtin_pure.py b/osh/builtin_pure.py index f109766104..28249dce6f 100644 --- a/osh/builtin_pure.py +++ b/osh/builtin_pure.py @@ -576,12 +576,15 @@ def Run(self, cmd_val): argv = cmd_val.argv[1:] arg, arg_index = ECHO_SPEC.ParseLikeEcho(argv) argv = argv[arg_index:] + + backslash_c = False # \c terminates input + if arg.e: new_argv = [] for a in argv: - parts = [] + parts = [] # type: List[str] lex = match.EchoLexer(a) - while True: + while not backslash_c: id_, value = lex.Next() if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator break @@ -590,15 +593,14 @@ def Run(self, cmd_val): # Unusual behavior: '\c' prints what is there and aborts processing! if p is None: - new_argv.append(''.join(parts)) - for i, a in enumerate(new_argv): - if i != 0: - sys.stdout.write(' ') # arg separator - sys.stdout.write(a) - return 0 # EARLY RETURN + backslash_c = True + break parts.append(p) + new_argv.append(''.join(parts)) + if backslash_c: # no more args either + break # Replace it argv = new_argv @@ -620,7 +622,7 @@ def Run(self, cmd_val): sys.stdout.write(' ') # arg separator sys.stdout.write(a) - if not arg.n: + if not arg.n and not backslash_c: sys.stdout.write('\n') return 0 diff --git a/spec/builtin-io.test.sh b/spec/builtin-io.test.sh index 34c267b628..0bbeed1ef3 100644 --- a/spec/builtin-io.test.sh +++ b/spec/builtin-io.test.sh @@ -103,9 +103,9 @@ echo -e 'ab\0cd' flags='-e' case $SH in dash) flags='' ;; esac -echo $flags xy 'ab\cde' 'ab\cde' +echo $flags xy 'ab\cde' 'zzz' ## stdout-json: "xy ab" -## N-I mksh stdout-json: "xy abde abde" +## N-I mksh stdout-json: "xy abde zzz" #### echo -e with hex escape echo -e 'abcd\x65f' diff --git a/spec/builtin-printf.test.sh b/spec/builtin-printf.test.sh index 653812033f..5adec8406a 100644 --- a/spec/builtin-printf.test.sh +++ b/spec/builtin-printf.test.sh @@ -1,5 +1,3 @@ -#!/usr/bin/env bash -# # printf # bash-completion uses this odd printf -v construction. It seems to mostly use # %s and %q though. @@ -436,9 +434,13 @@ echo status=$? [$] status=0 ## END -## N-I osh STDOUT: -[\044] -status=2 + +#### printf %b with \c early return +printf '[%b]\n' 'ab\ncd\cxy' +echo $? +## STDOUT: +[ab +cd0 ## END #### printf %c -- doesn't respect UTF-8! Bad.