1

If I want to find any files with .txt in the name, and for every match that is found copy it into the /junk folder, I understand I could use the following:

find / -name ".txt" -exec cp {} /junk \;

Could a pipe be used instead of -exec in the above command?

I think I remember someone saying that a pipe will run each side (of the pipe) at the same time where as -exec will run the left side and then the right side? Am I miss remembering this? Pipe obviously needs to get the output from the left command before it gives the output to the right command so I don’t see how they could both run at the same time.

Kusalananda
  • 333,661
  • That's not exact. Whenever a file meets the conditions of find (here: -name "*.txt"), it will execute the execpart, I think in the same process context. On the other side, piping means to create two different processes, one reading the output of the other (to be exact: usually STDIN of the second process reads STDOUT of the first). So they run in parallel. – ridgy Jan 31 '17 at 19:54
  • So looking at

    find / -name ".txt" | args cp /junk

    the command on the left will fully complete and then feed the results into the command on the right. Where as with

    find / -name ".txt" -exec cp {} /junk ;

    will perform the copy each time a match is hit even before the find cmd is fully complete.

    Is that correct?

    – microscope Jan 31 '17 at 20:51
  • @Wildcard this is not a duplicate. the question you linked to is asking about for i in $(find ...); do ...; this question is asking about find ... | ... and find ... -exec .... the answers are completely different. – strugee Feb 01 '17 at 01:39
  • @strugee, not really. The answers there address this question quite well. xargs is just a different method of "looping over the output." On a side note, look over the questions in this list and see how many of them are exactly asking the question, "Why does my shell script choke on whitespace or other special characters?" (Spoiler: it's very few, yet they are all correctly closed as duplicates.) – Wildcard Feb 01 '17 at 01:45
  • @Wildcard clarification: to me it seems like the underlying question here stems from OP's confusion on how piping works. that's very different from the for question, since that involves whitespace and special characters as well as the fact that you're slurping the entire file list into memory all at once. though given OP's accepted answer (which I couldn't see in Review) it seems my interpretation may be incorrect. – strugee Feb 01 '17 at 01:57

2 Answers2

3

You can use cpio in copy-pass mode for this.

find sourcedir -name "*.txt" | cpio -pd /junk

cpio takes a list of files from standard input, and in copy-pass mode copies the files into the destination directory.

Johan Myréen
  • 13,168
  • 1
    Might want to mention -print0 and -0 in the event there's fancy characters in them filenames. – thrig Jan 31 '17 at 20:17
  • @thrig Blanks in file names are not a problem with cpio. Unlike xargs, cpio uses only newlines as file name separators. Files containing newlines pose a problem, of course, but they should be quite rare. – Johan Myréen Jan 31 '17 at 21:05
  • I forgot the -d argument to cpio in the example above, which is needed if the files are contained in subdirectories. With the -d option, cpio creates leading directories where needed. – Johan Myréen Jan 31 '17 at 21:08
1

You can also pipe find to the xargs command

The bare snippets from - http://www.unixmantra.com/2013/12/xargs-all-in-one-tutorial-guide.html

Find all the .mp3 files in the music folder and pass to the ls command, -print0 is required if any filenames contain whitespace.:

   find ./music -name "*.mp3" -print0 | xargs -0 ls

Find all files in the work folder, pass to grep and search for profit:

   find ./work -print | xargs grep "profit"

You need to use {} with various command which take more than two arguments at a time. For example mv command need to know the file name. The following will find all .bak files in or below the current directory and move them to ~/.old.files directory:

find . -name "*.sh" -print0 | xargs -0 -I {} mv {} ~/back.scripts

You can rename {} to something else. In the following example {} is renamed as file. This is more readable as compare to previous example:

find . -name "*.sh" -print0 | xargs -0 -I file mv file ~/back.scripts

Where,

-0 If there are blank spaces or characters (including newlines) many commands will not work. This option take cares of file names with blank space.

-I Replace occurrences of replace-str in the initial-arguments with names read from standard input. Also, unquoted blanks do not terminate input items; instead the separator is the newline character.

ivanivan
  • 4,955