25
find . -name "filename including space" -print0 | xargs -0 ls -aldF > log.txt
find . -name "filename including space" -print0 | xargs -0 rm -rdf

Is it possible to combine these two commands into one so that only 1 find will be done instead of 2?

I know for xargs -I there may be ways to do it, which may lead to errors when proceeding filenames including spaces. Any guidance is much appreciated.

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
Richard Chen
  • 303
  • 1
  • 3
  • 10
  • Is `-exec` an option I could use for your command or do you need to use `xargs`? – ztank1013 Sep 19 '11 at 15:12
  • `xargs` is preferred as the output may be quite large. – Richard Chen Sep 19 '11 at 15:31
  • 2
    If the output is going to be quite large be careful at `xargs` command because there is a `-s max-chars` parameter with a default value that could alter your command expected behavior. – ztank1013 Sep 19 '11 at 15:43
  • possible duplicate of [xargs with multiple commands as argument](http://stackoverflow.com/questions/6958689/xargs-with-multiple-commands-as-argument) – Flow Feb 04 '14 at 12:01

8 Answers8

50
find . -name "filename including space" -print0 | 
  xargs -0 -I '{}' sh -c 'ls -aldF {} >> log.txt; rm -rdf {}'

Ran across this just now, and we can invoke the shell less often:

find . -name "filename including space" -print0 | 
  xargs -0 sh -c '
      for file; do
          ls -aldF "$file" >> log.txt
          rm -rdf "$file"
      done
  ' sh

The trailing "sh" becomes $0 in the shell. xargs provides the files (returrned from find) as command line parameters to the shell: we iterate over them with the for loop.

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Thanks. I got a "xargs: illegal option -- i" for `find . -name "filename including space" -print0 | xargs -0 -i sh -c 'ls -aldF {} > log.txt; rm -rdf {}'`. And I got a "xargs: illegal option -- c" for `find . -name "filename including space" -print0 | xargs -0 -I sh -c 'ls -aldF {} > log.txt; rm -rdf {}'`. Did I make some mistakes? – Richard Chen Sep 19 '11 at 16:07
  • no, definitely use lower case `-i`. I can't explain the first error. The second error occurs because the "sh" is consumed by the `-I` option – glenn jackman Sep 19 '11 at 16:14
  • But I didn't find -i option for `xargs` in my manual. Or is -i not for `xargs`? It would be great if more explanations for -i are given. Thanks again :) – Richard Chen Sep 19 '11 at 16:21
  • How does your xargs have `-0` but not `-i`? I see in this [xargs(1)](http://linux.die.net/man/1/xargs) man page that `-i` is deprecated, so use `-I '{}'` instead. – glenn jackman Sep 19 '11 at 16:24
  • Thanks. Replacing -i with -I '{}' works great with the rm part. But it only list the last file in find, which is another pain... – Richard Chen Sep 19 '11 at 16:37
  • Thanks Glenn! Finally I've worked this out in your way. As mentioned above, the original command on my OS will only log the last file in find. So I did a little tweak to use `tee -a log .file` instead of `> log.txt`. And it worked perfectly. So this is really a great way to execute multiple commands using the output of `xargs` without saving intermediate results. Brilliant! Thanks! – Richard Chen Sep 19 '11 at 23:48
  • or, use `>>` to append instead of `>` to overwrite. – glenn jackman Sep 20 '11 at 01:27
  • Thanks a lot. Struggled for more than an hour for running 2 commands with xargs result :) – Makesh Apr 13 '15 at 12:06
16

If you're just wanting to avoid doing the find multiple times, you could do a tee right after the find, saving the find output to a file, then executing the lines as:

find . -name "filename including space" -print0 | tee my_teed_file | xargs -0 ls -aldF > log.txt
cat my_teed_file | xargs -0 rm -rdf 

Another way to accomplish this same thing (if indeed it's what you're wanting to accomplish), is to store the output of the find in a variable (supposing it's not TB of data):

founddata=`find . -name "filename including space" -print0`
echo "$founddata" | xargs -0 ls -aldF > log.txt
echo "$founddata" | xargs -0 rm -rdf
l0b0
  • 55,365
  • 30
  • 138
  • 223
Jonathan M
  • 17,145
  • 9
  • 58
  • 91
  • I understand the author wants to ask whether xargs support to execute multi-command in one command. But as I know, the xargs can only execute one operation in one command. So as u suggested, we may need to save it temporarily. – Ivan Sep 19 '11 at 15:12
  • @Ivan: Yeah. I think this is what he's looking for, but not via xargs. It still gets to his main goal stated in the OP: "Is it possible to combine these two commands into one so that *only 1 find will be done instead of 2*?" – Jonathan M Sep 19 '11 at 15:16
  • It seems that I didn't give a well-matched title for this topic. But anyhow thank you both. So it's my understanding as per Ivan's post that it's not possible to execute more than one operations in one xargs command? – Richard Chen Sep 19 '11 at 15:19
  • @Richard: It doesn't appear to be possible in GNU: http://unixhelp.ed.ac.uk/CGI/man-cgi?xargs – Jonathan M Sep 19 '11 at 15:21
  • 1
    Yeah, the second way also works great. The key is to think in the right direction. If impossible to execute 2 commands at the same time, results need to be stored for future usage. – Richard Chen Sep 19 '11 at 15:29
  • Make sure you quote the variable when you echo it: `echo "$founddata"` – glenn jackman Sep 22 '11 at 19:37
  • What if $founddata keeps very big filesystem tree structure? This will fail. but xargs is userfull because you can add option for parallel execution. – Znik Feb 27 '14 at 10:14
5

I believe all these answers by now have given out the right ways to solute this problem. And I tried the 2 solutions of Jonathan and the way of Glenn, all of which worked great on my Mac OS X. The method of mouviciel did not work on my OS maybe due to some configuration reasons. And I think it's similar to Jonathan's second method (I may be wrong).

As mentioned in the comments to Glenn's method, a little tweak is needed. So here is the command I tried which worked perfectly FYI:

find . -name "filename including space" -print0 | 
xargs -0 -I '{}' sh -c 'ls -aldF {} | tee -a log.txt ; rm -rdf {}'

Or better as suggested by Glenn:

find . -name "filename including space" -print0 | 
xargs -0 -I '{}' sh -c 'ls -aldF {} >> log.txt ; rm -rdf {}'
Richard Chen
  • 303
  • 1
  • 3
  • 10
  • Good combination of the answers. Since the check mark is falling in my box, and we really ought to split it, I'm "sharing it" by giving Glenn a +1 and encouraging others to do so also. Nice job. – Jonathan M Sep 20 '11 at 00:19
  • Sorry for the check mark failing. I don't know how the check system works and I played it around for a while...my fault. Is there any way to make it up? My logic is the earliest answer among all workable ones get the check. And I'll give all workable ones +1 when I get 15 reputations... – Richard Chen Sep 20 '11 at 00:33
4

As long as you do not have newline in your filenames, you do not need -print0 for GNU Parallel:

find . -name "My brother's 12\" records" | parallel ls {}\; rm -rdf {} >log.txt

Watch the intro video to learn more: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
  • 31,768
  • 5
  • 86
  • 104
  • 1
    You should state your relation to GNU Parallel, in your network profile. I'm not sure it's really that relevant here, but SE doesn't have private user msgs. – J. M. Becker Jan 31 '12 at 06:32
2

Just a variation of the xargs approach without that horrible -print0 and xargs -0, this is how I would do it:

ls -1 *.txt  | xargs --delimiter "\n" --max-args 1 --replace={} sh -c 'cat {}; echo "\n"'

Footnotes:

  • Yes I know newlines can appear in filenames but who in their right minds would do that
  • There are short options for xargs but for the reader's understanding I've used the long ones.
  • I would use ls -1 when I want non-recursive behavior rather than find -maxdepth 1 -iname "*.txt" which is a bit more verbose.
Sridhar Sarnobat
  • 25,183
  • 12
  • 93
  • 106
  • 1
    The `-1` option (list one file per line) is already the default if the stdout is not the terminal (eg. pipe). – Ray Dec 08 '16 at 12:54
1

You can execute multiple commands after find using for instead of xargs:

IFS=$'\n'
for F in `find . -name "filename including space"`
do
    ls -aldF $F > log.txt
    rm -rdf $F
done

The IFS defines the Internal Field Separator, which defaults to <space><tab><newline>. If your filenames may contain spaces, it is better to redefine it as above.

ericbn
  • 10,163
  • 3
  • 47
  • 55
1

I'm late to the party, but there is one more solution that wasn't covered here: user-defined functions. Putting multiple instructions on one line is unwieldy, and can be hard to read/maintain. The for loop above avoids that, but there is the possibility of exceeding the command line length.

Here's another way (untested).

function processFiles {
  ls -aldF "$@"
  rm -rdf "$@"
}
export -f processFiles

find . -name "filename including space"` -print0 \
  | xargs -0 bash -c processFiles dummyArg > log.txt

This is pretty straightforward except for the "dummyArg" which gave me plenty of grief. When running bash in this way, the arguments are read into

"$0" "$1" "$2"  ....

instead of the expected

"$1" "$2" "$3"    ....

Since processFiles{} is expecting the first argument to be "$1", we have to insert a dummy value into "$0".

Footnontes:

  1. I am using some elements of bash syntax (e.g. "export -f"), but I believe this will adapt to other shells.
  2. The first time I tried this, I didn't add a dummy argument. Instead I added "$0" to the argument lines inside my function ( e.g. ls -aldf "$0" "$@" ). Bad idea. Aside from stylistic issues, it breaks when the "find" command returns nothing. In that case, $0 is set to "bash", Using the dummy argument instead avoids all of this.
Phil Freed
  • 98
  • 7
0

Another solution:

find . -name "filename including space" -print0 \
| xargs -0 -I FOUND echo "$(ls -aldF FOUND > log.txt ; rm -rdf FOUND)"
mouviciel
  • 66,855
  • 13
  • 106
  • 140