[git commit] shell: improve bash compatibility of read built-in

Denys Vlasenko vda.linux at googlemail.com
Tue Jul 1 18:21:37 UTC 2025


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

Make the read built-in more compatible with bash:

- Return an exit code of 142 on timeout.

- When the timeout expires before a newline is detected in the
  input bash captures the partial input.  This behaviour is new
  since bash version 4.4.  BusyBox shells had the pre-4.4 behaviour
  where the input was lost.

Update the tests to suit and fix a couple of compiler errors in
the testsuite.

function                                             old     new   delta
builtin_read                                         154     174     +20
readcmd                                              213     228     +15
shell_builtin_read                                  1364    1370      +6
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/0 up/down: 41/0)               Total: 41 bytes

Signed-off-by: Ron Yorston <rmy at pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/ash.c                             |  5 +++++
 shell/ash_test/ash-read/read_ifs2.right |  9 +++++++++
 shell/ash_test/ash-read/read_ifs2.tests |  9 +++++++++
 shell/ash_test/ash-read/read_t.right    |  8 ++++----
 shell/ash_test/ash-read/read_t.tests    | 18 +++++++++---------
 shell/ash_test/printenv.c               |  4 +---
 shell/ash_test/recho.c                  |  2 +-
 shell/hush.c                            |  5 +++++
 shell/hush_test/hush-read/read_t.right  |  8 ++++----
 shell/hush_test/hush-read/read_t.tests  | 18 +++++++++---------
 shell/shell_common.c                    | 10 +++++++---
 11 files changed, 63 insertions(+), 33 deletions(-)

diff --git a/shell/ash.c b/shell/ash.c
index 9173b8608..92b1df5f8 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -14395,6 +14395,11 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 			goto again;
 	}
 
+	if ((uintptr_t)r == 2) /* -t SEC timeout? */
+		/* bash: "The exit status is greater than 128 if the timeout is exceeded." */
+		/* The actual value observed with bash 5.2.15: */
+		return 128 + SIGALRM;
+
 	if ((uintptr_t)r > 1)
 		ash_msg_and_raise_error(r);
 
diff --git a/shell/ash_test/ash-read/read_ifs2.right b/shell/ash_test/ash-read/read_ifs2.right
new file mode 100644
index 000000000..797137dae
--- /dev/null
+++ b/shell/ash_test/ash-read/read_ifs2.right
@@ -0,0 +1,9 @@
+|X|Y:Z:|
+|X|Y:Z|
+|X|Y|
+|X|Y|
+|X||
+|X||
+|||
+Whitespace should be trimmed too:
+|X|Y|
diff --git a/shell/ash_test/ash-read/read_ifs2.tests b/shell/ash_test/ash-read/read_ifs2.tests
new file mode 100755
index 000000000..f01a68978
--- /dev/null
+++ b/shell/ash_test/ash-read/read_ifs2.tests
@@ -0,0 +1,9 @@
+echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|")
+echo "X:Y:Z"  | (IFS=": " read x y; echo "|$x|$y|")
+echo "X:Y:"   | (IFS=": " read x y; echo "|$x|$y|")
+echo "X:Y"    | (IFS=": " read x y; echo "|$x|$y|")
+echo "X:"     | (IFS=": " read x y; echo "|$x|$y|")
+echo "X"      | (IFS=": " read x y; echo "|$x|$y|")
+echo ""       | (IFS=": " read x y; echo "|$x|$y|")
+echo Whitespace should be trimmed too:
+echo "X:Y  : " | (IFS=": " read x y; echo "|$x|$y|")
diff --git a/shell/ash_test/ash-read/read_t.right b/shell/ash_test/ash-read/read_t.right
index 04126cbe6..3eedae275 100644
--- a/shell/ash_test/ash-read/read_t.right
+++ b/shell/ash_test/ash-read/read_t.right
@@ -1,4 +1,4 @@
-><
-><
->test<
->test<
+>te:142<
+>:142<
+>test:0<
+>test:0<
diff --git a/shell/ash_test/ash-read/read_t.tests b/shell/ash_test/ash-read/read_t.tests
index d65f1aeaa..9fbeec517 100755
--- a/shell/ash_test/ash-read/read_t.tests
+++ b/shell/ash_test/ash-read/read_t.tests
@@ -1,10 +1,10 @@
-# bash 3.2 outputs:
+# bash 5.2 outputs:
 
