8

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?

eatloaf
  • 607
  • 1
  • 9
  • 16
  • 2
    What is the \" about? That would tell find to list files called exactly `"*.y"` (the quotes will be passed to find and while the * will be expanded by shell, it will find nothing and passed through anyway), which I doubt exist. – Jan Hudec Jan 06 '12 at 14:39
  • Among the various compile-time options for busybox are `CONFIG_FEATURE_FIND_PRINT0`, `CONFIG_FEATURE_FIND_EXEC`, `CONFIG_FEATURE_FIND_EXEC_PLUS` -- approaches will or won't work depending on the details of exactly how *your* copy was compiled. – Charles Duffy Dec 18 '15 at 15:36

3 Answers3

12

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.

Community
  • 1
  • 1
praetorian droid
  • 2,989
  • 1
  • 17
  • 19
  • +1: The find command comes with the `-exec` option. It's a bit slower than `xargs` because it executes the command for each and every file found. But, it works, and it doesn't have _whitespace_ issues because the shell isn't involved. If you can't use `xargs` because of whitespace issues, use `-exec`. Loops are just as inefficient as the `-exec` parameter since they execute once for each and every file, but have the whitespace issues that `xargs` have. – David W. Jan 06 '12 at 15:22
  • 1
    `xargs` also executes the command for each file in this case so `find` is not slower. – praetorian droid Jan 06 '12 at 16:34
  • You're right. I didn't look at the command. Normally you use xargs instead of `-exec` because xargs combines as many files as it can on the command line and executes the command only a few times. – David W. Jan 06 '12 at 20:56
  • wrong; it is because find and exec do not run shells so must exec /bin/echo for each file. if we modify the script to use /bin/echo then the time is comparable to the first two options; adding an option `find "$1" -printf "mv %p %p.old\n"` runs faster than the builtin echo. – Hello71 Oct 12 '14 at 19:58
  • This doesn't work as written, because Busybox doesn't support the -exec option to find. The while loop approach posted by Jan Hudec works on Busybox – Greg Rundlett Dec 18 '15 at 15:01
  • @DavidW., re: "slower", `-exec ... {} +` behaves just as xargs does (in terms of minimizing the invocation count), and has been part of the POSIX standard for `find` since 2006. – Charles Duffy Dec 18 '15 at 15:34
  • In busybox '-exec' option support is configurable at compile time just like the 'find' command itself. Updated the answer. – praetorian droid Dec 18 '15 at 15:34
3
  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.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
1

An alternative is to use a loop:

find . -maxdepth 1 -type d -name \"*.y\" -mtime +`expr 2 \* 365` -print | while IFS= read file
do
    mv "$file" "$file".old
done
dogbane
  • 266,786
  • 75
  • 396
  • 414
  • 1
    `read` uses newline as separator. Either you need `read -0` (but I am not sure it's implemented in busybox) or you need just `-print`. – Jan Hudec Jan 06 '12 at 14:36