diff --git a/src/cmd/ksh93/bltins/test.c b/src/cmd/ksh93/bltins/test.c index 628727c98..569844959 100644 --- a/src/cmd/ksh93/bltins/test.c +++ b/src/cmd/ksh93/bltins/test.c @@ -202,8 +202,26 @@ int b_test(int argc, char *argv[],Shbltin_t *context) exitval = (*argv[2]!=0); goto done; } - if(cp[0] != '-' || cp[2]) + if(cp[0] != '-' || cp[2] || cp[1]=='?') + { /* + * The following ugly hack supports 'test --man --' and '[ --man -- ]' and related + * getopts documentation options (which all overload the error message mechanism). + * This is the only way to make the 'test' command self-documenting; supporting the + * getopts doc options without the extra '--' argument would break the test/[ syntax. + */ + if(cp[0]=='-' && (cp[1]=='-' || cp[1]=='?') && + strcmp(argv[2],"--")==0) + { + char *av[3]; + av[0] = argv[0]; + av[1] = argv[1]; + av[2] = 0; + optget(av,sh_opttest); + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + return(2); + } break; + } exitval = (!test_unop(tdata.sh,cp[1],argv[2])); goto done; case 2: diff --git a/src/cmd/ksh93/data/testops.c b/src/cmd/ksh93/data/testops.c index 039a17c7e..9ffdc9d21 100644 --- a/src/cmd/ksh93/data/testops.c +++ b/src/cmd/ksh93/data/testops.c @@ -55,6 +55,109 @@ const Shtable_t shtab_testops[] = "", 0 }; +const char sh_opttest[] = +"[-1c?\n@(#)$Id: test (AT&T Research/ksh93) 2020-08-31 $\n]" +USAGE_LICENSE +"[+NAME?test, [ - evaluate expression]" +"[+DESCRIPTION?\btest\b evaluates expressions and returns its result using the " + "exit status. Option parsing is not performed; all arguments, " + "including \b--\b, are processed as operands. It is essential to quote " + "expression arguments to suppress empty removal, field splitting, file " + "name generation, and constructs such as redirection. If the command " + "is invoked as \b[\b, a final \b]]\b argument is required and " + "discarded. The evaluation of the expression depends on the number of " + "operands, as follows:]" +"{" + "[+0?Evaluates to false.]" + "[+1?True if argument is not an empty string.]" + "[+2?If first operand is \b!\b, the result is True if the second " + "operand is an empty string. Otherwise, it is evaluated " + "as one of the unary expressions defined below. If the " + "unary operator is invalid and the second argument is \b--\b, " + "then the first argument is processed as a \bgetopts\b(1) " + "help option.]" + "[+3?If first operand is \b!\b, the result is True if the second " + "and third operand evaluated as a unary expression is False. " + "Otherwise, the three operands are evaluated as one of the " + "binary expressions listed below.]" + "[+4?If first operand is \b!\b, the result is True if the next " + "three operands are a valid binary expression that is False.]" +"}" +"[+?For the following unary and binary operators:]" +"{" + "[+?If any \afile\a is a symlink, it is followed before testing, " + "except for \b-L\b/\b-h\b" +#if SHOPT_TEST_L + "/\b-l\b" +#endif + ".]" + "[+?If any \afile\a is of the form \b/dev/fd/\b\an\a, " + "then file descriptor \an\a is checked.]" + "[+?Operators marked with a * are not part of the POSIX standard.]" +"}" +"[+UNARY OPERATORS?These evaluate as True if:]{" + "[+-a \afile\a *?Same as \b-e\b.]" + "[+-b \afile\a?\afile\a is a block special file.]" + "[+-c \afile\a?\afile\a is a character special file.]" + "[+-d \afile\a?\afile\a is a directory.]" + "[+-e \afile\a?\afile\a exists and is not a broken symlink.]" + "[+-f \afile\a?\afile\a is a regular file.]" + "[+-g \afile\a?\afile\a has its set-group-id bit set.]" + "[+-h \afile\a?Same as \b-L\b.]" + "[+-k \afile\a *?\afile\a has its sticky bit on.]" +#if SHOPT_TEST_L + "[+-l \afile\a *?Same as \b-L\b.]" +#endif + "[+-n \astring\a?Length of \astring\a is non-zero.]" + "[+-o \aoption\a *?Shell option \aoption\a is enabled.]" + "[+-p \afile\a?\afile\a is a FIFO.]" + "[+-r \afile\a?\afile\a is readable.]" + "[+-s \afile\a?\afile\a has size > 0.]" + "[+-t \anum\a?File descriptor number \anum\a is " + "open and associated with a terminal.]" + "[+-u \afile\a?\afile\a has its set-user-id bit set.]" + "[+-v \avarname\a *?The variable \avarname\a is set.]" + "[+-w \afile\a?\afile\a is writable.]" + "[+-x \afile\a?\afile\a is executable, or if directory, searchable.]" + "[+-z \astring\a?\astring\a is a zero-length string.]" + "[+-G \afile\a *?Group of \afile\a is the effective " + "group ID of the current process.]" + "[+-L \afile\a?\afile\a is a symbolic link.]" + "[+-N \afile\a *?\afile\a has been modified since it was last read.]" + "[+-O \afile\a *?\afile\a exists and owner is the effective " + "user ID of the current process.]" + "[+-R \avarname\a *?\avarname\a is a name reference.]" + "[+-S \afile\a?\afile\a is a socket.]" +"}" +"[+BINARY OPERATORS?These evaluate as True if:]{" + "[+\astring1\a = \astring2\a?\astring1\a is equal to \astring2\a.]" + "[+\astring1\a == \astring2\a *?Same as \b=\b.]" + "[+\astring1\a != \astring2\a?\astring1\a is not equal to \astring2\a.]" + "[+\anum1\a -eq \anum2\a?\anum1\a is numerically equal to \anum2\a.]" + "[+\anum1\a -ne \anum2\a?\anum1\a is not numerically equal to \anum2\a.]" + "[+\anum1\a -lt \anum2\a?\anum1\a is less than \anum2\a.]" + "[+\anum1\a -le \anum2\a?\anum1\a is less than or equal to \anum2\a.]" + "[+\anum1\a -gt \anum2\a?\anum1\a is greater than \anum2\a.]" + "[+\anum1\a -ge \anum2\a?\anum1\a is greater than or equal to \anum2\a.]" + "[+\afile1\a -nt \afile2\a *?\afile1\a is newer than \afile2\a " + "or \afile1\a does not exist.]" + "[+\afile1\a -ot \afile2\a *?\afile1\a is older than \afile2\a " + "or \afile2\a does not exist.]" + "[+\afile1\a -ef \afile2\a *?\afile1\a \afile1\a is a hard link or " + "symbolic link to \afile2\a.]" +"}" +"\n" +"\nexpression\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?The expression evaluated to True.]" + "[+1?The expression evaluated to False.]" + "[+>1?An error occurred.]" +"}" + +"[+SEE ALSO?\blet\b(1), \bexpr\b(1)]" +; + const char test_opchars[] = "HLNRSVOGCaeohrwxdcbfugkv" #if SHOPT_TEST_L "l" diff --git a/src/cmd/ksh93/include/test.h b/src/cmd/ksh93/include/test.h index 1049fc6ca..a9ed37bdc 100644 --- a/src/cmd/ksh93/include/test.h +++ b/src/cmd/ksh93/include/test.h @@ -61,6 +61,7 @@ extern int test_unop(Shell_t*,int, const char*); extern int test_inode(const char*, const char*); extern int test_binop(Shell_t*,int, const char*, const char*); +extern const char sh_opttest[]; extern const char test_opchars[]; extern const char e_argument[]; extern const char e_missing[]; diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index 4de86e3fc..a65b7dc4c 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -7219,6 +7219,15 @@ grammatical ambiguities in the expressions. To avoid the many pitfalls arising from these issues, the \f3[[\fP compound command should be used instead. The primary purpose of the \f3test\fP and \f3[\fP commands is compatibility with other shells that lack \f3[[\fP. +.RS +.PP +The \f3test\fP/\f3[\fP command does not parse options except if there are +two arguments and the second is \f3--\fP. To access the inline documentation +with an option such as \f3--man\fP, you need one of the forms +.B test\ --man\ -- +or +.BR [\ --man\ --\ ] . +.RE .TP \f3times\fP Displays the accumulated user and system CPU times, one line with the times