如何在 bash 中从目录中选择随机文件?

我有一个大约有2000个文件的目录。如何通过使用 bash 脚本或管道命令列表来选择 N文件的随机样本?

137298 次浏览

下面是一个使用 GNU sort 随机选项的脚本:

ls |sort -R |tail -$N |while read file; do
# Something involving $file, or you can leave
# off the while to just get the filenames
done

下面是一些不解析 ls输出的可能性,对于名称中带有空格和有趣符号的文件,它们是100% 安全的。它们都将用随机文件列表填充数组 randf。如果需要,这个数组很容易用 printf '%s\n' "${randf[@]}"打印。

  • 这个文件可能会多次输出同一个文件,而且需要提前知道 N。这里我选择 N = 42。

    a=( * )
    randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )
    

    这个特性没有很好的记录

  • 如果事先不知道 N,但是你真的很喜欢以前的可能性,你可以使用 eval。但是它是邪恶的,而且您必须确保 N不会直接来自用户输入而没有被彻底检查!

    N=42
    a=( * )
    eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
    

    我个人不喜欢 eval,因此这个答案!

  • 使用更简单的方法(循环) :

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
    randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
    
  • If you don't want to possibly have several times the same file:

    N=42
    a=( * )
    randf=()
    for((i=0;i<N && ${#a[@]};++i)); do
    ((j=RANDOM%${#a[@]}))
    randf+=( "${a[j]}" )
    a=( "${a[@]:0:j}" "${a[@]:j+1}" )
    done
    

Note. This is a late answer to an old post, but the accepted answer links to an external page that shows terrible practice, and the other answer is not much better as it also parses the output of ls. A comment to the accepted answer points to an excellent answer by Lhunath which obviously shows good practice, but doesn't exactly answer the OP.

您可以使用 shuf(来自 GNU coreutils 包)。只要给它一个文件名列表,并要求它从一个随机排列中返回第一行:

ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..

调整 -n, --head-count=COUNT值以返回所需的行数。例如,返回5个随机的文件名,您将使用:

find dirname -type f | shuf -n 5

这是我能在 MacOS 上使用 bash 的唯一脚本。我合并并编辑了以下两个链接中的片段:

Ls 命令: 如何得到一个递归的全路径列表,每个文件一行

http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/

#!/bin/bash


# Reads a given directory and picks a random file.


# The directory you want to use. You could use "$1" instead if you
# wanted to parametrize it.
DIR="/path/to/"
# DIR="$1"


# Internal Field Separator set to newline, so file names with
# spaces do not break our script.
IFS='
'


if [[ -d "${DIR}" ]]
then
# Runs ls on the given dir, and dumps the output into a matrix,
# it uses the new lines character as a field delimiter, as explained above.
#  file_matrix=($(ls -LR "${DIR}"))


file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }'))
num_files=${#file_matrix[*]}


# This is the command you want to run on a random file.
# Change "ls -l" by anything you want, it's just an example.
ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi


exit 0

我使用这个: 它使用临时文件,但是深入到一个目录中,直到找到一个常规文件并返回它。

# find for a quasi-random file in a directory tree:


# directory to start search from:
ROOT="/";


tmp=/tmp/mytempfile
TARGET="$ROOT"
FILE="";
n=
r=
while [ -e "$TARGET" ]; do
TARGET="$(readlink -f "${TARGET}/$FILE")" ;
if [ -d "$TARGET" ]; then
ls -1 "$TARGET" 2> /dev/null > $tmp || break;
n=$(cat $tmp | wc -l);
if [ $n != 0 ]; then
FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
#       r=$(($RANDOM % $n)) ;
#       FILE=$(tail -n +$(( $r + 1 ))  $tmp | head -n 1);
fi ;
else
if [ -f "$TARGET"  ] ; then
rm -f $tmp
echo $TARGET
break;
else
# is not a regular file, restart:
TARGET="$ROOT"
FILE=""
fi
fi
done;

如果已经安装了 Python (可以使用 Python2或 Python3) :

若要选择一个文件(或从任意命令中选择一行) ,请使用

ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"

要选择 N文件/行,请使用(注意,N在命令的末尾,将其替换为一个数字)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N

这是一个更晚的回复,我刚刚投了赞成票,因为这是迄今为止最好的答案,超过了两次。(一次用于避免 eval,一次用于安全文件名处理。)

但是我花了几分钟来理清这个答案所使用的“没有很好的文档记录”的特性。如果您的 Bash 技能足够扎实,您可以立即看到它是如何工作的,那么跳过此评论。但是我没有,而且解决了这个问题,我认为这是值得解释的。

特写 # 1是 shell 自己的文件 globbing。a=(*)创建一个数组 $a,其成员是工作目录中的文件。Bash 理解文件名的所有怪异之处,所以这个列表保证是正确的,保证是转义的,等等。无需担心正确解析 ls返回的文本文件名。

第二部数组的 Bash 参数展开式参数展开式,一个嵌套在另一个中。这从 ${#ARRAY[@]}开始,它扩展到 $ARRAY的长度。

然后使用该展开为数组下标。找到1到 N 之间的随机数的标准方法是取随机数模 N 的值。我们需要一个介于0和数组长度之间的随机数。为了清楚起见,这里分成两行:

LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}

但是这个解决方案在一行中完成,去除了不必要的变量赋值。

第三部Bash 支撑扩展,虽然我不得不承认我不完全理解它。例如,使用大括号展开生成一个名为 filename1.txtfilename2.txt等的25个文件的列表: echo "filename"{1..25}".txt"

上面子 shell 中的表达式 "${a[RANDOM%${#a[@]}]"{1..42}"}"使用这个技巧生成42个独立的扩展。大括号展开在 ]}之间放置一个数字,一开始我以为它是数组的下标,但如果是这样的话,它会在前面加一个冒号。(它也会从数组中的一个随机点返回42个连续项,这与从数组中返回42个随机项完全不同。)我认为它只是让 shell 运行扩展42次,从而从数组中返回42个随机项。(但如果有人能更全面地解释一下,我很乐意听听。)

N 必须硬编码(到42)的原因是大括号展开在变量展开之前发生。

最后,这里是 第四部,如果你想递归地为一个目录层次结构做这件事:

shopt -s globstar
a=( ** )

这将打开导致 **递归匹配的 外壳选项。现在,$a数组包含整个层次结构中的每个文件。

来一个康先生稍微修改过的 Perl 解决方案怎么样: 如何在 Unix 命令行或 shell 脚本中对文本文件的行进行洗牌?

< p > $ls | perl-MList: : Util = shuffle-e’@lines = shuffle (< >) ; print @ lines [0. . 4]”

一个简单的解决方案,选择 5随机文件,而 避免解析 ls。它还可以处理包含空格、换行符和其他特殊字符的文件:

shuf -ezn 5 * | xargs -0 -n1 echo

用要为文件执行的命令替换 echo

ls | shuf -n 10 # ten random files

MacOS 没有 Sort-R命令,所以我只需要一个 bash 解决方案,随机化所有文件 没有副本,这里没有找到。这个解决方案类似于 niourf _ niourf 的解决方案 # 4,但希望能添加更好的注释。

脚本应该很容易修改,以便在 N.个示例使用 if 计数器之后停止,或者 niourf _ niourf’s for 循环使用 N. $RANDOM 限制在32000个文件之内,但是在大多数情况下应该可以做到这一点。

#!/bin/bash


array=(*)  # this is the array of files to shuffle
# echo ${array[@]}
for dummy in "${array[@]}"; do  # do loop length(array) times; once for each file
length=${#array[@]}
randomi=$(( $RANDOM % $length ))  # select a random index


filename=${array[$randomi]}
echo "Processing: '$filename'"  # do something with the file


unset -v "array[$randomi]"  # set the element at index $randomi to NULL
array=("${array[@]}")  # remove NULL elements introduced by unset; copy array
done

如果文件夹中有更多文件,可以使用我在 unix堆栈交换中找到的以下管道命令。

find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/

这里我想要复制文件,但是如果你想移动文件或者做其他事情,只需要改变我使用 cp的最后一个命令。

如果要将这些文件的示例复制到另一个文件夹:

ls | shuf -n 100 | xargs -I % cp % ../samples/

显然,首先使示例目录。