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.

Inspired by the accepted answer to this thread: List X random files from a directory, I tried to put together two lines that would pick one file from each subfolder of my current directory, with no success:

rand() REPLY=$RANDOM
for x in *; do y=$($x/*(o+rand[1])); print ${y:r}; done

It looks like the expansion of y is not working properly. Why?

What's wrong with the code above?

share|improve this question

2 Answers 2

up vote 2 down vote accepted

It is not the expansion of y, but the value being assigned to y that is the problem.

You have this

y=$($x/*(o+rand[1]))

The value being assigned is the result of the command substitution ($(…)). The only “word” in the substituted command is the glob expression. This means that it will try to run the selected file as a command (which will probably cause an error, and produce no useful output).

To get it to work (although with a change in semantics), you can change your command substitution assignment into an array assignment (i.e. remove the $ after the =):

y=($x/*(o+rand[1]))

If you want to keep the command substitution, you need to make it output the selected pathname instead of trying to execute it as a command:

y=$(print -- $x/*(o+rand[1]))

But there is a hidden issue here. Each glob will be evaluated in a separate subshell, so each directory will use the same sequence of random numbers to sort its entries. You can observe this if you use set -x and look at the sequence of values being assigned to REPLY for each directory. This means that the files will not be selected independently across directories.

Here are some alternate approaches that you can use to assign a glob expansion to a parameter (these keep the $RANDOM evaluation in the same shell, so the selections will be (more) independent):

for y in $x/*(o+rand[1]); do print -- ${y:r}; done
set -- $x/*(o+rand[1]); print -l -- ${@:r}

If you want to handle empty directories, then you should probably add the N glob qualifier (acts as if NULL_GLOB were set). This will also help handle non-directories that match the outer glob (though you could use the / glob qualifier to limit that one to just directories).

rand() REPLY=$RANDOM
for x in *(/); do y=($x/*(No+rand[1])); print -l -- ${y:r}; done
share|improve this answer

You can add modifiers to globbing qualifiers:

for x (*(/)) print -rl -- $x/*(No+rand[1]:r)

See also:

print -rl -- *(N/e{'REPLY=($REPLY/*(No+rand[1]:r))'})
share|improve this answer

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.