From 216e4d1398266c4f8265ff9dd9226d1878f60b5a Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Tue, 9 May 2023 22:47:40 -0500 Subject: [PATCH] Implement set -u in toysh. --- scripts/runtest.sh | 13 ++++++++----- tests/sh.test | 1 + toys/pending/sh.c | 11 ++++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/scripts/runtest.sh b/scripts/runtest.sh index ce6e0fd0..3d2df237 100644 --- a/scripts/runtest.sh +++ b/scripts/runtest.sh @@ -167,10 +167,13 @@ testcmd() # Simple implementation of "expect" written in shell. -# txpect NAME COMMAND [I/O/E/Xstring]... -# Run COMMAND and interact with it: send I strings to input, read O or E -# strings from stdout or stderr (empty string is "read line of input here"), -# X means close stdin/stdout/stderr and match return code (blank means nonzero) +# txpect NAME COMMAND [I/O/E/X/R[OE]string]... +# Run COMMAND and interact with it: +# I send string to input +# OE read exactly this string from stdout or stderr (bare = read+discard line) +# note: non-bare does not read \n unless you include it with O$'blah\n' +# R prefix means O or E is regex match (read line, must contain substring) +# X close stdin/stdout/stderr and match return code (blank means nonzero) txpect() { local NAME CASE VERBOSITY LEN PID A B X O @@ -236,7 +239,7 @@ txpect() wait $PID A=$? exec {OUT}<&- {ERR}<&- - if [ -z "$LEN" ] + if [ "$LEN" -eq 0 ] then [ $A -eq 0 ] && { do_fail;break;} # any error else diff --git a/tests/sh.test b/tests/sh.test index 7ffc993c..4f4d4cb2 100644 --- a/tests/sh.test +++ b/tests/sh.test @@ -597,6 +597,7 @@ shxpect '${a?b} sets err, stops cmdline eval' \ shxpect 'trace redirect' I$'set -x; echo one\n' E$'+ echo one\n'"$P" O$'one\n' \ I$'echo two 2>/dev/null\n' O$'two\n' E$'+ echo two\n'"$P" \ I$'{ echo three; } 2>/dev/null\n' O$'three\n' E"$P" +shxpect 'set -u' I$'set -u; echo $walrus\n' REwalrus X testing 'source file' 'source input' 'hello\n' 'echo hello \\\n' '' testing '. file' '. input' 'hello\n' 'echo hello \\\n' '' diff --git a/toys/pending/sh.c b/toys/pending/sh.c index ff29c11d..d97c2bbe 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -30,7 +30,7 @@ * TODO: getuid() vs geteuid() * TODO: test that $PS1 color changes work without stupid \[ \] hack * TODO: Handle embedded NUL bytes in the command line? (When/how?) - * TODO: set -e -u -o pipefail, shopt -s nullglob + * TODO: set -e -o pipefail, shopt -s nullglob * * bash man page: * control operators || & && ; ;; ;& ;;& ( ) | |& @@ -388,6 +388,7 @@ static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>", #define OPT_B 0x100 #define OPT_C 0x200 #define OPT_x 0x400 +#define OPT_u 0x800 // only export $PWD and $OLDPWD on first cd #define OPT_cd 0x80000000 @@ -1939,7 +1940,10 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, } else if (ss[-1]=='{'); // not prefix, fall through else if (cc == '#') { // TODO ${#x[@]} dd = !!strchr("@*", *ss); // For ${#@} or ${#*} do normal ${#} - ifs = getvar_special(ss-dd, jj, &kk, delete) ? : ""; + if (!(ifs = getvar_special(ss-dd, jj, &kk, delete))) { + if (TT.options&OPT_u) goto barf; + ifs = ""; + } if (!dd) push_arg(delete, ifs = xmprintf("%zu", strlen(ifs))); // ${!@} ${!@Q} ${!x} ${!x@} ${!x@Q} ${!x#} ${!x[} ${!x[*]} } else if (cc == '!') { // TODO: ${var[@]} array @@ -2000,6 +2004,7 @@ barf: aa.v = TT.ff->arg.v+1; } else { ifs = getvar_special(ss, jj, &jj, delete); + if (!ifs && (TT.options&OPT_u)) goto barf; if (!jj) { if (ss[-1] == '{') goto barf; new[oo++] = '$'; @@ -4386,7 +4391,7 @@ void set_main(void) if (!cc || !dd) break; for (jj = 1; cc[jj]; jj++) { if (cc[jj] == 'o') oo++; - else if (-1 != (kk = stridx("BCx", cc[jj]))) { + else if (-1 != (kk = stridx("BCxu", cc[jj]))) { if (*cc == '-') TT.options |= OPT_B<