John Lawrence Aspden
John Lawrence Aspden

Reputation: 17470

bash loop over files mysterious bug

Can anyone tell me what's going on here:

This:

find . -name "*.mp3" | while read fname ; do 
    echo "$fname"; 
    ls -l "$fname"; 
    echo mplayer "$fname" ; 
    echo "$fname" ;
done

works absolutely fine as far as I can see, but if I actually try to run mplayer instead of echoing the command:

find . -name "*.mp3" | while read fname ; do 
     echo "$fname"; 
     ls "$fname"; 
     mplayer "$fname" ; 
     echo "$fname" ; 
done

Then it plays one file and then goes haywire.

I'm thinking that mplayer must be interacting with read somehow, but I am not wise in the ways of bash.

Upvotes: 1

Views: 122

Answers (3)

choroba
choroba

Reputation: 242103

Use the -noconsolecontrols option of mplayer. It makes it ignore the standard input, so it doesn't steal characters from it and read can process them, and mplayer doesn't behave strangely, interpreting file names as commands coming from the keyboard.

Upvotes: 4

Ed Morton
Ed Morton

Reputation: 204310

Do this instead:

#!/bin/env bash
while IFS= read -r -d '' fname <&9; do 
     printf '%s\n' "$fname"
     ls "$fname"
     mplayer "$fname"
     printf '%s\n' "$fname"
done 9< <(find . -name '*.mp3' -print0)

See http://mywiki.wooledge.org/BashFAQ/001

Upvotes: 2

Niobos
Niobos

Reputation: 932

Your guess is correct. find outputs its list of files to stdout. You pipe that in to a second process: your while loop. In this loop, both read and mplayer read from stdin. read will read the first filename, and mplayer will read all the rest of the input as if you were controlling it via the keyboard while running.

The easiest solution is to add </dev/null after mplayer, but it may not be happy if it can't read from stdin. Other solutions are to use a separate file descriptor for your find | read part, or use a for-loop instead.

Upvotes: 2

Related Questions