Issue
The problem is - I have a list of files and I want to send them into a program, so it could handle this list in the same process. What I mean is, for example I have a bunch video files, that I want to play with mpv, but I need to pass them into a certain order which I can create with ls video\ * | sort -k2
. The name of each file looks like video [number] - [title].mp4
. I want to sort them by [number]
column so I call ls
and sort
programs, because my shell (zsh) can't do such thing by it self (I think).
I typed ls video\ * | sort -k2
and it gave me a sorted list as I wanted it to. But when I'm trying to pass it into mpv with mpv $(ls video\ * | sort -k2)
, for some reason mpv doesn't read file names line by line, instead it reads them from space to space (I mean, it's not mpv's problem, it's the shell who's giving mpv such arguments separated by spaces but not lines).
I tried to wrap each line with quotes, tried to create a oneline list, tried both at the same time, but none if that worked. Shell always separates file names space by space, it ignores newlines or quotes. It's all because spaces, isn't?
Solution
why “mpv $(ls video\ *)” is not the same as “mpv video\ *”?
Because the result of a command substitution undergoes word splitting while the result of filename expansion does not, because word splitting happens before filename expansion (see shell expansions).
Word splitting is that magic thing that when the argument is unquoted it is spitted for arguments using whitespaces. That means that with a function that would just print the count of arguments f() { echo $#; }
having var="a b c variable with spaces"; f $var
- when $var
is unqouted the function f
will receive 6 arguments - the result of $var
expansion is spitted on whitespaces.
When the result of command substitution is unquoted, it undergoes word splitting. That means that in f $(echo a b c)
the function f
will receive 3 arguments.
Because your files have a space in it video\ *
- when you pass the unquoted result of a command ls
that outputs strings with whitespaces it will be spitted on whitespaces. So video
and the rest will be separate arguments.
Do not parse ls. ls
is for nice printing in shell - for nothing else. Most of the cases are easy to transform to find -exec
or find | xargs
for example you want to: find . -maxdepth 1 -type f -name 'video *' | sort -k2 | xargs -d '\n' mpv
or even better with zero terminated streams: find . -maxdepth 1 -type f -name 'video *' -print0 | sort -z -k2 | xargs -0 mpv
.
It's all because spaces, isn't?
Yes.
Answered By - KamilCuk Answer Checked By - Pedro (WPSolving Volunteer)