13

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)

  • i'm curious what OS you're on. – ixtmixilix Sep 18 '12 at 22:03
7

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

18

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.

  • command not found (not installed) :/ – Amelio Vazquez-Reina Sep 18 '12 at 2:23
  • Strange... it should be a part of coreutils. If your coreutils is old, then the command won't be there. – Renan Sep 18 '12 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. – janmoesen Sep 18 '12 at 15:53
  • @janmoesen I didn't know about this. Thanks! – Renan Sep 18 '12 at 15:55
  • @Renan Not every Unix has GNU coreutils installed by default. – Kusalananda Aug 30 '17 at 8:22
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";
}
  • 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 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? – 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
1

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

shuf -en 30 dir/* | while read file; do
    echo $file
done
  • As seen in other comments, the OP is on a system without shuf. – Kusalananda Aug 30 '17 at 8:23
  • Avoiding shuf isn't mentioned in the question. This answer will still be helpful to other users. – scai Aug 30 '17 at 8:42
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.

  • 1
    This may list the same file more than once. Avoiding this is a significant part of the problem. – Gilles 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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