0

The following question likely does not relate specifically to Vim. I use a Vim example, as this is where I encounter the issue.

Working on Ubuntu, I often open multiple files in Vim using tab pages:

$ vim --help | grep tab
   -p[N]        Open N tab pages (default: one for each file)

I also use find with xargs and grep -l to obtain a list of files.

find . -type f -name "*.txt" | xargs grep -l "zod"

I can then quickly review the files output by find in vim:

vim -p `find . -type f -name "*.txt" | xargs grep -l "zod"`

The earlier grep command would fail if there are spaces in the files or directories, so -print0 can be added to the arguments to find; and -0 can be added to the arguments to xargs. The following creates a MWE set of sample files and directories including spaces:

echo zod > xx.txt && echo zod > 'x x.txt' && mkdir aa && echo zod > aa/xx.txt && echo zod > 'aa/x x.txt' && mkdir 'a a' && echo zod > 'a a/xx.txt' && echo zod > 'a a/x x.txt'

The following will then list the 6 text files we expect:

find . -type f -name "*.txt" -print0 | xargs -0 grep -l "zod"

But if I then try to pass the output of this command to vim tab pages (as below), the paths including spaces are split, and opened as 2 existent, and 9 non-existent files. Is there a way to get past the problem?

vim -p `find . -type f -name "*.txt" -print0 | xargs -0 grep -l "zod"`

I am keen to avoid side-effects such intermediate files or shell/environment variables (such as used in the top answer to a similar question here); and so I am looking specifically for a single-line command.

5
  • 3
    This question is similar to: How to edit a list of generated files whose names contain spaces. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented May 21, 2025 at 15:18
  • I've updated the question as you suggest. Commented May 21, 2025 at 16:57
  • user7543 the currently highest voted answer in the suggested duplicate does exactly what you ask. No temporary file. Commented May 21, 2025 at 20:15
  • "I am keen to avoid using an intermediate file (such as used in the top answer to a similar question here)" - for future reference, answers can be reordered either automatically or manually, so "top answer" isn't as meaningful as you probably intend. It's better to link directly to the answer you're referencing Commented May 21, 2025 at 20:18
  • @ChrisDavies Thanks. I've updated the question to address your points. Commented May 22, 2025 at 9:17

2 Answers 2

2

In:

vim -p `find . -type f -name "*.txt" | xargs grep -l "zod"`
  • find produces a list of file paths newline delimited which is not post-processable
  • xargs expects a blank+newline delimited list of files with quotes and backslashes interpreted as escaping operators which is not what find produces. Actually, there's no command that produces output in the format expected by xargs these days, xargs is generally useless without options such as -0 or -d.
  • grep -l similarly produces something not-postprocessable.
  • `cmd` (using the ancient deprecated form of command substitution) in shells such as sh or bash takes the whole output, splits it on characters of $IFS (space, tab, newline by default there) and then performs filename generation on each resulting word.

So it's not only spaces that are a problem, it's all whitespace characters recognised as separators by xargs, quotes, backslashes, space, tab, newline (assuming an unmodified $IFS), all globbing characters including at least *, ? and [...].

Here, on a GNU system and with the GNU shell (bash) or other shells supporting ksh-style process substitution:

xargs -r0a <(find . -name '*.txt' -type f -exec grep -lZ zod {} +) vim -p

Would have find call grep directly (no need for xargs with find) and get grep to output the list of files NUL-delimited, which is post-processable as NUL happens to be the only character that cannot occur in a file path. xargs -0 understands that format.

In zsh, you could also do:

vim -p ${(0)"$(grep -lZ zod ./**/*.txt(.))"}

Where we replace find with recursive globs and glob qualifier and use the 0 parameter expansion flag to split on NULs (and use the modern $(...) form of command substitution).

One drawback compared to xargs -r is that if there's no matching file, that still runs vim -p without filename argument. One advantage is that you'll have a sorted list of files and hidden ones will be omitted (you can add them back with the D qualifier).

1
  • Many thanks. I had been close, but reading a comment elsewhere on the inefficiency of -exec ... {} \; (which is the only syntax I knew) put me off. I see how -exec ... {} + can remove that problem. As I'm less familiar with xargs' -a flag (and also <(cmd)) I came up with find . -name '*.txt' -type f -exec grep -lZ zod {} + | xargs -o0r vi -p. I will try to use the modern $(...) syntax elsewhere (this was also persuasive), and maybe look into Zsh. Commented May 22, 2025 at 10:06
1

Take advantage of find's -print0 and xargs's -0 options:

find . -type f -print0 | \
    xargs -0 -r doit

to produce, and consume, a NUL-separated list of filenames. It's "safe" because ASCII NUL (0x00) and / are disallowed in filenames.

Read man find xargs.

If you're feeding the list of files to a command, like vim, that only wants a single file, use xargs's -n 1 option to invoke vim for each file, as explained in man xargs.

For debugging, preface your vim command with echo (xargs -0 -r -n1 echo vim), to see and inspect the commands xargs produces without executing them.

2
  • This doesn't work when I try to open the list of files another applications, such as Vim. Your suggestion looks like part of the last command I provide in my question, with the addition of -r. Adding -r does not change it from the outcome there: "the paths including spaces are split, and opened as 2 existent, and 9 non-existent files". Commented May 22, 2025 at 9:22
  • If you're feeding the list of files to a command, like vim, use xargs's -n 1 option to invoke vim for each file, as explained in man xargs. For debugging, preface your vim command with echo (xargs -0 -r -n1 echo vim). Commented May 22, 2025 at 13:19

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.