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 (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 failiure
*/
/* Do nothing on failure */
}
}

View file

@ -215,7 +215,7 @@ wcstring builtin_help_get(parser_t &parser, const wchar_t *name)
wcstring out;
const wcstring name_esc = escape_string(name, 1);
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++)
{

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)
{
/**
Variables used for parsing the argument list
*/
static const struct woption
long_options[] =
/** Variables used for parsing the argument list */
const struct woption long_options[] =
{
{
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"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
}
}
;
{ 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"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";
@ -443,6 +406,8 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
int erase = 0, list = 0, unexport=0;
int universal = 0, query=0;
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
@ -474,10 +439,12 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
case 'e':
erase = 1;
preserve_incoming_failure_exit_status = false;
break;
case 'n':
list = 1;
preserve_incoming_failure_exit_status = false;
break;
case 'x':
@ -506,6 +473,7 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
case 'q':
query = 1;
preserve_incoming_failure_exit_status = false;
break;
case 'h':
@ -825,6 +793,8 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
free(dest);
if (retcode == STATUS_BUILTIN_OK && preserve_incoming_failure_exit_status)
retcode = incoming_exit_status;
return retcode;
}

View file

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

View file

@ -150,7 +150,7 @@ extern const wchar_t *program_name;
read( 0, &exit_read_buff, 1 ); \
exit_without_destructors( 1 ); \
} \
/**
Exit program at once, leaving an error message about running out of memory.

View file

@ -465,7 +465,7 @@ bool completer_t::condition_test(const wcstring &condition)
if (cached_entry == condition_cache.end())
{
/* 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;
}
else
@ -1007,7 +1007,7 @@ void completer_t::complete_cmd_desc(const wcstring &str)
since apropos is only called once.
*/
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
be shown, and the output is used as a prompt.
Please keep in mind that the function is executed by <a
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.
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.
\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
parameter.
A command substitution will not change the value of the <a
href='#variables-status'>status</a> variable outside of the command
substitution.
The exit status of the last run command substitution is available in the <a
href='#variables-status'>status</a> variable.
Only part of the output can be used, see <a href='#expand-index-range'>index
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
'-l', not making the variable local.
In assignment mode, set exits with an exit status of zero it the
variable assignments where sucessfully performed, with a non-zero exit
status otherwise. In query mode, the exit status is the number of
variables that where not found. In erase mode, set exits with a zero
exit status in case of 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.
In assignment mode, set exits with a non-zero exit status if variable
assignments could not be successfully performed. If the variable assignments
were performed, the exit status is unchanged. This allows simultaneous capture
of the output and exit status of a subcommand, e.g. <code>if set output
(command)</code>. In query mode, the exit status is the number of variables that
were not found. In erase mode, set exits with a zero exit status in case of
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
@ -85,3 +86,9 @@ variable did not exist.
<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
<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

