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)

1
  • i'm curious what OS you're on. Commented Sep 18, 2012 at 22:03

5 Answers 5

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.

5
  • command not found (not installed) :/ Commented Sep 18, 2012 at 2:23
  • Strange... it should be a part of coreutils. If your coreutils is old, then the command won't be there. Commented Sep 18, 2012 at 2:24
  • 6
    This is the quick and dirty way, and I would use it, too — for a one-off script. For anything more durable, avoid parsing the output of ls. Commented Sep 18, 2012 at 15:53
  • @janmoesen I didn't know about this. Thanks! Commented Sep 18, 2012 at 15:55
  • @Renan Not every Unix has GNU coreutils installed by default. Commented Aug 30, 2017 at 8:22
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

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
2
  • As seen in other comments, the OP is on a system without shuf. Commented Aug 30, 2017 at 8:23
  • Avoiding shuf isn't mentioned in the question. This answer will still be helpful to other users. Commented Aug 30, 2017 at 8: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";
}
5
  • 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. Commented Sep 18, 2012 at 7: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? Commented Sep 18, 2012 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). Commented Sep 18, 2012 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? Commented Sep 18, 2012 at 15:02
  • Yeah, I removed my downvote which admittedly was a bit harsh. Commented Sep 18, 2012 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.

2
  • 1
    This may list the same file more than once. Avoiding this is a significant part of the problem. Commented Sep 18, 2012 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. Commented Sep 22, 2012 at 20:54

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.