nu_cmd_lang/core_commands/
ignore.rs1use nu_engine::command_prelude::*;
2#[cfg(feature = "os")]
3use nu_protocol::{
4 ByteStream, Signals,
5 process::{ChildPipe, check_ok},
6 shell_error::io::IoError,
7 write_all_and_flush,
8};
9use nu_protocol::{OutDest, engine::StateWorkingSet};
10#[cfg(feature = "os")]
11use std::{
12 io::{self, Cursor},
13 thread,
14};
15
16#[derive(Clone)]
17pub struct Ignore;
18
19impl Command for Ignore {
20 fn name(&self) -> &str {
21 "ignore"
22 }
23
24 fn description(&self) -> &str {
25 "Ignore selected output streams from the previous command in the pipeline."
26 }
27
28 fn signature(&self) -> nu_protocol::Signature {
29 Signature::build("ignore")
30 .input_output_types(vec![(Type::Any, Type::Any)])
31 .switch(
32 "stderr",
33 "Consume all stderr output and allow stdout output through.",
34 Some('e'),
35 )
36 .switch(
37 "stdout",
38 "Consume all stdout output and allow stderr output through.",
39 Some('o'),
40 )
41 .switch(
42 "show-errors",
43 "Allow errors through and set $env.LAST_EXIT_CODE (internal failures use 1).",
44 Some('x'),
45 )
46 .category(Category::Core)
47 }
48
49 fn search_terms(&self) -> Vec<&str> {
50 vec!["silent", "quiet", "out-null"]
51 }
52
53 fn is_const(&self) -> bool {
54 true
55 }
56
57 fn run(
58 &self,
59 engine_state: &EngineState,
60 stack: &mut Stack,
61 call: &Call,
62 input: PipelineData,
63 ) -> Result<PipelineData, ShellError> {
64 let consume_stderr = call.has_flag(engine_state, stack, "stderr")?;
65 let consume_stdout = call.has_flag(engine_state, stack, "stdout")? || !consume_stderr;
66 let show_errors = call.has_flag(engine_state, stack, "show-errors")?;
67 let span = call.head;
68
69 match input {
70 PipelineData::ByteStream(stream, metadata) => {
71 #[cfg(feature = "os")]
72 {
73 let stream_type = stream.type_();
74 match stream.into_child() {
75 Ok(child) => handle_external_child(
76 engine_state,
77 stack,
78 span,
79 child,
80 stream_type,
81 metadata,
82 consume_stdout,
83 consume_stderr,
84 show_errors,
85 ),
86 Err(stream) => {
87 if !consume_stdout {
88 if show_errors {
89 stack.set_last_exit_code(0, span);
90 }
91 return Ok(PipelineData::byte_stream(stream, metadata));
92 }
93
94 match stream.drain() {
95 Ok(()) => {
96 if show_errors {
97 stack.set_last_exit_code(0, span);
98 }
99 Ok(PipelineData::empty())
100 }
101 Err(err) => {
102 if show_errors {
103 stack.set_last_exit_code(1, span);
104 }
105 Err(err)
106 }
107 }
108 }
109 }
110 }
111 #[cfg(not(feature = "os"))]
112 {
113 if !consume_stdout {
114 return Ok(PipelineData::byte_stream(stream, metadata));
115 }
116 match stream.drain() {
117 Ok(()) => Ok(PipelineData::empty()),
118 Err(err) => Err(err),
119 }
120 }
121 }
122 PipelineData::Value(Value::Error { error, .. }, _) => {
123 if consume_stderr {
124 if show_errors {
125 stack.set_last_exit_code(1, span);
126 Err(*error)
127 } else {
128 Ok(PipelineData::empty())
129 }
130 } else {
131 if show_errors {
132 stack.set_last_exit_code(1, span);
133 }
134 Err(*error)
135 }
136 }
137 input => {
138 if !consume_stdout {
139 if show_errors {
140 stack.set_last_exit_code(0, span);
141 }
142 return Ok(input);
143 }
144
145 match input.drain() {
146 Ok(()) => {
147 if show_errors {
148 stack.set_last_exit_code(0, span);
149 }
150 Ok(PipelineData::empty())
151 }
152 Err(err) => {
153 if show_errors {
154 stack.set_last_exit_code(1, span);
155 }
156 Err(err)
157 }
158 }
159 }
160 }
161 }
162
163 fn run_const(
164 &self,
165 working_set: &StateWorkingSet,
166 call: &Call,
167 input: PipelineData,
168 ) -> Result<PipelineData, ShellError> {
169 let consume_stderr = call.has_flag_const(working_set, "stderr")?;
170 let consume_stdout = call.has_flag_const(working_set, "stdout")? || !consume_stderr;
171
172 if consume_stderr && matches!(&input, PipelineData::Value(Value::Error { .. }, _)) {
173 return Ok(PipelineData::empty());
174 }
175
176 if consume_stdout {
177 input.drain()?;
178 Ok(PipelineData::empty())
179 } else {
180 Ok(input)
181 }
182 }
183
184 fn examples(&self) -> Vec<Example<'_>> {
185 vec![
186 Example {
187 description: "Ignore all stdout output (default behavior).",
188 example: "echo done | ignore",
189 result: Some(Value::nothing(Span::test_data())),
190 },
191 Example {
192 description: "Consume stderr and allow stdout through.",
193 example: "echo done | ignore --stderr",
194 result: Some(Value::test_string("done")),
195 },
196 Example {
197 description: "Consume stdout while keeping stderr visible.",
198 example: "$'done' | ignore --stdout",
199 result: Some(Value::nothing(Span::test_data())),
200 },
201 Example {
202 description: "Show internal errors and read the resulting exit code.",
203 example: "try { error make {msg: 'boom'} | ignore --show-errors } catch { $env.LAST_EXIT_CODE }",
204 result: Some(Value::test_int(1)),
205 },
206 ]
207 }
208
209 fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
210 (Some(OutDest::PipeSeparate), Some(OutDest::PipeSeparate))
211 }
212}
213
214#[cfg(feature = "os")]
215#[expect(clippy::too_many_arguments)]
216fn handle_external_child(
218 engine_state: &EngineState,
219 stack: &mut Stack,
220 span: Span,
221 mut child: nu_protocol::process::ChildProcess,
222 stream_type: nu_protocol::ByteStreamType,
223 metadata: Option<nu_protocol::PipelineMetadata>,
224 consume_stdout: bool,
225 consume_stderr: bool,
226 show_errors: bool,
227) -> Result<PipelineData, ShellError> {
228 child.ignore_error(!show_errors);
229
230 if !consume_stdout && !show_errors {
232 if consume_stderr && let Some(stderr) = child.stderr.take() {
233 consume_child_pipe_on_thread(stderr, span)?;
234 }
235
236 return Ok(PipelineData::byte_stream(
237 ByteStream::child(child, span),
238 metadata,
239 ));
240 }
241
242 let output = match child.wait_with_output() {
245 Ok(output) => output,
246 Err(err) => {
247 if show_errors {
248 stack.set_last_exit_code(1, span);
249 }
250 return Err(err);
251 }
252 };
253
254 if !consume_stderr && let Some(stderr) = output.stderr.as_deref() {
255 write_to_out_dest(stderr, stack.stderr(), span, false, engine_state.signals())?;
256 }
257
258 let stdout = output.stdout.unwrap_or_default();
259 let exit_status = output.exit_status;
260
261 if show_errors {
262 let exit_code = exit_status.code();
263 stack.set_last_exit_code(exit_code, span);
264
265 if let Err(err) = check_ok(exit_status, false, span) {
266 if !consume_stdout && !stdout.is_empty() {
267 write_to_out_dest(&stdout, stack.stdout(), span, true, engine_state.signals())?;
268 }
269 return Err(err);
270 }
271 }
272
273 if !consume_stdout && !stdout.is_empty() {
274 let stream = ByteStream::read(
275 Cursor::new(stdout),
276 span,
277 engine_state.signals().clone(),
278 stream_type,
279 );
280 return Ok(PipelineData::byte_stream(stream, metadata));
281 }
282
283 Ok(PipelineData::empty())
284}
285
286#[cfg(feature = "os")]
287fn write_to_out_dest(
289 bytes: &[u8],
290 out_dest: &OutDest,
291 span: Span,
292 stdout_fallback: bool,
293 signals: &Signals,
294) -> Result<(), ShellError> {
295 if bytes.is_empty() {
296 return Ok(());
297 }
298
299 match out_dest {
300 OutDest::Null => Ok(()),
301 OutDest::File(file) => {
302 let mut file = file.as_ref();
303 write_all_and_flush(bytes, &mut file, "file", Some(span), signals)
304 }
305 OutDest::Print
306 | OutDest::Inherit
307 | OutDest::Pipe
308 | OutDest::PipeSeparate
309 | OutDest::Value => {
310 if stdout_fallback {
311 let mut stdout = io::stdout();
312 write_all_and_flush(bytes, &mut stdout, "stdout", Some(span), signals)
313 } else {
314 let mut stderr = io::stderr();
315 write_all_and_flush(bytes, &mut stderr, "stderr", Some(span), signals)
316 }
317 }
318 }
319}
320
321#[cfg(feature = "os")]
322fn consume_child_pipe_on_thread(pipe: ChildPipe, span: Span) -> Result<(), ShellError> {
325 thread::Builder::new()
326 .name("ignore stderr consumer".into())
327 .spawn(move || {
328 let mut sink = io::sink();
329 match pipe {
330 ChildPipe::Pipe(mut pipe) => io::copy(&mut pipe, &mut sink),
331 ChildPipe::Tee(mut tee) => io::copy(&mut tee, &mut sink),
332 }?;
333 Ok::<(), io::Error>(())
334 })
335 .map_err(|err| ShellError::Io(IoError::new(err, span, None)))?;
336 Ok(())
337}
338
339#[cfg(test)]
340mod test {
341 #[test]
342 fn test_examples() -> nu_test_support::Result {
343 use super::Ignore;
344 nu_test_support::test().examples(Ignore)
345 }
346}