Alternatives to xargs -l

11,065

Solution 1

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.

Solution 2

  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.

Solution 3

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
Share:
11,065
eatloaf
Author by

eatloaf

Updated on June 14, 2022

Comments

  • eatloaf
    eatloaf almost 2 years

    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?

  • Jan Hudec
    Jan Hudec over 12 years
    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.
  • David W.
    David W. over 12 years
    +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.
  • praetorian droid
    praetorian droid over 12 years
    xargs also executes the command for each file in this case so find is not slower.
  • David W.
    David W. over 12 years
    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.
  • Hello71
    Hello71 over 9 years
    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.
  • Greg Rundlett
    Greg Rundlett over 8 years
    Busybox doesn't even understand print0. But your whie loop works
  • Greg Rundlett
    Greg Rundlett over 8 years
    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
  • Charles Duffy
    Charles Duffy over 8 years
    @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.
  • praetorian droid
    praetorian droid over 8 years
    In busybox '-exec' option support is configurable at compile time just like the 'find' command itself. Updated the answer.