Best way to choose a random file from a directory in a shell script

后端 未结 11 1489
长情又很酷
长情又很酷 2020-12-13 02:00

What is the best way to choose a random file from a directory in a shell script?

Here is my solution in Bash but I would be very interested for a more portable (non-

相关标签:
11条回答
  • 2020-12-13 02:11

    Is "shuf" not portable?

    shuf -n1 -e /path/to/files/*
    

    or find if files are deeper than one directory:

    find /path/to/files/ -type f | shuf -n1
    

    it's part of coreutils but you'll need 6.4 or newer to get it... so RH/CentOS does not include it.

    0 讨论(0)
  • 2020-12-13 02:14

    My 2 cents, with a version that should not break when filenames with special chars exist:

    #!/bin/bash --
    dir='some/directory'
    
    let number_of_files=$(find "${dir}" -type f -print0 | grep -zc .)
    let rand_index=$((1+(RANDOM % number_of_files)))
    
    printf "the randomly-selected file is: "
    find "${dir}" -type f -print0 | head -z -n "${rand_index}" | tail -z -n 1
    printf "\n"
    
    0 讨论(0)
  • 2020-12-13 02:18

    Here's a shell snippet that relies only on POSIX features and copes with arbitrary file names (but omits dot files from the selection). The random selection uses awk, because that's all you get in POSIX. It's a very poor random number generator, since awk's RNG is seeded with the current time in seconds (so it's easily predictable, and returns the same choice if you call it multiple times per second).

    set -- *
    n=$(echo $# | awk '{srand(); print int(rand()*$0) + 1}')
    eval "file=\$$n"
    echo "Processing $file"
    

    If you don't want to ignore dot files, the file name generation code (set -- *) needs to be replaced by something more complicated.

    set -- *; [ -e "$1" ] || shift
    set .[!.]* "$@"; [ -e "$1" ] || shift
    set ..?* "$@"; [ -e "$1" ] || shift
    if [ $# -eq 0]; then echo 1>&2 "empty directory"; exit 1; fi
    

    If you have OpenSSL available, you can use it to generate random bytes. If you don't but your system has /dev/urandom, replace the call to openssl by dd if=/dev/urandom bs=3 count=1 2>/dev/null. Here's a snippet that sets n to a random value between 1 and $#, taking care not to introduce a bias. This snippet assumes that $# is at most 2^23-1.

    while
      n=$(($(openssl rand 3 | od -An -t u4) + 1))
      [ $n -gt $((16777216 / $# * $#)) ]
    do :; done
    n=$((n % $#))
    
    0 讨论(0)
  • 2020-12-13 02:25

    Put each line of output from the command 'ls' into an associative array named line and then choose one of those like so...

    ls | awk '{ line[NR]=$0 } END { print line[(int(rand()*NR+1))]}'
    
    0 讨论(0)
  • 2020-12-13 02:27

    This boils down to: How can I create a random number in a Unix script in a portable way?

    Because if you have a random number between 1 and N, you can use head -$N | tail to cut somewhere in the middle. Unfortunately, I know no portable way to do this with the shell alone. If you have Python or Perl, you can easily use their random support but AFAIK, there is no standard rand(1) command.

    0 讨论(0)
  • 2020-12-13 02:27

    Newlines in file-names can be avoided by doing the following in Bash:

    #!/bin/sh
    
    OLDIFS=$IFS
    IFS=$(echo -en "\n\b")
    
    DIR="/home/user"
    
    for file in $(ls -1 $DIR)
    do
        echo $file
    done
    
    IFS=$OLDIFS
    
    0 讨论(0)
提交回复
热议问题