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?
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.
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.
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.
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.
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With