[git commit] ls: fix -Q to match GNU

Denys Vlasenko vda.linux at googlemail.com
Thu Jul 31 23:02:43 UTC 2025


commit: https://git.busybox.net/busybox/commit/?id=4f3a56dc12a2126bdacff73f1cfe586d06e800c0
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

function                                             old     new   delta
print_name                                           137     229     +92
display_files                                        375     402     +27
c_escape_conv_str00                                    -      24     +24
display                                             1476    1485      +9
conv_str                                              33       -     -33
------------------------------------------------------------------------------
(add/remove: 2/1 grow/shrink: 3/0 up/down: 152/-33)           Total: 119 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 coreutils/ls.c          | 50 +++++++++++++++++++++++++++++++++++++------------
 include/libbb.h         |  3 +++
 libbb/c_escape.c        | 20 ++++++++++++++++++++
 libbb/dump.c            | 39 +++++++++++++++++++-------------------
 testsuite/hexdump.tests | 10 ++++++++++
 testsuite/ls.tests      | 26 +++++++++++++++++++++++--
 6 files changed, 114 insertions(+), 34 deletions(-)

diff --git a/coreutils/ls.c b/coreutils/ls.c
index c725be92d..9e4b83032 100644
--- a/coreutils/ls.c
+++ b/coreutils/ls.c
@@ -457,19 +457,29 @@ static unsigned calc_name_len(const char *name)
 	if (!(option_mask32 & (OPT_q|OPT_Q)))
 		return strlen(name);
 
-	name = printable_string2(&uni_stat, name);
-
 	if (!(option_mask32 & OPT_Q)) {
+		printable_string2(&uni_stat, name);
 		return uni_stat.unicode_width;
 	}
 
-	// TODO: quote chars 7..13 as \a,b,t,n,v,f,r
-	// other chars <32 or >127 as \ooo octal
-	len = 2 + uni_stat.unicode_width;
+	len = 2 + strlen(name);
 	while (*name) {
+	        unsigned char ch = (unsigned char)*name;
+		if (ch < ' ' || ch > 0x7e) {
+			ch -= 7;
+			if ((signed char)ch >= 0 && ch <= 6) {
+				// quote chars 7..13 as \a,b,t,n,v,f,r
+				len++;
+				goto next;
+			}
+			// other chars <32 or >126 as \ooo octal
+			len += 3;
+			goto next;
+		}
 		if (*name == '"' || *name == '\\') {
 			len++;
 		}
+ next:
 		name++;
 	}
 	return len;
@@ -492,23 +502,39 @@ static unsigned print_name(const char *name)
 		return strlen(name);
 	}
 
-	name = printable_string2(&uni_stat, name);
-
 	if (!(option_mask32 & OPT_Q)) {
+		name = printable_string2(&uni_stat, name);
 		fputs_stdout(name);
 		return uni_stat.unicode_width;
 	}
 
