Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives to xargs -l

I want to rename a bunch of dirs from DIR to DIR.OLD. Ideally I would use the following:

find . -maxdepth 1 -type d -name \"*.y\" -mtime +`expr 2 \* 365` -print0 | xargs -0 -r -I file mv file file.old

But the machine I want to execute this on has BusyBox installed and the BusyBox xargs doesn't support the "-I" option.

What are some common alternative methods for collecting an array of files and then executing on them in a shell script?

like image 413
eatloaf Avatar asked Jan 06 '12 14:01

eatloaf


People also ask

What can I use instead of xargs?

GNU parallel is an alternative to xargs that is designed to have the same options, but is line-oriented. Thus, using GNU Parallel instead, the above would work as expected.

Why is xargs necessary?

In this brief tutorial, we saw the use of xargs to build and execute commands from standard input. It is used in situations when a command can take input from other commands, only in the form of arguments. It is also particularly useful when we have a list of file paths on STDIN and want to do something with them.

What is difference between xargs and pipe?

Pipe (|) is used when you want the standard output to be fed the next stage of the pipeline (cat foobar. txt | grep example). 'man xargs' will be of great assistance.

What is the difference between exec and xargs in Unix?

since this asks about the differences and that question asks about which is faster, but the difference is that xargs invokes the command in batches while find -exec invokes the command once per result, which makes xargs faster.


2 Answers

You can use -exec and {} features of the find command so you don't need any pipes at all:

find -maxdepth 1 -type d -name "*.y" -mtime +`expr 2 \* 365` -exec mv "{}" "{}.old" \;

Also you don't need to specify '.' path - this is default for find. And you used extra slashes in "*.y". Of course if your file names do not really contain quotes.

In fairness it should be noted, that version with while read loop is the fastest of proposed here. Here are some example measurements:

$ cat measure 
#!/bin/sh
case $2 in
  1) find "$1" -print0 | xargs -0 -I file echo mv file file.old ;;

  2) find "$1" -exec echo mv '{}' '{}.old' \; ;;

  3) find "$1" | while read file; do
       echo mv "$file" "$file.old"
     done;;
esac
$ time ./measure android-ndk-r5c 1 | wc
   6225   18675  955493
real    0m6.585s
user    0m18.933s
sys     0m4.476s
$ time ./measure android-ndk-r5c 2 | wc
   6225   18675  955493
real    0m6.877s
user    0m18.517s
sys     0m4.788s
$ time ./measure android-ndk-r5c 3 | wc
   6225   18675  955493
real    0m0.262s
user    0m0.088s
sys     0m0.236s

I think it's because find and xargs invokes additional /bin/sh (actually exec(3) does it) every time for execute a command, while shell while loop do not.

Upd: If your busybox version was compiled without -exec option support for the find command then the while loop or xargs, suggested in the other answers (one, two), is your way.

like image 193
praetorian droid Avatar answered Sep 20 '22 17:09

praetorian droid


  1. Use a for loop. Unfortunately I don't think busybox understands read -0 either, so you won't be able to handle newlines properly. If you don't need to, it's easiest to just:

    find . -maxdepth 1 -type d -name \"*.y\" -mtime +`expr 2 \* 365` -print | while read file; do mv -- "$file" "$file".old; done
    
  2. Use a sh -c as the command. Note the slightly weird use of $0 to name the first argument (it would normally be the script name and that goes to $0 and while you are suppressing script with -c, the argument still goes to $0) and the use of -n 1 to avoid batching.

    find . -maxdepth 1 -type d -name \"*.y\" -mtime +`expr 2 \* 365` -print0 | xargs -0 -r -n 1 sh -c 'mv -- "$0" "$0".old'
    

Edit Oops: I forgot about the find -exec again.

like image 29
Jan Hudec Avatar answered Sep 24 '22 17:09

Jan Hudec