pipe binary data to external commands (#8058)

Fixes #7615 

# Description

When calling external commands, we create a table from the pipeline data
to handle external commands expecting paginated input. When a binary
value is made into a table, we convert the vector of bytes representing
the binary bytes into a pretty formatted string. This results in the
pretty formatted string being sent to external commands instead of the
actual binary bytes. By checking whether the stdout of the call is being
redirected, we can decide whether to send the raw binary bytes or the
pretty formatted output when creating a table command.

# User-Facing Changes

When passing binary values to external commands, the external command
will receive the actual bytes instead of the pretty printed string. Use
cases that don't involve piping a binary value into an external command
are unchanged.


![new_behavior](https://user-images.githubusercontent.com/32406734/218349172-24cd12f2-d563-4957-bdf1-6aa804b174b2.png)

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

cargo fmt --all -- --check to check standard code formatting (cargo fmt
--all applies these changes)
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect to check that you're using the standard code
style
    cargo test --workspace to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
Henry Jetmundsen 2023-02-24 12:39:52 -08:00 committed by GitHub
parent 85bfdca578
commit 253b223e65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 27 additions and 10 deletions

View File

@ -154,12 +154,10 @@ pub(crate) fn print_table_or_error(
if command.get_block_id().is_some() {
print_or_exit(pipeline_data, engine_state, config);
} else {
let table = command.run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
pipeline_data,
);
// The final call on table command, it's ok to set redirect_output to false.
let mut call = Call::new(Span::new(0, 0));
call.redirect_stdout = false;
let table = command.run(engine_state, stack, &call, pipeline_data);
match table {
Ok(table) => {

View File

@ -241,12 +241,14 @@ fn handle_table_command(
PipelineData::ExternalStream { .. } => Ok(input),
PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(
Box::new(if call.redirect_stdout {
vec![Ok(val)].into_iter()
} else {
vec![Ok(format!("{}\n", nu_pretty_hex::pretty_hex(&val))
.as_bytes()
.to_vec())]
.into_iter(),
),
.into_iter()
}),
ctrlc,
call.head,
None,

View File

@ -213,6 +213,15 @@ fn external_command_not_expand_tilde_with_quotes() {
)
}
#[test]
fn external_command_receives_raw_binary_data() {
Playground::setup("external command receives raw binary data", |dirs, _| {
let actual =
nu!(cwd: dirs.test(), pipeline(r#"0x[deadbeef] | nu --testbin input_bytes_length"#));
assert_eq!(actual.out, r#"4"#);
})
}
#[cfg(windows)]
#[test]
fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() {

View File

@ -169,6 +169,7 @@ fn main() -> Result<()> {
"chop" => test_bins::chop(),
"repeater" => test_bins::repeater(),
"nu_repl" => test_bins::nu_repl(),
"input_bytes_length" => test_bins::input_bytes_length(),
_ => std::process::exit(1),
}
std::process::exit(0)

View File

@ -1,4 +1,4 @@
use std::io::{self, BufRead, Write};
use std::io::{self, BufRead, Read, Write};
use nu_cli::{eval_env_change_hook, eval_hook};
use nu_command::create_default_context;
@ -257,6 +257,13 @@ fn did_chop_arguments() -> bool {
false
}
pub fn input_bytes_length() {
let stdin = io::stdin();
let count = stdin.lock().bytes().count();
println!("{}", count);
}
fn args() -> Vec<String> {
// skip (--testbin bin_name args)
std::env::args().skip(2).collect()