Make subcommands modify $status, and make builtin_set not modify status unless it fails

https://github.com/fish-shell/fish-shell/issues/547
https://github.com/fish-shell/fish-shell/issues/214
This commit is contained in:
ridiculousfish 2013-01-31 15:57:08 -08:00
parent 0db1b6ce44
commit ad8d68dd43
21 changed files with 143 additions and 139 deletions

View file

@ -358,11 +358,9 @@ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_
/* If we have a script, either built-in or a file source, then run it */ /* If we have a script, either built-in or a file source, then run it */
if (really_load && has_script_source) if (really_load && has_script_source)
{ {
if (exec_subshell(script_source) == -1) if (exec_subshell(script_source, false /* do not apply exit status */) == -1)
{ {
/* /* Do nothing on failure */
Do nothing on failiure
*/
} }
} }

View file

@ -215,7 +215,7 @@ wcstring builtin_help_get(parser_t &parser, const wchar_t *name)
wcstring out; wcstring out;
const wcstring name_esc = escape_string(name, 1); const wcstring name_esc = escape_string(name, 1);
const wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str()); const wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str());
if (exec_subshell(cmd, lst) >= 0) if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0)
{ {
for (size_t i=0; i<lst.size(); i++) for (size_t i=0; i<lst.size(); i++)
{ {

View file

@ -379,58 +379,21 @@ static void print_variables(int include_values, int esc, bool shorten_ok, int sc
*/ */
static int builtin_set(parser_t &parser, wchar_t **argv) static int builtin_set(parser_t &parser, wchar_t **argv)
{ {
/** Variables used for parsing the argument list */
/** const struct woption long_options[] =
Variables used for parsing the argument list
*/
static const struct woption
long_options[] =
{ {
{ { L"export", no_argument, 0, 'x' },
L"export", no_argument, 0, 'x' { L"global", no_argument, 0, 'g' },
} { L"local", no_argument, 0, 'l' },
, { L"erase", no_argument, 0, 'e' },
{ { L"names", no_argument, 0, 'n' },
L"global", no_argument, 0, 'g' { L"unexport", no_argument, 0, 'u' },
} { L"universal", no_argument, 0, 'U' },
, { L"long", no_argument, 0, 'L' },
{ { L"query", no_argument, 0, 'q' },
L"local", no_argument, 0, 'l' { L"help", no_argument, 0, 'h' },
} { 0, 0, 0, 0 }
, } ;
{
L"erase", no_argument, 0, 'e'
}
,
{
L"names", no_argument, 0, 'n'
}
,
{
L"unexport", no_argument, 0, 'u'
}
,
{
L"universal", no_argument, 0, 'U'
}
,
{
L"long", no_argument, 0, 'L'
}
,
{
L"query", no_argument, 0, 'q'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
const wchar_t *short_options = L"+xglenuULqh"; const wchar_t *short_options = L"+xglenuULqh";
@ -443,6 +406,8 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
int erase = 0, list = 0, unexport=0; int erase = 0, list = 0, unexport=0;
int universal = 0, query=0; int universal = 0, query=0;
bool shorten_ok = true; bool shorten_ok = true;
bool preserve_incoming_failure_exit_status = true;
const int incoming_exit_status = proc_get_last_status();
/* /*
Variables used for performing the actual work Variables used for performing the actual work
@ -474,10 +439,12 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
case 'e': case 'e':
erase = 1; erase = 1;
preserve_incoming_failure_exit_status = false;
break; break;
case 'n': case 'n':
list = 1; list = 1;
preserve_incoming_failure_exit_status = false;
break; break;
case 'x': case 'x':
@ -506,6 +473,7 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
case 'q': case 'q':
query = 1; query = 1;
preserve_incoming_failure_exit_status = false;
break; break;
case 'h': case 'h':
@ -825,6 +793,8 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
free(dest); free(dest);
if (retcode == STATUS_BUILTIN_OK && preserve_incoming_failure_exit_status)
retcode = incoming_exit_status;
return retcode; return retcode;
} }

View file

@ -135,7 +135,7 @@ public:
/** Returns whether the color is bold */ /** Returns whether the color is bold */
bool is_bold() const bool is_bold() const
{ {
return !! (flags & flag_bold); return !!(flags & flag_bold);
} }
/** Set whether the color is bold */ /** Set whether the color is bold */

View file

@ -465,7 +465,7 @@ bool completer_t::condition_test(const wcstring &condition)
if (cached_entry == condition_cache.end()) if (cached_entry == condition_cache.end())
{ {
/* Compute new value and reinsert it */ /* Compute new value and reinsert it */
test_res = (0 == exec_subshell(condition)); test_res = (0 == exec_subshell(condition, false /* don't apply exit status */));
condition_cache[condition] = test_res; condition_cache[condition] = test_res;
} }
else else
@ -1007,7 +1007,7 @@ void completer_t::complete_cmd_desc(const wcstring &str)
since apropos is only called once. since apropos is only called once.
*/ */
wcstring_list_t list; wcstring_list_t list;
if (exec_subshell(lookup_cmd, list) != -1) if (exec_subshell(lookup_cmd, list, false /* don't apply exit status */) != -1)
{ {
/* /*

View file

@ -11,8 +11,7 @@ By defining the \c fish_prompt function, the user can choose a custom
prompt. The \c fish_prompt function is executed when the prompt is to prompt. The \c fish_prompt function is executed when the prompt is to
be shown, and the output is used as a prompt. be shown, and the output is used as a prompt.
Please keep in mind that the function is executed by <a The exit status of commands within \c fish_prompt will not modify the <a href="index.html#variables-status">$status</a> seen outside of fish_prompt.
href='index.html#expand-command-substitution'>command substitution</a>, and so the exit status of commands within fish_prompt will not modify the <a href="index.html#variables-status">$status</a> seen outside of fish_prompt.
\subsection fish_prompt-example Example \subsection fish_prompt-example Example

View file

@ -578,9 +578,8 @@ this list is executed, and substituted by the output. If the output is
more than one line long, each line will be expanded to a new more than one line long, each line will be expanded to a new
parameter. parameter.
A command substitution will not change the value of the <a The exit status of the last run command substitution is available in the <a
href='#variables-status'>status</a> variable outside of the command href='#variables-status'>status</a> variable.
substitution.
Only part of the output can be used, see <a href='#expand-index-range'>index Only part of the output can be used, see <a href='#expand-index-range'>index
range expansion</a> for details. range expansion</a> for details.

View file

@ -68,13 +68,14 @@ non-switch arguments. For example, <code>set flags -l</code> will have
the effect of setting the value of the variable <code>flags</code> to the effect of setting the value of the variable <code>flags</code> to
'-l', not making the variable local. '-l', not making the variable local.
In assignment mode, set exits with an exit status of zero it the In assignment mode, set exits with a non-zero exit status if variable
variable assignments where sucessfully performed, with a non-zero exit assignments could not be successfully performed. If the variable assignments
status otherwise. In query mode, the exit status is the number of were performed, the exit status is unchanged. This allows simultaneous capture
variables that where not found. In erase mode, set exits with a zero of the output and exit status of a subcommand, e.g. <code>if set output
exit status in case of success, with a non-zero exit status if the (command)</code>. In query mode, the exit status is the number of variables that
commandline was invalid, if the variable was write-protected or if the were not found. In erase mode, set exits with a zero exit status in case of
variable did not exist. success, with a non-zero exit status if the commandline was invalid, if the
variable was write-protected or if the variable did not exist.
\subsection set-example Example \subsection set-example Example
@ -85,3 +86,9 @@ variable did not exist.
<code>set -e smurf</code> removes the variable \c smurf. <code>set -e smurf</code> removes the variable \c smurf.
<code>set PATH[4] ~/bin</code> changes the fourth element of the \c PATH array to \c ~/bin <code>set PATH[4] ~/bin</code> changes the fourth element of the \c PATH array to \c ~/bin
<pre>if set python_path (which python)
echo "Python is at $python_path"
end</pre>
The above outputs the path to Python if \c which returns true.

View file

@ -1438,11 +1438,11 @@ void exec(parser_t &parser, job_t *j)
} }
static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst) static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, bool apply_exit_status)
{ {
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
int prev_subshell = is_subshell; int prev_subshell = is_subshell;
int status, prev_status; const int prev_status = proc_get_last_status();
char sep=0; char sep=0;
const env_var_t ifs = env_get_string(L"IFS"); const env_var_t ifs = env_get_string(L"IFS");
@ -1456,38 +1456,33 @@ static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst)
else else
{ {
sep = 0; sep = 0;
debug(0, L"Warning - invalid command substitution separator '%lc'. Please change the firsta character of IFS", ifs[0]); debug(0, L"Warning - invalid command substitution separator '%lc'. Please change the first character of IFS", ifs[0]);
} }
} }
is_subshell=1; is_subshell=1;
prev_status = proc_get_last_status();
const shared_ptr<io_buffer_t> io_buffer(io_buffer_t::create(0)); int subcommand_status = -1; //assume the worst
// IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may be null // IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may be null
if (io_buffer.get() == NULL) const shared_ptr<io_buffer_t> io_buffer(io_buffer_t::create(0));
{ if (io_buffer.get() != NULL)
status = -1;
}
else
{ {
parser_t &parser = parser_t::principal_parser(); parser_t &parser = parser_t::principal_parser();
if (parser.eval(cmd, io_chain_t(io_buffer), SUBST)) if (parser.eval(cmd, io_chain_t(io_buffer), SUBST) == 0)
{ {
status = -1; subcommand_status = proc_get_last_status();
}
else
{
status = proc_get_last_status();
} }
io_buffer->read(); io_buffer->read();
} }
proc_set_last_status(prev_status); // If the caller asked us to preserve the exit status, restore the old status
// Otherwise set the status of the subcommand
proc_set_last_status(apply_exit_status ? subcommand_status : prev_status);
is_subshell = prev_subshell; is_subshell = prev_subshell;
@ -1515,17 +1510,17 @@ static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst)
} }
} }
return status; return subcommand_status;
} }
int exec_subshell(const wcstring &cmd, std::vector<wcstring> &outputs) int exec_subshell(const wcstring &cmd, std::vector<wcstring> &outputs, bool apply_exit_status)
{ {
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
return exec_subshell_internal(cmd, &outputs); return exec_subshell_internal(cmd, &outputs, apply_exit_status);
} }
__warn_unused int exec_subshell(const wcstring &cmd) __warn_unused int exec_subshell(const wcstring &cmd, bool apply_exit_status)
{ {
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
return exec_subshell_internal(cmd, NULL); return exec_subshell_internal(cmd, NULL, apply_exit_status);
} }

4
exec.h
View file

@ -54,8 +54,8 @@ void exec(parser_t &parser, job_t *j);
\return the status of the last job to exit, or -1 if en error was encountered. \return the status of the last job to exit, or -1 if en error was encountered.
*/ */
__warn_unused int exec_subshell(const wcstring &cmd, std::vector<wcstring> &outputs); __warn_unused int exec_subshell(const wcstring &cmd, std::vector<wcstring> &outputs, bool preserve_exit_status);
__warn_unused int exec_subshell(const wcstring &cmd); __warn_unused int exec_subshell(const wcstring &cmd, bool preserve_exit_status);
/** /**

View file

@ -1352,7 +1352,7 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
const wcstring subcmd(paran_begin + 1, paran_end-paran_begin - 1); const wcstring subcmd(paran_begin + 1, paran_end-paran_begin - 1);
if (exec_subshell(subcmd, sub_res) == -1) if (exec_subshell(subcmd, sub_res, true /* do apply exit status */) == -1)
{ {
parser.error(CMDSUBST_ERROR, -1, L"Unknown error while evaulating command substitution"); parser.error(CMDSUBST_ERROR, -1, L"Unknown error while evaulating command substitution");
return 0; return 0;

View file

@ -112,7 +112,7 @@ void kill_add(const wcstring &str)
if (! cmd.empty()) if (! cmd.empty())
{ {
if (exec_subshell(cmd) == -1) if (exec_subshell(cmd, false /* do not apply exit status */) == -1)
{ {
/* /*
Do nothing on failiure Do nothing on failiure
@ -175,7 +175,7 @@ static void kill_check_x_buffer()
wcstring cmd = L"xsel -t 500 -b"; wcstring cmd = L"xsel -t 500 -b";
wcstring new_cut_buffer=L""; wcstring new_cut_buffer=L"";
wcstring_list_t list; wcstring_list_t list;
if (exec_subshell(cmd, list) != -1) if (exec_subshell(cmd, list, false /* do not apply exit status */) != -1)
{ {
for (i=0; i<list.size(); i++) for (i=0; i<list.size(); i++)

View file

@ -2900,7 +2900,8 @@ int parser_t::test_args(const wchar_t * buff, wcstring *out, const wchar_t *pre
} }
// helper type used in parser::test below // helper type used in parser::test below
struct block_info_t { struct block_info_t
{
int position; //tokenizer position int position; //tokenizer position
block_type_t type; //type of the block block_type_t type; //type of the block
int indentation; //indentation associated with the block int indentation; //indentation associated with the block

View file

@ -10,7 +10,7 @@
#include "exec.h" #include "exec.h"
#ifndef JOIN_THREADS_BEFORE_FORK #ifndef JOIN_THREADS_BEFORE_FORK
#define JOIN_THREADS_BEFORE_FORK 0 #define JOIN_THREADS_BEFORE_FORK 0
#endif #endif
/** The number of times to try to call fork() before giving up */ /** The number of times to try to call fork() before giving up */

View file

@ -328,7 +328,7 @@ void job_set_flag(job_t *j, unsigned int flag, int set)
int job_get_flag(const job_t *j, unsigned int flag) int job_get_flag(const job_t *j, unsigned int flag)
{ {
return !! (j->flags & flag); return !!(j->flags & flag);
} }
int job_signal(job_t *j, int signal) int job_signal(job_t *j, int signal)

View file

@ -692,7 +692,7 @@ void reader_write_title()
wcstring_list_t lst; wcstring_list_t lst;
proc_push_interactive(0); proc_push_interactive(0);
if (exec_subshell(title, lst) != -1) if (exec_subshell(title, lst, false /* do not apply exit status */) != -1)
{ {
size_t i; size_t i;
if (lst.size() > 0) if (lst.size() > 0)
@ -718,6 +718,9 @@ static void exec_prompt()
data->left_prompt_buff.clear(); data->left_prompt_buff.clear();
data->right_prompt_buff.clear(); data->right_prompt_buff.clear();
/* Do not allow the exit status of the prompts to leak through */
const bool apply_exit_status = false;
/* If we have any prompts, they must be run non-interactively */ /* If we have any prompts, they must be run non-interactively */
if (data->left_prompt.size() || data->right_prompt.size()) if (data->left_prompt.size() || data->right_prompt.size())
{ {
@ -726,9 +729,9 @@ static void exec_prompt()
if (! data->left_prompt.empty()) if (! data->left_prompt.empty())
{ {
wcstring_list_t prompt_list; wcstring_list_t prompt_list;
// status is ignored if (exec_subshell(data->left_prompt, prompt_list, apply_exit_status))
if (exec_subshell(data->left_prompt, prompt_list))
{ {
// returned status value is ignored
} }
for (size_t i = 0; i < prompt_list.size(); i++) for (size_t i = 0; i < prompt_list.size(); i++)
{ {
@ -741,8 +744,9 @@ static void exec_prompt()
{ {
wcstring_list_t prompt_list; wcstring_list_t prompt_list;
// status is ignored // status is ignored
if (exec_subshell(data->right_prompt, prompt_list)) if (exec_subshell(data->right_prompt, prompt_list, apply_exit_status))
{ {
// returned status value is ignored
} }
for (size_t i = 0; i < prompt_list.size(); i++) for (size_t i = 0; i < prompt_list.size(); i++)
{ {

View file

@ -1373,7 +1373,7 @@ void s_reset(screen_t *s, screen_reset_mode_t mode)
} }
else else
{ {
// draw in "bright black" (gray) // draw in "bright black" (gray)
abandon_line_string.append(L"\x1b[0m" //bright abandon_line_string.append(L"\x1b[0m" //bright
L"\x1b[30;1m"); //black L"\x1b[30;1m"); //black
} }

View file

@ -160,3 +160,20 @@ else
end; end;
set -U -e baz set -U -e baz
echo "Verify subcommand statuses"
echo (false) $status (true) $status (false) $status
echo "Verify that set passes through exit status, except when passed -n or -q or -e"
false ; set foo bar ; echo 1 $status # passthrough
true ; set foo bar ; echo 2 $status # passthrough
false ; set -q foo ; echo 3 $status # no passthrough
true ; set -q foo ; echo 4 $status # no passthrough
false ; set -n > /dev/null ; echo 5 $status # no passthrough
false ; set -e foo ; echo 6 $status # no passthrough
true ; set -e foo ; echo 7 $status # no passthrough
false ; set -h > /dev/null ; echo 8 $status # no passthrough
true ; set -NOT_AN_OPTION 2> /dev/null ; echo 9 $status # no passthrough
false ; set foo (echo A; true) ; echo 10 $status $foo
true ; set foo (echo B; false) ; echo 11 $status $foo
true

View file

@ -20,3 +20,17 @@ Test 19 pass
Test 20 pass Test 20 pass
Test 21 pass Test 21 pass
Test 22 pass Test 22 pass
Verify subcommand statuses
1 0 1
Verify that set passes through exit status, except when passed -n or -q or -e
1 1
2 0
3 0
4 0
5 0
6 0
7 1
8 0
9 1
10 0 A
11 1 B

View file

@ -336,7 +336,7 @@ static wcstring complete_get_desc_suffix_internal(const wcstring &suff)
wcstring_list_t lst; wcstring_list_t lst;
wcstring desc; wcstring desc;
if (exec_subshell(cmd, lst) != -1) if (exec_subshell(cmd, lst, false /* do not apply exit status */) != -1)
{ {
if (lst.size()>0) if (lst.size()>0)
{ {