Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

So, I want to play some MP3s in a console player that takes file names as inputs. I have the following files, as given by ls -1 * where * get expanded by my shell (zsh):

1 - Main title.mp3
10 - End title.mp3
2 - Intro.mp3 
...

But clearly, how I actually want them to be ordered is like so:

1 - Main title.mp3
2 - Intro.mp3 
...
10 - End title.mp3

What shell expansion magic do I need to make it so?

I know I could get the same thing with some clever pipes and sort -h but that's not really an option because I am a lazy typist. So, unless I wrote a small shell script to add song, I'd just rather do nyxmms2 add /path/to/dir/*.mp3 thus why I am looking at how to do the filtering within zsh's expansion... Also, I want to know if this is possible because it applies to a lot of other situations as well -- for example, log files or revisions or ...

share|improve this question
    
Could explain why the sort method isn't an option? Just curious. What are you ultimately trying to do? –  slm Apr 26 '13 at 13:46
    
Done: In a nut shell, I am lazy and cannot be bothered with typing | sort -n all the time I add things ... ^_~ –  Sardathrion Apr 26 '13 at 14:05
1  
make it an alias then: alias lss="ls | sort -h". Make it part of your environment so it's always there. –  slm Apr 26 '13 at 14:17
add comment

4 Answers 4

Use the n glob qualifier.

print -lr -- *.mp3(n)

You can change the default sort order by setting the numeric_glob_sort option.

setopt numeric_glob_sort
print -lr -- *.mp3

If you need lexicographic order for one pattern while numeric_glob_sort may be in effect, negate the n glob qualifier.

print -lr -- *(^n)
share|improve this answer
    
Though probably not an issue to the OP, it should be noted that the n flag only understands positive integer decimal numbers (no negative, no floats, no k/M/G/T... suffices like sort -h does, no hex, and numbers with leading 0s are considered as decimal, not octal) –  Stéphane Chazelas Apr 28 '13 at 8:11
add comment

One approach would be to run the output of ls through the command sort to control how the output is displayed.

You can use the -h (aka. --human-numeric-sort) which sorts things in human readable form.

$ ls | sort -h
1 - Main title.mp3
2 - Main title.mp3
10 - Main title.mp3

EDIT #1 - addressing OP's comment

The above can be wrapped into an alias like so:

$ alias lss="ls | sort -h"

Running the above command would then be reduced to this:

$ lss
1 - Main title.mp3
2 - Main title.mp3
10 - Main title.mp3

References

share|improve this answer
1  
As I said in the question, I was hoping not to have to use pipes and sort but for some magic with zsh that would allow it to do that for me when it expands *. –  Sardathrion Apr 26 '13 at 13:38
add comment

You can change the sort order in zsh globbing (see Gilles' answer), but ls will sort its arguments anyway, and by default lexicographically.

If your ls is the GNU ls, you can use the -v option to sort numerically:

ls -1v

Or:

ls -1vd -- *

It's not equivalent to sort -h because sort -h is to sort things like the output of du -h, that is where 1.12G is bigger than 999M, while ls -v and zsh's (n) globbing qualifiers are geared towards sorting things like version numbers (where 1.20 is greater than 1.4 for instance).

If you do want a globbing that understand those suffices, you'd need to write a zsh sort function for that.

With zsh, you can define glob sorting order with a function and use it as *(o+that-function). that-function takes the filename to sort in the $REPLY variable and should return into that same variable, something that zsh can then sort lexicographically (or numerically if n is also provided).

Something like:

h() {
  local -A x
  local match
  setopt localoptions extendedglob
  x=(k 1 K 1 M 2 G 3 T 4 P 5 E 6)
  REPLY=${REPLY//(#b)((|[0-9]#.)[0-9]##)([${(kj::)x}])/$((match[1]*2**$x[$match[3]]0))}
}

would replaces all the 1.1G to their value (1181116006.4).

Which you can then use as:

ls -1Ud -- *(no+h)

(-U being the GNU ls option to tell ls not to sort its arguments).

That won't work well however with numbers with a fractional part as it would sort 1.20 after 1.3 for instance:

$ ls
1.20  1.3  3.1G  500M
$ ls -v1Ud -- *(no+h)
1.3
1.20
500M
3.1G

For something that would sort all sorts of decimal floating numbers with optional suffices (like -1e20M, 1E-20, 100k, 3000M), because zsh can only sort lexicographically or numerically limited to positive decimal integers, we'd need a function to convert those into either strings that sort lexicographically in the same order, or positive decimal integers that sort numerically in the same order.

zsh does floating point and decimal arithmetic with 64bit numbers where available, so we could apply a floating point function that turns those numbers (or at least the range supported by zsh floats) into an integer from 0 to 263. Using the logarithm function could be an option. Something like:

zmodload zsh/mathfunc
h() {
  local -A x
  local match v
  setopt localoptions extendedglob
  x=(k 1 K 1 M 2 G 3 T 4 P 5 E 6 Z 7 Y 8)
  REPLY=${REPLY//(#b)([-+]|)(([0-9]#.|)[0-9]##([eE]([-+]|)[0-9]##|))\
([${(kj::)x}]|)/[$(([#10]1e15*(1500+ $match[1](745+log((v=$match[2]\
${${match[2]##*[Ee.]*}:+.}*2.**$x[$match[6]]0)==0?2.48e-324:v)))))]}
}

To be used as:

print -rl -- *(no+h)

Or, to fall back to numerical sorting to differentiate 100000000000000001 and 100000000000000002 for instance (which have same logarithm with zsh float precision):

print -rl -- *(noe:h:on)

Then, we get something even better than sort -h:

$ ls | sort -h
-4
-3
+20
a2
b1
b10
b2
1e-20Y
1e3
1.20
1.3
3
20
999
1000 1e9
1000 900M
1001
1024
1k
12k
0.000000001G

$ print -rl -- *(noe:h:on)
-4
-3
0.000000001G
1.20
1.3
3
20
+20
999
1e3
1000 900M
1000 1e9
1001
1k
1024
1e-20Y
12k
a2
b1
b2
b10

Now, for your specific nyxmms2 problem, to complement Gilles' answer, I you're a lazy typist, you could also define this time a selection (instead of "ordering") function for music files, like:

m() [[ $REPLY == (#i)*.(mp3|ogg)(-.) ]]

nyxmms2 *Zep*(n+m)

Or use a variable:

m='.(#i)(mp3|ogg)(n-.)'
nyxmms2 *Zep*$~m

Or a global alias:

alias -g @m='./*.(#i)(mp3|ogg)(n-.)'
nyxmms2 @m

To turn on numericglobsort for nyxmms2 only, you could do:

preexec() {[[ $1 = nyxmms2* ]] && setopt numericglobsort}
precmd setopt nonumericglobsort
share|improve this answer
add comment

you could always use shell expansion to do this.

nyxmms2 add $(ls /path/to/dir/*.mp3 | sort -n)

since you're dealing with music you might create "playlists"

ls /path/to/album/or/song/*.mp3 | sort -n >> /path/to/playlist
nyxmms2 add < /path/to/playlist
share|improve this answer
    
As I said in the question, I was hoping not to have to use pipes and sort but for some magic with zsh that would allow it to do that for me when it expands *. –  Sardathrion Apr 26 '13 at 13:40
    
@Sardathrion why don't you want to use pipes? this is what they are for..... –  h3rrmiller Apr 26 '13 at 14:10
    
Pure unadulterated laziness and the thought that there might be something within zsh that allows you to set the sorting order. –  Sardathrion Apr 26 '13 at 14:26
    
@Sardathrion there is but it wont do interpretive sort like sort -n... with zsh you could use parameter expansion to sort but it will not interpret 2 as being greater than 10 (ls *(n)) –  h3rrmiller Apr 26 '13 at 14:47
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.