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
  • 320,670
  • 36
  • 633
  • 936
microscope
  • 23
  • 1
  • 5
  • That's not exact. Whenever a file meets the conditions of `find` (here: `-name "*.txt")`, it will execute the `exec`part, 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](http://unix.stackexchange.com/questions/linked/131766?lq=1) 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
  • 12,862
  • 1
  • 32
  • 33
  • 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,870
  • 1
  • 9
  • 20
  • 2
    [Brevity is acceptable, but fuller explanations are better.](http://unix.stackexchange.com/help/how-to-answer) – Kusalananda Jan 31 '17 at 20:31
  • So the following would produce the same result? find / -name ".txt" | args cp /junk – microscope Jan 31 '17 at 20:43
  • 1
    @microscope No, because that would copy *from* `/junk` instead of *to* `/junk`. Although what you want to do can be done with `xargs`, it's difficult to get right ([`xargs` expects quoting that `find` doesn't produce](http://unix.stackexchange.com/questions/131766/why-does-my-shell-script-choke-on-whitespace-or-other-special-characters)); `-exec` is the right thing for what you want to do. – Gilles 'SO- stop being evil' Jan 31 '17 at 23:23
  • @microscope i've edited my reply with some examples from a tutorial – ivanivan Jan 31 '17 at 23:27