shellunixgnu-findutils

GNU find "or" function is not working with -print0 | xargs -0 tar


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?


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