forked from extern/nushell
Error on non-zero exit statuses (#13515)
# Description This PR makes it so that non-zero exit codes and termination by signal are treated as a normal `ShellError`. Currently, these are silent errors. That is, if an external command fails, then it's code block is aborted, but the parent block can sometimes continue execution. E.g., see #8569 and this example: ```nushell [1 2] | each { ^false } ``` Before this would give: ``` ╭───┬──╮ │ 0 │ │ │ 1 │ │ ╰───┴──╯ ``` Now, this shows an error: ``` Error: nu:🐚:eval_block_with_input × Eval block failed with pipeline input ╭─[entry #1:1:2] 1 │ [1 2] | each { ^false } · ┬ · ╰── source value ╰──── Error: nu:🐚:non_zero_exit_code × External command had a non-zero exit code ╭─[entry #1:1:17] 1 │ [1 2] | each { ^false } · ──┬── · ╰── exited with code 1 ╰──── ``` This PR fixes #12874, fixes #5960, fixes #10856, and fixes #5347. This PR also partially addresses #10633 and #10624 (only the last command of a pipeline is currently checked). It looks like #8569 is already fixed, but this PR will make sure it is definitely fixed (fixes #8569). # User-Facing Changes - Non-zero exit codes and termination by signal now cause an error to be thrown. - The error record value passed to a `catch` block may now have an `exit_code` column containing the integer exit code if the error was due to an external command. - Adds new config values, `display_errors.exit_code` and `display_errors.termination_signal`, which determine whether an error message should be printed in the respective error cases. For non-interactive sessions, these are set to `true`, and for interactive sessions `display_errors.exit_code` is false (via the default config). # Tests Added a few tests. # After Submitting - Update docs and book. - Future work: - Error if other external commands besides the last in a pipeline exit with a non-zero exit code. Then, deprecate `do -c` since this will be the default behavior everywhere. - Add a better mechanism for exit codes and deprecate `$env.LAST_EXIT_CODE` (it's buggy).
This commit is contained in:
@ -27,7 +27,7 @@ use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::{
|
||||
config::NuCursorShape,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||
report_shell_error, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||
Value,
|
||||
};
|
||||
use nu_utils::{
|
||||
@ -88,7 +88,7 @@ pub fn evaluate_repl(
|
||||
let start_time = std::time::Instant::now();
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
perf!("translate env vars", start_time, use_color);
|
||||
|
||||
@ -98,7 +98,7 @@ pub fn evaluate_repl(
|
||||
Value::string("0823", Span::unknown()),
|
||||
);
|
||||
|
||||
unique_stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||
unique_stack.set_last_exit_code(0, Span::unknown());
|
||||
|
||||
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||
@ -286,7 +286,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
// Check whether $env.NU_USE_IR is set, so that the user can change it in the REPL
|
||||
// Temporary while IR eval is optional
|
||||
@ -302,7 +302,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf!("pre-prompt hook", start_time, use_color);
|
||||
@ -312,7 +312,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// fire the "env_change" hook
|
||||
let env_change = engine_state.get_config().hooks.env_change.clone();
|
||||
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
||||
report_error_new(engine_state, &error)
|
||||
report_shell_error(engine_state, &error)
|
||||
}
|
||||
perf!("env-change hook", start_time, use_color);
|
||||
|
||||
@ -386,7 +386,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
trace!("adding menus");
|
||||
line_editor =
|
||||
add_menus(line_editor, engine_reference, &stack_arc, config).unwrap_or_else(|e| {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
Reedline::create()
|
||||
});
|
||||
|
||||
@ -506,7 +506,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
&hook,
|
||||
"pre_execution",
|
||||
) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -808,7 +808,7 @@ fn do_auto_cd(
|
||||
) {
|
||||
let path = {
|
||||
if !path.exists() {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::DirectoryNotFound {
|
||||
dir: path.to_string_lossy().to_string(),
|
||||
@ -820,7 +820,7 @@ fn do_auto_cd(
|
||||
};
|
||||
|
||||
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::IOError {
|
||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
||||
@ -834,7 +834,7 @@ fn do_auto_cd(
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
if let Err(err) = stack.set_cwd(&path) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
return;
|
||||
};
|
||||
let cwd = Value::string(cwd, span);
|
||||
@ -867,7 +867,7 @@ fn do_auto_cd(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(last_shell as i64, span),
|
||||
);
|
||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||
stack.set_last_exit_code(0, Span::unknown());
|
||||
}
|
||||
|
||||
///
|
||||
@ -1141,7 +1141,7 @@ fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedl
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user