0

I want to create a file (containing the dir name) in any sub dir of e.g music that has at least one but maybe more .mp3 file. I want one file created no matter if there is one or more .mp3 in that dir and the dir can have whitespace.. I tried something like this: for i in $(find . -name "*.mp3" -printf "%h\n"|sort -u); do echo "$i" ; done

This breaks the path into 2 lines where the whitespace was so:

./directory one

outputs as:

./directory
one
FoxSam12
  • 179
  • 1
  • 9
  • I think [this answer](http://stackoverflow.com/a/7039579/3076724) should help you. Not particularly sure why you need the `sort`. – Reinstate Monica Please Nov 02 '14 at 05:25
  • You should tell us exactly what you want to do. Don't be in the following situation: _You want to solve X. You think that to solve X, you need Y. You don't know how to achieve Y, so you come to SO to ask about Y._ The problem with this approach is that while we'll be good at answering about Y, maybe, you'll need something completely unrelated to Y... Why are you, in the first place, using `sort`? – gniourf_gniourf Nov 05 '14 at 10:47

1 Answers1

1

The construct $( ... ) in your

for x in $(find ... | ... | ... ) ; do ... ; done

executes whatever is in $( ... ) and passes the newline separated output that you would see in the terminal if you had executed the ... command from the shell prompt to the for construct as a long list of names separated by blanks, as in

% ls -d1 b*
bianco nodi.pdf
bin
b.txt
% echo $(ls -d1 b*)
bianco nodi.pdf bin b.txt
% 

now, the for cycle assign to i the first item in the list, in my example bianco and of course it's not what you want...

This situation is dealt with this idiom, in which the shell reads ONE WHOLE LINE at a time

% ls -d1 b* | while read i ; do echo "$i" ; ... ; done

in your case

find . -name "*.mp3" -printf "%h\n" | sort -u | while read i ; do
       echo "$i"
done

hth, ciao


Edit

My anser above catches the most common case of blanks inside the filename, but it still fails if one has blanks at the beginning or the end of the filename and it fails also if there are newlines embedded in the filename.

Hence, I'm modifying my answer quite a bit, according to the advice from BroSlow (see comments below).

find . -name "*.mp3" -printf "%h\0" | \
sort -uz | while IFS= read -r -d '' i ; do
     ...
done

Key points

  1. find's printf now separates filenames with a NUL.
  2. sort, by the -z option, splits elements to be sorted on NULs rather than on newlines.
  3. IFS= stops completely the shell habit of splitting on generic whitespace.
  4. read's option -d (this is a bashism) means that the input is split on a particular character (by default, a newline).
    Here I have -d '' that bash sees as specifying NUL, where BroSlow had $'\0' that bash expands, by the rules of parameter expansion, to '' but may be clearer as one can see an explicit reference to the NUL character.

I like to close with "Thank you, BroSlow".

Community
  • 1
  • 1
gboffi
  • 22,939
  • 8
  • 54
  • 85
  • This takes care of most error cases, but will still filenames with eols, preceding and trailing whitespace, and other special characters. See [this answer](http://stackoverflow.com/questions/7039130/bash-iterate-over-list-of-files-with-spaces/7039579#7039579) for how to properly loop over `find` results, and `-z` option of `sort`. – Reinstate Monica Please Nov 03 '14 at 10:45
  • @BroSlow As the OP mentions `.mp3` files... given the names that I've actually seen applied to `.mp3` files... I think that your correction is very welcome! Please, in your opinion, have I to edit my answer to take into account your comment, or is the link you gave sufficient to the purpose.? – gboffi Nov 03 '14 at 23:44
  • Everyone should really avoid whitespace and special characters in any filenames, but it's generally a poor assumption to assume people will actually do it. I think just switching it to `find . -name "*.mp3" -printf "%h\0" | sort -uz | while IFS= read -r -d $'\0' i` will catch every edge case. – Reinstate Monica Please Nov 04 '14 at 00:23