Search code examples
macosfindxargsmv

Why "xargs find | xargs mv" and "xargs find -exec mv" work but produce error printouts "No such file or directory"?


I get error printouts when I use methods of (1) xargs find | xargs mv and (2) xargs find -exec mv to move files, but the moving of the files works as expected. I get no error printouts when I use (3) an intermediate variable, which is what I want.

Finding the inode numbers, which is what I'm doing with stat and sed, shouldn't be related to the problem.

I have condensed my problem into the following. Basically I want to get rid of the error printouts in both of Methods 1 and 2 (no piping to /dev/null please).

Note I'm on macOS, using BSD stat, and I'm also using zsh.

And I also know about find -print0 | xargs -0, I just wanted to condense the problem so that it's not as long. This is as condensed as I want to get.

Setup

Make test subdirectories (which are later moved by inode number).

mkdir -p "./testdir/dir"{1..4}
Method 1 - xargs find | xargs mv

Works to move the subdirectories, but with error printouts.

stat -s ./testdir/* | sed -E -n 's/.*st_ino=([0-9]+).*/\1/p' | xargs -I abc find ./testdir/* -maxdepth 1 -inum abc | xargs -I {} mv {} ~/.Trash

Error printouts:

find: ./testdir/dir1: No such file or directory
find: ./testdir/dir1: No such file or directory
find: ./testdir/dir2: No such file or directory
find: ./testdir/dir1: No such file or directory
find: ./testdir/dir2: No such file or directory
find: ./testdir/dir3: No such file or directory
Method 2 - xargs find -exec mv

Works to move the subdirectories, but with error printouts.

stat -s ./testdir/* | sed -E -n 's/.*st_ino=([0-9]+).*/\1/p' | xargs -I abc find ./testdir/* -maxdepth 1 -inum abc -exec mv {} ~/.Trash \;

Error printouts:

find: ./testdir/dir1: No such file or directory
find: ./testdir/dir1: No such file or directory
find: ./testdir/dir2: No such file or directory
find: ./testdir/dir1: No such file or directory
find: ./testdir/dir2: No such file or directory
find: ./testdir/dir3: No such file or directory
find: ./testdir/dir1: No such file or directory
find: ./testdir/dir2: No such file or directory
find: ./testdir/dir3: No such file or directory
find: ./testdir/dir4: No such file or directory
Method 3 - intermediate variable

Works to move the subdirectories, but this time without error printouts, which is what I want.

dirs_to_move=$(stat -s ./testdir/* | sed -E -n 's/.*st_ino=([0-9]+).*/\1/p' | xargs -I abc find ./testdir/* -maxdepth 1 -inum abc)
printf '%s\n' "$dirs_to_move" | xargs -I {} mv {} ~/.Trash
Question

How do I eliminate the error printouts from both of Methods 1 and 2? What is causing the printouts?


Solution

  • Short answer: find is being told to search all 4 subdirectories, even after some of them have been deleted.

    Detailed eplanation: The root problem is that the wildcard in the xargs -I abc find ./testdir/* -maxdepth 1 ... part gets expanded by the shell before any of the commands get run. So that part of the script becomes:

    ... | xargs -I abc find ./testdir/dir1 ./testdir/dir2 ./testdir/dir3 ./testdir/dir4 -maxdepth 1 ...
    

    What happens then is the stat | sed part sends the first inode number to xargs, that runs find, find searches all 4 directories for the matching item, and either deletes it directly or sends its path to the next xargs for deletion.

    Next, the stat | sed part sends the second inode number to xargs, that runs find, find searches all 4 directories... oops, hey, one of them's missing! So it prints an error message about ./testdir/dir1 not existing, searches the other three, and (one way or another) deletes the next one.

    Next comes the third inode number, and this time neither ./testdir/dir1 nor ./testdir/dir2 exists, so you get two error messages. Etc etc etc.

    (There's also an additional problem with the second one, where immediately after running the mv command, find then tries to search its contents, and oops it's gone. That's why you get more error messages that way. I think you might've wanted -maxdepth 0 to keep it from trying to do that.)

    Solution: I'm not sure what the larger context is, but my immediate reaction is that this is an overcomplex mess and as much as possible should be removed. But without knowing what can be changed without breaking the big picture, the minimal fix I see is to just have find search the entire testdir directory, rather than using a wildcard to list specific (wrong) subdirectories:

    ... | xargs -I abc find ./testdir -maxdepth 1 ...
    

    (And note that in this form, -maxdepth 1 is actually correct.)