5

If I run this command:

sudo find . -name *.mp3

then I can get a listing of lots of mp3 files.

Now I want to do something with each mp3 file in a loop. For example, I could create a while loop, and inside assign the first file name to the variable file. Then I could do something with that file. Next I could assign the second file name to the variable file and do with that, etc.

How can I realize this using a linux shell command? Any help is appreciated, thanks!

phs
  • 10,687
  • 4
  • 58
  • 84
Searene
  • 25,920
  • 39
  • 129
  • 186
  • 1
    All the solutions not involving `-exec` or `xargs -0` are doomed to fall (and fail) under shell word splitting rules and/or reek of so-called “useless use of cat” issues. – jørgensen Feb 22 '12 at 08:25
  • Excuse me, but why do not I see a word splitting problem in this, as you would say "doomed to fail", solution: echo -e 'hello.txt\nmy picture.jpg' | while read line; do echo reading line \"$line\"; done ? – dimir Feb 23 '12 at 07:58
  • There are a variety of solutions explained on Unix & Linux Stack Exchange: http://unix.stackexchange.com/q/110402/62291 and http://unix.stackexchange.com/q/9496/62291 – toxalot Mar 15 '14 at 07:56

4 Answers4

17

For this, use the read builtin:

sudo find . -name *.mp3 |
while read filename
do
    echo "$filename"    # ... or any other command using $filename
done

Provided that your filenames don't use the newline (\n) character, this should work fine.

Jeremy Kerr
  • 1,895
  • 12
  • 24
  • 8
    You can use `-print0` option for find and `-d ''` option for read to get around the `\n` issue. Also, use `-r` option for read so backslashes in the filename are not interpreted as escape sequences. – glenn jackman Feb 22 '12 at 15:15
  • 2
    Sorry but, why `sudo`? Apart from being useless, it is dangerous and may not work with user bound sound infrastructures (i.e. pulseaudio) – iMil Dec 15 '14 at 09:08
  • 1
    @iMil: simply because it was given like that in the question. – Jeremy Kerr Dec 15 '14 at 14:51
11

My favourites are

find . -name '*.mp3' -exec cmd {} \;

or

find . -name '*.mp3' -print0 | xargs -0 cmd

While Loop

As others have pointed out, you can frequently use a while read loop to read filenames line by line, it has the drawback of not allowing line-ends in filenames (who uses that?).

xargs vs. -exec cmd {} +

Summarizing the comments saying that -exec...+ is better, I prefer xargs because it is more versatile:

  • works with other commands than just find
  • allows 'batching' (grouping) in command lines, say xargs -n 10 (ten at a time)
  • allows parallellizing, say xargs -P4 (max 4 concurrent processes running at a time)
  • does privilige separation (such as in the OP's case, where he uses sudo find: using -exec would run all commands as the root user, whereas with xargs that isn't necessary:

    sudo find -name '*.mp3' -print0 | sudo xargs -0 require_root.sh
    sudo find -name '*.mp3' -print0 | xargs -0 nonroot.sh
    
  • in general, pipes are just more versatile (logging, sorting, remoting, caching, checking, parallelizing etc, you can do that)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • `{} +` is better in relation to `{} ;`. `{} ;` is equivalent to `xargs -n1`. `{} +` can — like xargs, yes — pass in multiple files as arguments, but without having to spawn the extra xargs process, though on the counterside you don't get to change the symbol as you can with `xargs -I`. So there's that :) – jørgensen Feb 22 '12 at 08:46
  • If using `-exec`, you should really place the `{}` inside double quotes in case the file name contains spaces or other special characters. – Some programmer dude Feb 22 '12 at 09:34
  • @JoachimPileborg No: the (double) quotes never even reach `find`: the shell interprets them before that time. `find -exec` does the right thing anyway (it doesn't use the shell but something like [execvp](http://linux.die.net/man/3/execvp). – sehe Feb 22 '12 at 09:36
  • Downvoting as no $file variable involved which was mentioned in the task. – dimir Feb 22 '12 at 10:58
  • Alright, alright, I agree this is not a wrong solution. Pity I can't remove the vote down. :-( – dimir Feb 23 '12 at 07:59
  • 1
    thx, `-exec` works perfect ,but i need a `while` loop to do with lots of codes :) – Searene Feb 23 '12 at 08:13
2

How about using the -exec option to find?

find . -name '*.mp3' -exec mpg123 '{}' \;

That will call the command mpg123 for every file found, i.e. it will play all the files, in the order they are found.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
-3
for file in $(sudo find . -name *.mp3);
do
    # do something with file
done
Mallik
  • 250
  • 1
  • 6
  • 1
    didn't downvote, but this works badly for filenames with special characters (like spaces) – sehe Feb 22 '12 at 09:34
  • yes.. I usually use it with my files that never had special characters except underscores in the names.. Your solution is fool-proof. Thanks! – Mallik Feb 22 '12 at 09:47