1

I'm using this command to find specific files (2 files) which works as it should be on it's own;

find . -iname '*.txt' -o -iname '*.pdf'

and returns the correct files;

./.folder1/file1.txt
./.folder1/file1.pdf
./.folder2/file2.txt
./.folder2/file2.pdf

However, if I try to make these founded files into a tarball, it only includes the first -iname part from the find command, like this;

find . -iname '*.txt' -o -iname '*.pdf' -print0 | xargs -0 tar -czvf files.tar.gz

so it doesn't include the *.pdfs in this example and only includes *.txts in the tarball:

./.folder1/file1.txt
./.folder2/file2.txt

how can I fix this so it makes both *.txts and *.pdfs into a tarball?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Marry Jane
  • 305
  • 2
  • 15

2 Answers2

3

First, using find ... | xargs tar -c is not well-advised: When find generates a list of files so long that xargs splits into multiple tar invocations, all but the last invocation will be overwritten.

Much safer to run just one copy of tar, and configure that to read from stdin:

find . '(' -iname '*.txt' -o -iname '*.pdf' ')' -print0 |
  tar -c --null -T - -zf backups.tar.gz

Writing a shell equivalent to your find logic, the original/buggy version might look like this:

for f in *; do
  { [[ $f = *.txt ]]; } || \
  { [[ $f = *.pdf ]] && printf '%s\0' "$f"; }
done

There's nothing actually done if the *.txt branch is true, because the printf is only conditional on the *.pdf case.


Either use parens to group your branches:

find . '(' -iname '*.txt' -o -iname '*.pdf' ')' -print0

...which makes the logic look like:

for f in *; do
  { [[ $f = *.txt ]] || [[ $f = *.pdf ]]; } && printf '%s\0' "$f";
done

Or put a separate copy of the action on each side:

find . -iname '*.txt' -print0 -o -iname '*.pdf' -print0

...which acts like:

for f in *; do
  { [[ $f = *.txt ]] && printf '%s\0' "$f"; } || \
  { [[ $f = *.pdf ]] && printf '%s\0' "$f"; }
done
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1

-o has lower precedence than the implicit -a that joins -iname '*.pdf' and -print0. You need to parenthesize the arguments to -o:

find . \( -iname '*.txt' -o -iname '*.pdf' \) -print0

find only adds an implicit -print if there are no actions specified at all. Without the parentheses, you have an explicit -print0, which prevents an implicit -print being applied to the result of the first -iname primary.

chepner
  • 497,756
  • 71
  • 530
  • 681