-	// TODO: quote chars 7..13 as \a,b,t,n,v,f,r
-	// other chars <32 or >127 as \ooo octal
-	len = 2 + uni_stat.unicode_width;
+	len = 2 + strlen(name);
 	putchar('"');
 	while (*name) {
-		if (*name == '"' || *name == '\\') {
+	        unsigned char ch = (unsigned char)*name;
+		if (ch < ' ' || ch > 0x7e) {
+			putchar('\\');
+			ch -= 7;
+			if ((signed char)ch >= 0 && ch <= 6) {
+				// quote chars 7..13 as \a,b,t,n,v,f,r
+				ch = c_escape_conv_str07[1 + 3 * ch];
+				len++;
+				goto put_ch;
+			}
+			// other chars <32 or >126 as \ooo octal
+			ch = (unsigned char)*name;
+			putchar('0' + ((ch>>6) & 7));
+			putchar('0' + ((ch>>3) & 7));
+			ch = '0' + (ch & 7);
+			len += 3;
+			goto put_ch;
+		}
+		if (ch == '"' || ch == '\\') {
 			putchar('\\');
 			len++;
 		}
-		putchar(*name);
+ put_ch:
+		putchar(ch);
 		name++;
 	}
 	putchar('"');
diff --git a/include/libbb.h b/include/libbb.h
index 7105bd479..cdc05049c 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1113,6 +1113,9 @@ char *bin2hex(char *dst, const char *src, int count) FAST_FUNC;
 /* Reverse */
 char* hex2bin(char *dst, const char *src, int count) FAST_FUNC;
 
+extern const char c_escape_conv_str00[];
+#define c_escape_conv_str07 (c_escape_conv_str00+3)
+
 void FAST_FUNC xorbuf_3(void *dst, const void *src1, const void *src2, unsigned count);
 void FAST_FUNC xorbuf(void* buf, const void* mask, unsigned count);
 void FAST_FUNC xorbuf16_aligned_long(void* buf, const void* mask);
diff --git a/libbb/c_escape.c b/libbb/c_escape.c
new file mode 100644
index 000000000..6c109f2e0
--- /dev/null
+++ b/libbb/c_escape.c
@@ -0,0 +1,20 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2025 by Denys Vlasenko <vda.linux at googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//kbuild:lib-y += c_escape.o
+
+#include "libbb.h"
+
+const char c_escape_conv_str00[] ALIGN1 =
+	"\\""0""\0" // [0]:00
+	"\\""a""\0" // [1]:07
+	"\\""b""\0" // [2]:08
+	"\\""t""\0" // [3]:09
+	"\\""n""\0" // [4]:0a
+	"\\""v""\0" // [5]:0b
+	"\\""f""\0" // [6]:0c
+	"\\""r"     // [7]:0d
+	;
diff --git a/libbb/dump.c b/libbb/dump.c
index ac5d47d9e..0cc7775d6 100644
--- a/libbb/dump.c
+++ b/libbb/dump.c
@@ -478,37 +478,36 @@ static void bpad(PR *pr)
 		continue;
 }
 
