mirror of
https://github.com/nushell/nushell.git
synced 2025-08-19 14:28:50 +02:00
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:
@@ -3,7 +3,7 @@ use nu_parser::{escape_for_script_arg, escape_quote_string, parse};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::StateWorkingSet,
|
||||
report_error,
|
||||
report_parse_error,
|
||||
};
|
||||
use nu_utils::stdout_write_all_and_flush;
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn parse_commandline_args(
|
||||
|
||||
let output = parse(&mut working_set, None, commandline_args.as_bytes(), false);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_parse_error(&working_set, err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ use nu_cli::{eval_config_contents, eval_source};
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error, report_error_new, Config, ParseError, PipelineData, Spanned,
|
||||
report_parse_error, report_shell_error, Config, ParseError, PipelineData, Spanned,
|
||||
};
|
||||
use nu_utils::{get_default_config, get_default_env};
|
||||
use std::{
|
||||
@@ -34,19 +34,17 @@ pub(crate) fn read_config_file(
|
||||
);
|
||||
// Load config startup file
|
||||
if let Some(file) = config_file {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
match engine_state.cwd_as_string(Some(stack)) {
|
||||
Ok(cwd) => {
|
||||
if let Ok(path) = canonicalize_with(&file.item, cwd) {
|
||||
eval_config_contents(path, engine_state, stack);
|
||||
} else {
|
||||
let e = ParseError::FileNotFound(file.item, file.span);
|
||||
report_error(&working_set, &e);
|
||||
report_parse_error(&StateWorkingSet::new(engine_state), &e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
report_error(&working_set, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
} else if let Some(mut config_path) = nu_path::config_dir() {
|
||||
@@ -168,11 +166,11 @@ pub(crate) fn read_default_env_file(engine_state: &mut EngineState, stack: &mut
|
||||
match engine_state.cwd(Some(stack)) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,11 +252,11 @@ fn eval_default_config(
|
||||
match engine_state.cwd(Some(stack)) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ use nu_cli::NuCompleter;
|
||||
use nu_parser::{flatten_block, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error_new, DeclId, ShellError, Span, Value, VarId,
|
||||
report_shell_error, DeclId, ShellError, Span, Value, VarId,
|
||||
};
|
||||
use reedline::Completer;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
@@ -55,7 +55,7 @@ fn read_in_file<'a>(
|
||||
let file = std::fs::read(file_path)
|
||||
.into_diagnostic()
|
||||
.unwrap_or_else(|e| {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::FileNotFoundCustom {
|
||||
msg: format!("Could not read file '{}': {:?}", file_path, e.to_string()),
|
||||
|
@@ -25,7 +25,7 @@ use nu_cmd_base::util::get_init_cwd;
|
||||
use nu_lsp::LanguageServer;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
engine::EngineState, report_error_new, ByteStream, PipelineData, ShellError, Span, Spanned,
|
||||
engine::EngineState, report_shell_error, ByteStream, PipelineData, ShellError, Span, Spanned,
|
||||
Value,
|
||||
};
|
||||
use nu_std::load_standard_library;
|
||||
@@ -67,7 +67,7 @@ fn main() -> Result<()> {
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
report_error_new(&engine_state, &err);
|
||||
report_shell_error(&engine_state, &err);
|
||||
}
|
||||
|
||||
// TODO: make this conditional in the future
|
||||
@@ -92,7 +92,7 @@ fn main() -> Result<()> {
|
||||
.unwrap_or(PathBuf::from(&xdg_config_home))
|
||||
.join("nushell")
|
||||
{
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
&engine_state,
|
||||
&ShellError::InvalidXdgConfig {
|
||||
xdg: xdg_config_home,
|
||||
@@ -164,7 +164,7 @@ fn main() -> Result<()> {
|
||||
let (args_to_nushell, script_name, args_to_script) = gather_commandline_args();
|
||||
let parsed_nu_cli_args = parse_commandline_args(&args_to_nushell.join(" "), &mut engine_state)
|
||||
.unwrap_or_else(|err| {
|
||||
report_error_new(&engine_state, &err);
|
||||
report_shell_error(&engine_state, &err);
|
||||
std::process::exit(1)
|
||||
});
|
||||
|
||||
|
34
src/run.rs
34
src/run.rs
@@ -10,7 +10,7 @@ use nu_cli::read_plugin_file;
|
||||
use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl, EvaluateCommandsOpts};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
report_error_new, PipelineData, Spanned,
|
||||
report_shell_error, PipelineData, Spanned,
|
||||
};
|
||||
use nu_utils::perf;
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(crate) fn run_commands(
|
||||
engine_state.generate_nu_constant();
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
if let Err(err) = evaluate_commands(
|
||||
let result = evaluate_commands(
|
||||
commands,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
@@ -95,11 +95,13 @@ pub(crate) fn run_commands(
|
||||
error_style: parsed_nu_cli_args.error_style,
|
||||
no_newline: parsed_nu_cli_args.no_newline.is_some(),
|
||||
},
|
||||
) {
|
||||
report_error_new(engine_state, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
);
|
||||
perf!("evaluate_commands", start_time, use_color);
|
||||
|
||||
if let Err(err) = result {
|
||||
report_shell_error(engine_state, &err);
|
||||
std::process::exit(err.exit_code());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_file(
|
||||
@@ -158,29 +160,19 @@ pub(crate) fn run_file(
|
||||
engine_state.generate_nu_constant();
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
if let Err(err) = evaluate_file(
|
||||
let result = evaluate_file(
|
||||
script_name,
|
||||
&args_to_script,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
input,
|
||||
) {
|
||||
report_error_new(engine_state, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
);
|
||||
perf!("evaluate_file", start_time, use_color);
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
let last_exit_code = stack.get_env_var(&*engine_state, "LAST_EXIT_CODE");
|
||||
if let Some(last_exit_code) = last_exit_code {
|
||||
let value = last_exit_code.as_int();
|
||||
if let Ok(value) = value {
|
||||
if value != 0 {
|
||||
std::process::exit(value as i32);
|
||||
}
|
||||
}
|
||||
if let Err(err) = result {
|
||||
report_shell_error(engine_state, &err);
|
||||
std::process::exit(err.exit_code());
|
||||
}
|
||||
perf!("get exit code", start_time, use_color);
|
||||
}
|
||||
|
||||
pub(crate) fn run_repl(
|
||||
|
Reference in New Issue
Block a user