16

Is there a way to list a set of say, 30 random files from a directory using standard Linux commands? (in zsh)

The top answer described here does not work for me (sort does not recognize the option -R)

Amelio Vazquez-Reina
  • 40,169
  • 77
  • 197
  • 294

5 Answers5

20

Try piping the ls output to shuf, e.g.

$ touch 1 2 3 4 5 6 7 8 9 0
$ ls | shuf -n 5
5
9
0 
8
1

The -n flag specifies how many random files you want.

Renan
  • 16,976
  • 8
  • 69
  • 88
8

Since you're mentioning zsh:

rand() REPLY=$RANDOM
print -rl -- *(o+rand[1,30])

You can replace print with say ogg123 and * with say **/*.ogg

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
3

A simple solution that avoids parsing of ls and also works with spaces:

shuf -en 30 dir/* | while read file; do
    echo $file
done
scai
  • 10,543
  • 2
  • 25
  • 42
2

It's quite easy to solve this with a tiny bit of Perl. Select four files at random from the current directory:

perl -MList::Util=shuffle -e 'print shuffle(`ls`)' | head -n 4

For production use though, I would go with an expanded script that doesn't rely on ls output, can accept any dir, checks your args, etc. Note that the random selection itself is still only a couple of lines.

#!/usr/bin/perl    
use strict;
use warnings;
use List::Util qw( shuffle );

if ( @ARGV < 2 ) {
    die "$0 - List n random files from a directory\n"
        . "Usage: perl $0 n dir\n";
}
my $n_random = shift;
my $dir_name = shift;
opendir(my $dh, $dir_name) || die "Can't open directory $dir_name: $!";

# Read in the filenames in the directory, skipping '.' and '..'
my @filenames = grep { !/^[.]{1,2}$/ } readdir($dh);
closedir $dh;

# Handle over-specified input
if ( $n_random > $#filenames ) {
    print "WARNING: More values requested ($n_random) than available files ("
          . @filenames . ") - truncating list\n";
    $n_random = @filenames;
}

# Randomise, extract and print the chosen filenames
foreach my $selected ( (shuffle(@filenames))[0..$n_random-1] ) {
    print "$selected\n";
}
ire_and_curses
  • 12,232
  • 3
  • 38
  • 33
  • Imho a Perl-script is not a 'standard unix command'. This problem is easy to solve in any high-level scripting language such as Perl, Python or Ruby, but more interesting if it's directly on the commandline. – gerrit Sep 18 '12 at 07:42
  • 1
    @gerrit - I take your point. I provided this because the preferred answer (using `shuf`) was not sufficient for the OP. As to what is and isn't standard Unix - Perl exists everywhere, and it solves the problem succinctly and robustly. If I'd given this as a Perl one-liner, would that have counted as 'command line'? Is the fact Perl is powerful really an argument against the existence of this answer? – ire_and_curses Sep 18 '12 at 14:01
  • I'd say it's an answer to a different question. Of course in any fully-featured programming language this problem is trivial, for me the interesting part of the question is if it can be done /without/ resorting to a ~20 LOC script; scripting languages are powerful, but I don't think Perl classifies as a *command* (such as the OP was asking for; and no, if you'd given a 200-character commandline invoking Perl I'd have had the same opinion). – gerrit Sep 18 '12 at 14:39
  • @gerrit - Perhaps I can be clearer. Take a look at my edit: `perl -MList::Util=shuffle -e 'print shuffle(`ls`)' | head -n 4` Is this more like what you were looking for? – ire_and_curses Sep 18 '12 at 15:02
  • Yeah, I removed my downvote which admittedly was a bit harsh. – gerrit Sep 18 '12 at 15:28
0

A oneliner using nothing but Zsh:

files=(*); for x in {1..30}; do i=$((RANDOM % ${#files[@]} + 1)); echo "${files[i]}"; done

The same in Bash, where array indices are zero-based:

files=(*); for x in {1..30}; do i=$((RANDOM % ${#files[@]})); echo "${files[i]}"; done

Note that neither version takes duplicates into account.

janmoesen
  • 2,680
  • 17
  • 16
  • 1
    This may list the same file more than once. Avoiding this is a significant part of the problem. – Gilles 'SO- stop being evil' Sep 18 '12 at 23:40
  • Gilles: quite right, which is why I said "Note that neither version takes duplicates into account." You could still do it in pure Bash, if you do not care about time complexity and rebuild the array each time you remove a random one from it. Not even close to a one-liner, then, but of course that was not a requirement. – janmoesen Sep 22 '12 at 20:54