-# ><
-{ echo -n 'te'; sleep 2; echo 'st'; }   | (read -t 1 reply; echo ">$reply<")
-# ><
-{               sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<")
-# >test<
-{ echo -n 'te'; sleep 1; echo 'st'; }   | (read -t 2 reply; echo ">$reply<")
-# >test<
-{               sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<")
+# >te:142<
+{ echo -n 'te'; sleep 2; echo 'st'; }   | (read -t 1 reply; echo ">$reply:$?<")
+# >:142<
+{               sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply:$?<")
+# >test:0<
+{ echo -n 'te'; sleep 1; echo 'st'; }   | (read -t 2 reply; echo ">$reply:$?<")
+# >test:0<
+{               sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply:$?<")
diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c
index c86308d3b..f0f41984d 100644
--- a/shell/ash_test/printenv.c
+++ b/shell/ash_test/printenv.c
@@ -31,9 +31,7 @@
 extern char **environ;
 
 int
-main (argc, argv)
-     int argc;
-     char **argv;
+main (int argc, char **argv)
 {
   register char **envp, *eval;
   int len;
diff --git a/shell/ash_test/recho.c b/shell/ash_test/recho.c
index 42a5feafd..7e96b14cc 100644
--- a/shell/ash_test/recho.c
+++ b/shell/ash_test/recho.c
@@ -27,7 +27,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-void strprint();
+void strprint(char *);
 
 int main(int argc, char **argv)
 {
diff --git a/shell/hush.c b/shell/hush.c
index 4a97293cc..37cfecc08 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -11175,6 +11175,11 @@ static int FAST_FUNC builtin_read(char **argv)
 			goto again;
 	}
 
+	if ((uintptr_t)r == 2) /* -t SEC timeout? */
+		/* bash: "The exit status is greater than 128 if the timeout is exceeded." */
+		/* The actual value observed with bash 5.2.15: */
+		return 128 + SIGALRM;
+
 	if ((uintptr_t)r > 1) {
 		bb_simple_error_msg(r);
 		r = (char*)(uintptr_t)1;
diff --git a/shell/hush_test/hush-read/read_t.right b/shell/hush_test/hush-read/read_t.right
index 04126cbe6..3eedae275 100644
--- a/shell/hush_test/hush-read/read_t.right
+++ b/shell/hush_test/hush-read/read_t.right
@@ -1,4 +1,4 @@
-><
-><
->test<
->test<
+>te:142<
+>:142<
+>test:0<
+>test:0<
diff --git a/shell/hush_test/hush-read/read_t.tests b/shell/hush_test/hush-read/read_t.tests
index d65f1aeaa..9fbeec517 100755
--- a/shell/hush_test/hush-read/read_t.tests
+++ b/shell/hush_test/hush-read/read_t.tests
@@ -1,10 +1,10 @@
-# bash 3.2 outputs:
+# bash 5.2 outputs:
 
-# ><
-{ echo -n 'te'; sleep 2; echo 'st'; }   | (read -t 1 reply; echo ">$reply<")
-# ><
-{               sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<")
-# >test<
-{ echo -n 'te'; sleep 1; echo 'st'; }   | (read -t 2 reply; echo ">$reply<")
-# >test<
-{               sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<")
+# >te:142<
+{ echo -n 'te'; sleep 2; echo 'st'; }   | (read -t 1 reply; echo ">$reply:$?<")
+# >:142<
+{               sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply:$?<")
+# >test:0<
+{ echo -n 'te'; sleep 1; echo 'st'; }   | (read -t 2 reply; echo ">$reply:$?<")
+# >test:0<
+{               sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply:$?<")
diff --git a/shell/shell_common.c b/shell/shell_common.c
index e5c2cefb3..9a03f7265 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -204,8 +204,8 @@ shell_builtin_read(struct builtin_read_params *params)
 			 * 32-bit unix time wrapped (year 2038+).
 			 */
 			if (timeout <= 0) { /* already late? */
-				retval = (const char *)(uintptr_t)1;
-				goto ret;
+				retval = (const char *)(uintptr_t)2;
+				break;
 			}
 		}
 
@@ -217,8 +217,12 @@ shell_builtin_read(struct builtin_read_params *params)
 		pfd[0].events = POLLIN;
 //TODO race with a signal arriving just before the poll!
 		if (poll(pfd, 1, timeout) <= 0) {
-			/* timed out, or EINTR */
+			/* timed out, or some error */
 			err = errno;
+			if (!err) {
+				retval = (const char *)(uintptr_t)2;
+				break;
+			}
 			retval = (const char *)(uintptr_t)1;
 			goto ret;
 		}


More information about the busybox-cvs mailing list