@ -125,7 +125,7 @@ void exec_close(int fd)
int exec_pipe(int fd[2])
{
ASSERT_IS_MAIN_THREAD();
int res;
while ((res=pipe(fd)))
{
@ -621,11 +621,11 @@ void exec(parser_t &parser, job_t *j)
}
}
// This is a pipe that the "current" process in our loop below reads from
// Only pipe_read->pipe_fd[0] is used
shared_ptr<io_pipe_t> pipe_read(new io_pipe_t(0, true));
// This is the pipe that the "current" process in our loop below writes to
shared_ptr<io_pipe_t> pipe_write(new io_pipe_t(1, false));
@ -687,7 +687,7 @@ void exec(parser_t &parser, job_t *j)
set_child_group(j, &keepalive, 0);
}
}
/*
This loop loops over every process_t in the job, starting it as
appropriate. This turns out to be rather complex, since a
@ -695,17 +695,17 @@ void exec(parser_t &parser, job_t *j)
The loop also has to handle pipelining between the jobs.
*/
/* We can have up to three pipes "in flight" at a time:
1. The pipe the current process should read from (courtesy of the previous process)
2. The pipe that the current process should write to
3. The pipe that the next process should read from (courtesy of us)
We are careful to set these to -1 when closed, so if we exit the loop abruptly, we can still close them.
*/
int pipe_current_read = -1, pipe_current_write = -1, pipe_next_read = -1;
for (process_t *p=j->first_process; p; p = p->next)
{
@ -713,10 +713,10 @@ void exec(parser_t &parser, job_t *j)
assert(pipe_current_read == -1);
pipe_current_read = pipe_next_read;
pipe_next_read = -1;
/* Record the current read in pipe_read */
pipe_read->pipe_fd[0] = pipe_current_read;
/* See if we need a pipe */
const bool pipes_to_next_command = (p->next != NULL);
@ -760,15 +760,15 @@ void exec(parser_t &parser, job_t *j)
job_mark_process_as_failed(j, p);
break;
}
// This tells the redirection about the fds, but the redirection does not close them
memcpy(pipe_write->pipe_fd, local_pipe, sizeof(int)*2);
// Record our pipes
// The fds should be negative to indicate that we aren't overwriting an fd we failed to close
assert(pipe_current_write == -1);
pipe_current_write = local_pipe[1];
assert(pipe_next_read == -1);
pipe_next_read = local_pipe[0];
}
@ -838,7 +838,7 @@ void exec(parser_t &parser, job_t *j)
j->io.push_back(io_buffer);
}
}
if (! exec_error)
{
internal_exec_helper(parser, def.c_str(), TOP, j->io);
@ -865,7 +865,7 @@ void exec(parser_t &parser, job_t *j)
j->io.push_back(io_buffer);
}
}
if (! exec_error)
{
internal_exec_helper(parser, p->argv0(), TOP, j->io);
@ -1389,7 +1389,7 @@ void exec(parser_t &parser, job_t *j)
exec_close(pipe_current_read);
pipe_current_read = -1;
}
/* Close the write end too, since the curent child subprocess already has a copy of it. */
if (pipe_current_write >= 0)
{
@ -1397,7 +1397,7 @@ void exec(parser_t &parser, job_t *j)
pipe_current_write = -1;
}
}
/* Clean up any file descriptors we left open */
if (pipe_current_read >= 0)
exec_close(pipe_current_read);
@ -1405,7 +1405,7 @@ void exec(parser_t &parser, job_t *j)
exec_close(pipe_current_write);
if (pipe_next_read >= 0)
exec_close(pipe_next_read);
/* The keepalive process is no longer needed, so we terminate it with extreme prejudice */
if (needs_keepalive)
{
@ -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();
int prev_subshell = is_subshell;
int status, prev_status;
const int prev_status = proc_get_last_status();
char sep=0;
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
{
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;
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
if (io_buffer.get() == NULL)
{
status = -1;
}
else
const shared_ptr<io_buffer_t> io_buffer(io_buffer_t::create(0));
if (io_buffer.get() != NULL)
{
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();
}
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;
@ -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();
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();
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.
*/
__warn_unused int exec_subshell(const wcstring &cmd, std::vector<wcstring> &outputs);
__warn_unused int exec_subshell(const wcstring &cmd);
__warn_unused int exec_subshell(const wcstring &cmd, std::vector<wcstring> &outputs, bool preserve_exit_status);
__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);
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");
return 0;

View file

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

View file

@ -1203,7 +1203,7 @@ bool parser_t::job_remove(job_t *j)
void parser_t::job_promote(job_t *job)
{
signal_block();
job_list_t::iterator loc = std::find(my_job_list.begin(), my_job_list.end(), job);
assert(loc != my_job_list.end());
@ -2900,11 +2900,12 @@ int parser_t::test_args(const wchar_t * buff, wcstring *out, const wchar_t *pre
}
// helper type used in parser::test below
struct block_info_t {
struct block_info_t
{
int position; //tokenizer position
block_type_t type; //type of the block
int indentation; //indentation associated with the block
bool has_had_case; //if we are a switch, whether we've encountered a case
};
@ -2922,7 +2923,7 @@ int parser_t::test(const wchar_t *buff, int *block_level, wcstring *out, const w
tokenizer_t * const previous_tokenizer=current_tokenizer;
const int previous_pos=current_tokenizer_pos;
// These are very nearly stacks, but sometimes we have to inspect non-top elements (e.g. return)
std::vector<struct block_info_t> block_infos;
int indentation_sum = 0; //sum of indentation in block_infos
@ -3040,7 +3041,7 @@ int parser_t::test(const wchar_t *buff, int *block_level, wcstring *out, const w
{
tok_next(&tok);
tok_set_pos(&tok, mark);
/* Test that end is not used when not inside any block */
if (block_infos.empty())
{
@ -3060,7 +3061,7 @@ int parser_t::test(const wchar_t *buff, int *block_level, wcstring *out, const w
{
indentation_sum -= block_infos.back().indentation;
block_infos.pop_back();
}
}

View file

@ -10,7 +10,7 @@
#include "exec.h"
#ifndef JOIN_THREADS_BEFORE_FORK
#define JOIN_THREADS_BEFORE_FORK 0
#define JOIN_THREADS_BEFORE_FORK 0
#endif
/** 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)
{
return !! (j->flags & flag);
return !!(j->flags & flag);
}
int job_signal(job_t *j, int signal)

View file

@ -692,7 +692,7 @@ void reader_write_title()
wcstring_list_t lst;
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;
if (lst.size() > 0)
@ -718,6 +718,9 @@ static void exec_prompt()
data->left_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 (data->left_prompt.size() || data->right_prompt.size())
{
@ -726,9 +729,9 @@ static void exec_prompt()
if (! data->left_prompt.empty())
{
wcstring_list_t prompt_list;
// status is ignored
if (exec_subshell(data->left_prompt, prompt_list))
if (exec_subshell(data->left_prompt, prompt_list, apply_exit_status))
{
// returned status value is ignored
}
for (size_t i = 0; i < prompt_list.size(); i++)
{
@ -741,8 +744,9 @@ static void exec_prompt()
{
wcstring_list_t prompt_list;
// 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++)
{
@ -1048,10 +1052,10 @@ static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<c
wcstring msg;
wcstring prefix_esc;
char *foo;
shared_ptr<io_buffer_t> in(io_buffer_t::create(true));
shared_ptr<io_buffer_t> out(io_buffer_t::create(false));
// The above may fail e.g. if we have too many open fds
if (in.get() == NULL || out.get() == NULL)
return;
@ -1140,7 +1144,7 @@ static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<c
free(foo);
out->fd = 4;
term_donate();
parser_t &parser = parser_t::principal_parser();
io_chain_t io_chain;

View file

@ -1373,20 +1373,20 @@ void s_reset(screen_t *s, screen_reset_mode_t mode)
}
else
{
// draw in "bright black" (gray)
// draw in "bright black" (gray)
abandon_line_string.append(L"\x1b[0m" //bright
L"\x1b[30;1m"); //black
}
abandon_line_string.push_back(omitted_newline_char);
abandon_line_string.append(L"\x1b[0m"); //normal text ANSI escape sequence
abandon_line_string.append(screen_width - non_space_width, L' ');
}
abandon_line_string.push_back(L'\r');
// now we are certainly on a new line. But we may have dropped the omitted newline char on it. So append enough spaces to overwrite the omitted newline char, and then
abandon_line_string.append(non_space_width, L' ');
abandon_line_string.push_back(L'\r');
const std::string narrow_abandon_line_string = wcs2string(abandon_line_string);
write_loop(STDOUT_FILENO, narrow_abandon_line_string.c_str(), narrow_abandon_line_string.size());
s->actual.cursor.x = 0;

View file

@ -160,3 +160,20 @@ else
end;
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 21 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 desc;
if (exec_subshell(cmd, lst) != -1)
if (exec_subshell(cmd, lst, false /* do not apply exit status */) != -1)
{
if (lst.size()>0)
{