Skip to content

Commit ee5332d

Browse files
committed
fix: --print0 now works with --exec
If you use --print0 with --exec, it will now print a \0 between each set of commands run. That is, between the output for the commands run for each found item. Fixes: #1797
1 parent ba2dde8 commit ee5332d

File tree

7 files changed

+49
-24
lines changed

7 files changed

+49
-24
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
## Bugfixes
77

8+
- `--print0` combined with `--exec` will now print a `\0` between the output of each entry. Note that if there are multiple instances
9+
of `--exec`, the `\0` will be between each _set_ of commands, _not_ between each individual command run. Fixes #1797.
10+
811

912
## Changes
1013

doc/fd.1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ option.
449449

450450
If no placeholder is present, an implicit "{}" at the end is assumed.
451451

452+
If --print0 is also given, then a null character (\\0) will be printed between the output for each found entry.
453+
This allows another program to easily distinguish the output for each file if the command(s) produce multiple lines.
454+
452455
Examples:
453456

454457
- find all *.zip files and unzip them:
@@ -462,6 +465,10 @@ Examples:
462465
- Convert all *.jpg files to *.png files:
463466

464467
fd -e jpg -x convert {} {.}.png
468+
469+
- Run stat for each *.txt file, separated by null characters
470+
471+
fd -0 -e txt -x stat
465472
.RE
466473
.TP
467474
.BI "\-X, \-\-exec-batch " command

src/exec/command.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::io;
22
use std::io::Write;
3-
use std::sync::Mutex;
43

54
use argmax::Command;
65

@@ -11,15 +10,15 @@ struct Outputs {
1110
stdout: Vec<u8>,
1211
stderr: Vec<u8>,
1312
}
14-
struct OutputBuffer<'a> {
15-
output_permission: &'a Mutex<()>,
13+
pub struct OutputBuffer {
14+
null_separator: bool,
1615
outputs: Vec<Outputs>,
1716
}
1817