-static const char conv_str[] ALIGN1 =
-	"\0"  "\\""0""\0"
-	"\007""\\""a""\0"
-	"\b"  "\\""b""\0"
-	"\f"  "\\""f""\0"
-	"\n"  "\\""n""\0"
-	"\r"  "\\""r""\0"
-	"\t"  "\\""t""\0"
-	"\v"  "\\""v""\0"
-	;
-
 static void conv_c(PR *pr, unsigned char *p)
 {
-	const char *str = conv_str;
-
-	do {
-		if (*p == *str) {
-			++str;
-			goto strpr; /* map e.g. '\n' to "\\n" */
-		}
-		str += 4;
-	} while (*str);
+	const char *str;
+	unsigned char ch;
+
+	ch = *p;
+	if (ch == 0 || (ch -= 6, (signed char)ch > 0 && ch <= 7)) {
+		/* map chars 0,7..13 to "\0","\{a,b,t,n,v,f,r}" */
+		str = c_escape_conv_str00 + 3 * ch;
+		goto strpr;
+	}
 
 	if (isprint_asciionly(*p)) {
 		*pr->cchar = 'c';
 		printf(pr->fmt, *p);
 	} else {
+#if 1
 		char buf[4];
 		/* gcc-8.0.1 needs lots of casts to shut up */
 		sprintf(buf, "%03o", (unsigned)(uint8_t)*p);
 		str = buf;
+#else // use faster version? +20 bytes of code
+		char buf[4];
+		buf[3] = '\0';
+		ch = *p;
+		buf[2] = '0' + (ch & 7); ch >>= 3;
+		buf[1] = '0' + (ch & 7); ch >>= 3;
+		buf[0] = '0' + ch;
+		str = buf;
+#endif
  strpr:
 		*pr->cchar = 's';
 		printf(pr->fmt, str);
diff --git a/testsuite/hexdump.tests b/testsuite/hexdump.tests
index b2f6a2201..d2c0a5dc8 100755
--- a/testsuite/hexdump.tests
+++ b/testsuite/hexdump.tests
@@ -56,6 +56,16 @@ testing "hexdump -e %3_u" \
 " \
         "" "$input"
 
+testing "hexdump -e %3_c" \
+	"hexdump -e '16/1 \" %3_c\" \"\n\"'" \
+'  \\0 001 002 003 004 005 006  \\a  \\b  \\t  \\n  \\v  \\f  \\r 016 017
+ 020 021 022 023 024 025 026 027 030 031 032 033 034 035 036 037
+   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ 177
+ 200 201 202 203 204 205 206 207 210 211 212 213 214 215 216 217
+ 360 361 362 363 364 365 366 367 370 371 372 373 374 375 376 377
+' \
+        "" "$input"
+
 testing "hexdump -e /1 %d" \
 	"hexdump -e '16/1 \" %4d\" \"\n\"'" \
 	"\
diff --git a/testsuite/ls.tests b/testsuite/ls.tests
index 9309d366b..a95911034 100755
--- a/testsuite/ls.tests
+++ b/testsuite/ls.tests
@@ -19,7 +19,7 @@ test x"$CONFIG_UNICODE_SUPPORT" = x"y" \
 && test x"$CONFIG_LAST_SUPPORTED_WCHAR" = x"767" \
 && test x"$CONFIG_FEATURE_LS_SORTFILES" = x"y" \
 && testing "ls unicode test with codepoints limited to 767" \
-"(cd ls.testdir && sh ../ls.mk_uni_tests) && ls -1 ls.testdir" \
+"(cd ls.testdir && sh ../ls.mk_uni_tests) && ls -1q ls.testdir" \
 '0001_1__Some_correct_UTF-8_text___________________________________________|
 0002_2__Boundary_condition_test_cases_____________________________________|
 0003_2.1__First_possible_sequence_of_a_certain_length_____________________|
@@ -138,7 +138,7 @@ test x"$CONFIG_UNICODE_SUPPORT" = x"y" \
 && test x"$CONFIG_SUBST_WCHAR" = x"63" \
 && test x"$CONFIG_LAST_SUPPORTED_WCHAR" = x"0" \
 && testing "ls unicode test with unlimited codepoints" \
-"(cd ls.testdir && sh ../ls.mk_uni_tests) && ls -1 ls.testdir" \
+"(cd ls.testdir && sh ../ls.mk_uni_tests) && ls -1q ls.testdir" \
 '0001_1__Some_correct_UTF-8_text___________________________________________|
 0002_2__Boundary_condition_test_cases_____________________________________|
 0003_2.1__First_possible_sequence_of_a_certain_length_____________________|
@@ -262,6 +262,28 @@ test x"$CONFIG_FEATURE_LS_SORTFILES" = x"y" \
 "A\nB\nA\nB\nA\nB\n" \
 "" ""
 
+rm -rf ls.testdir 2>/dev/null
+mkdir ls.testdir || exit 1
+touch "`printf "ls.testdir/\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f_\x7f\x80\xfe\xff_\x22_\x27_\x5c"`"
+
+sq="'"
+
+# testing "test name" "command" "expected result" "file input" "stdin"
+testing "ls -q" \
+'ls -q ls.testdir' \
+'???????????????????????????????_????_"_'$sq'_\\''\n' \
+"" ""
+
+testing "ls -Q" \
+'ls -Q ls.testdir' \
+'"\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037_\\177\\200\\376\\377_\\"_'$sq'_\\\\"\n' \
+"" ""
+
+testing "ls -qQ" \
+'ls -qQ ls.testdir' \
+'"\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037_\\177\\200\\376\\377_\\"_'$sq'_\\\\"\n' \
+"" ""
+
 # Clean up
 rm -rf ls.testdir 2>/dev/null
 


More information about the busybox-cvs mailing list