0

I want to play all the .mp3 files within a directory using vlc, so I do something like this:

vlc $(find ~/Documents/music -name "*.mp3" -exec "echo \"{}\"" \;)

The issue is that I get "vlc: unknown option or missing mandatory argument `-D'". Presumably a parsing issue.

So let's do it file by file:

echo $(echo $(find ~/Documents/music -name "*.mp3" -exec echo "\"{}\" \n " \;) | head -n 1)

I get:

"/home/XXX/Documents/music/My Folder/1 - track/the name of track.mp3"

And when I run:

vlc "/home/XXX/Documents/music/My Folder/1 - track/the name of track.mp3"

It works.

But when I run

vlc $(echo $(find ~/Documents/music -name "*.mp3" -exec echo "\"{}\" \n " \;) | head -n 1)

I get:

cannot open file /home/XXX/the (No such file or directory)

Which would make sense if the arguments to vlc weren't properly quoted. But they are? So why is it being passed into vlc as an unquoted string?

Kusalananda
  • 333,661
unix
  • 3
  • 1

2 Answers2

4

Quotes are not processed in the output of expansions. If you do $(echo '"foo bar"'), or $(echo "\"foo bar\""), what ends up as the argument to the main command is "foo and bar". With literal quotes, and as two distinct arguments due to word splitting.

By default, word splitting happens on any whitespace, spaces or newlines, so from the output of find, you can't tell apart which whitespace is part of a filename, and which is part of what find printed to separate them. Even in general, you can't tell them apart, since file names can also contain newlines...

What you can do is:

find ... -print0 | xargs -0 vlc 

or

find ... -exec vlc {} +

The first has find output the file names separated with NUL bytes, and tells xargs to expect just that. Not all systems have find -print0 and xargs -0, though.

The second has find itself launch vlc, the trailing {} + tells it to pass multiple filenames to one invocation of vlc.

If you have a very large number of files to be found, and they don't fit on one command line, then both of those could launch vlc more than once.

See:

Using echo to debug this doesn't really work as it will join its arguments with a single space, making it impossible to tell the difference between echo foo bar, and echo "foo bar".

ilkkachu
  • 138,973
2

The $() expands properly, but the shell does more to the result than what you are aware of. Additionally, adding literal quotes to pathnames is almost never the correct thing to do (unless you are using eval somewhere).

To play all the MP3 files in or under ~/Documents/music using vlc:

find ~/Documents/music -type f -name '*.mp3' -exec vlc {} +

This would run vlc with as many MP3 files as possible at a time.

To skip using find all-together:

shopt -s globstar
vlc ~/Documents/music/**/*.mp3

This would work as long as you don't have a massive amount of MP3 files (in which case you would get an error because the argument list became too long).

The ** glob matches "recursively" down into a file hierarchy, but must be enabled with shopt -s globstar in bash (the zsh shell has this glob enabled by default).


Your command

vlc $(echo $(find ~/Documents/music -name "*.mp3" -exec echo "\"{}\" \n " \;) | head -n 1)

would first execute the find, head and echo. That pipeline would output

"/home/XXX/Documents/music/My Folder/1 - track/the name of track.mp3"

The shell would then split that string into the words "/home/XXX/Documents/music/My, Folder/1, -, track/the, name, of, and track.mp3" on the spaces in the string.

vlc would then be called with those separate arguments. In particular, the - argument may be mistaken for an option while the others would most likely be taken as separate pathname operands.

This is explained in depth in

Kusalananda
  • 333,661