19-
impl<'a> OutputBuffer<'a> {
20-
fn new(output_permission: &'a Mutex<()>) -> Self {
18+
impl OutputBuffer {
19+
pub fn new(null_separator: bool) -> Self {
2120
Self {
22-
output_permission,
21+
null_separator,
2322
outputs: Vec::new(),
2423
}
2524
}
@@ -29,34 +28,40 @@ impl<'a> OutputBuffer<'a> {
2928
}
3029

3130
fn write(self) {
32-
// avoid taking the lock if there is nothing to do
33-
if self.outputs.is_empty() {
31+
// Avoid taking the lock if there is nothing to do.
32+
// If null_separator is true, then we still need to write the
33+
// null separator, because the output may have been written directly
34+
// to stdout
35+
if self.outputs.is_empty() && !self.null_separator {
3436
return;
3537
}
36-
// While this lock is active, this thread will be the only thread allowed
37-
// to write its outputs.
38-
let _lock = self.output_permission.lock().unwrap();
3938

4039
let stdout = io::stdout();
4140
let stderr = io::stderr();
4241

42+
// While we hold these locks, only this thread will be able
43+
// to write its outputs.
4344
let mut stdout = stdout.lock();
4445
let mut stderr = stderr.lock();
4546

4647
for output in self.outputs.iter() {
4748
let _ = stdout.write_all(&output.stdout);
4849
let _ = stderr.write_all(&output.stderr);
4950
}
51+
if self.null_separator {
52+
// If null_separator is enabled, then we should write a \0 at the end
53+
// of the output for this entry
54+
let _ = stdout.write_all(b"\0");
55+
}
5056
}
5157
}
5258

5359
/// Executes a command.
5460
pub fn execute_commands<I: Iterator<Item = io::Result<Command>>>(
5561
cmds: I,
56-
out_perm: &Mutex<()>,
62+
mut output_buffer: OutputBuffer,
5763
enable_output_buffering: bool,
5864
) -> ExitCode {
59-
let mut output_buffer = OutputBuffer::new(out_perm);
6065
for result in cmds {
6166
let mut cmd = match result {
6267
Ok(cmd) => cmd,

src/exec/job.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::sync::Mutex;
2-
31
use crate::config::Config;
42
use crate::error::print_error;
53
use crate::exit_codes::{merge_exitcodes, ExitCode};
@@ -13,7 +11,6 @@ use super::CommandSet;
1311
pub fn job(
1412
results: impl IntoIterator<Item = WorkerResult>,
1513
cmd: &CommandSet,
16-
out_perm: &Mutex<()>,
1714
config: &Config,
1815
) -> ExitCode {
1916
// Output should be buffered when only running a single thread
@@ -37,7 +34,7 @@ pub fn job(
3734
let code = cmd.execute(
3835
dir_entry.stripped_path(config),
3936
config.path_separator.as_deref(),
40-
out_perm,
37+
config.null_separator,
4138
buffer_output,
4239
);
4340
ret = merge_exitcodes([ret, code]);

src/exec/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use std::io;
66
use std::iter;
77
use std::path::{Path, PathBuf};
88
use std::process::Stdio;
9-
use std::sync::Mutex;
109

1110
use anyhow::{bail, Result};
1211
use argmax::Command;
1312

13+
use crate::exec::command::OutputBuffer;
1414
use crate::exit_codes::{merge_exitcodes, ExitCode};
1515
use crate::fmt::{FormatTemplate, Token};
1616

@@ -80,14 +80,14 @@ impl CommandSet {
8080
&self,
8181
input: &Path,
8282
path_separator: Option<&str>,
83-
out_perm: &Mutex<()>,
83+
null_separator: bool,
8484
buffer_output: bool,
8585
) -> ExitCode {
8686
let commands = self
8787
.commands
8888
.iter()
8989
.map(|c| c.generate(input, path_separator));
90-
execute_commands(commands, out_perm, buffer_output)
90+
execute_commands(commands, OutputBuffer::new(null_separator), buffer_output)
9191
}
9292

9393
pub fn execute_batch<I>(&self, paths: I, limit: usize, path_separator: Option<&str>) -> ExitCode

src/walk.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,6 @@ impl WorkerState {
413413
if cmd.in_batch_mode() {
414414
exec::batch(rx.into_iter().flatten(), cmd, config)
415415
} else {
416-
let out_perm = Mutex::new(());
417-
418416
thread::scope(|scope| {
419417
// Each spawned job will store its thread handle in here.
420418
let threads = config.threads;
@@ -423,8 +421,8 @@ impl WorkerState {
423421
let rx = rx.clone();
424422

425423
// Spawn a job thread that will listen for and execute inputs.
426-
let handle = scope
427-
.spawn(|| exec::job(rx.into_iter().flatten(), cmd, &out_perm, config));
424+
let handle =
425+
scope.spawn(|| exec::job(rx.into_iter().flatten(), cmd, config));
428426

429427
// Push the handle of the spawned thread into the vector for later joining.
430428
handles.push(handle);

tests/tests.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,21 @@ fn test_exec_multi() {
18261826
);
18271827
}
18281828

1829+
#[cfg(not(windows))]
1830+
#[test]
1831+
fn test_exec_nulls() {
1832+
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
1833+
te.assert_output(
1834+
&["foo", "--print0", "--exec", "printf", "p=%s"],
1835+
"p=./a.fooNULL
1836+
p=./one/b.fooNULL
1837+
p=./one/two/C.Foo2NULL
1838+
p=./one/two/c.fooNULL
1839+
p=./one/two/three/d.fooNULL
1840+
p=./one/two/three/directory_fooNULL",
1841+
);
1842+
}
1843+
18291844
#[test]
18301845
fn test_exec_batch() {
18311846
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);

0 commit comments

Comments
 (0)