Issue
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 *.pdf
s in this example and only includes *.txt
s in the tarball:
./.folder1/file1.txt
./.folder2/file2.txt
how can I fix this so it makes both *.txt
s and *.pdf
s into a tarball?
Solution
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
Answered By - Charles Duffy