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

如何从bash中的目录中选择随机文件?,bash,random,Bash,Random,我有一个大约2000个文件的目录。如何通过使用bash脚本或管道命令列表选择N个文件的随机样本?下面是一个使用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输出的可能性,对于名称中带有空格和有趣符号的文

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

下面是一个使用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
如果不希望同一个文件多次出现,请执行以下操作:

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
注意。这是对一篇旧文章的一个迟来的回答,但被接受的回答链接到一个显示糟糕做法的外部页面,而另一个回答也没有太好,因为它还解析ls的输出。对已接受答案的评论指向了Lhunah的一个优秀答案,该答案显然显示了良好的实践,但并不完全符合OP的要求。

您可以使用GNU coreutils软件包中的shuf。只需向其提供一个文件名列表,并要求其返回随机排列的第一行:

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的唯一脚本。我合并并编辑了以下两个链接中的片段:


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

# 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,则可以使用Python 2或Python 3:

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

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

这是对@gniourf_gniourf迟来的回答的一个更晚的回应,我只是投了更高的票,因为这是迄今为止最好的答案,超过两次。一次用于避免评估,一次用于安全文件名处理

但我花了几分钟的时间来理清这个答案使用的那些没有很好记录的特性。如果您的Bash技能足够可靠,您可以立即看到它的工作原理,那么请跳过此评论。但我没有,在解开它之后,我认为这是值得解释的

功能1是shell自己的文件全局绑定。a=*创建一个数组,$a,其成员是当前目录中的文件。Bash理解文件名的所有奇怪之处,因此列表保证正确、保证转义等。无需担心正确解析ls返回的文本文件名

特性2是Bash for,一个嵌套在另一个中。这从${ARRAY[@]}开始,它扩展到$ARRAY的长度

然后使用该扩展为数组下标。找到1到N之间的随机数的标准方法是取随机数模N的值。我们想要一个介于0和数组长度之间的随机数。以下是为清晰起见分为两行的方法:

LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}
但是这个解决方案只在一行中完成,消除了不必要的变量赋值

特征3是,尽管我不得不承认我并不完全理解它。例如,大括号扩展用于生成名为filename1.txt、filename2.txt等25个文件的列表:echo filename{1..25}.txt

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

N必须硬编码为42的原因是支架展开发生在变量展开之前

最后,这里是功能4,如果您希望对目录层次结构递归执行此操作:

shopt -s globstar
a=( ** )

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

一个Perl解决方案怎么样?这个方案是由Kang先生稍微修改的:

$ls | perl-MList::Util=shuffle-e'@lines=shuffle;打印 @第[0..4]行'


选择5个随机文件的简单解决方案 虽然它还适用于包含空格、换行符和其他特殊字符的文件:

shuf -ezn 5 * | xargs -0 -n1 echo
将echo替换为您要为文件执行的命令。

MacOS没有sort-R和shuf命令,因此我需要一个仅限bash的解决方案,该解决方案可以随机化所有文件而不重复,但在这里找不到。此解决方案类似于gniourf_gniourf的解决方案4,但希望能添加更好的注释

ls | shuf -n 10 # ten random files
使用带有if的计数器或带有N$RANDOM的gniourf_gniourf's for循环,脚本应该可以很容易地修改为在N个样本后停止。限制为~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中找到的以下管道命令


在这里,我想复制文件,但如果您想移动文件或执行其他操作,只需更改我使用cp的最后一个命令。

Cool,不知道sort-R;我以前使用过bogosort:-psort:invalid选项-R有关详细信息,请尝试“排序-帮助”。这似乎不适用于包含空格的文件。这应该适用于管道处理行中包含空格的文件。它不适用于带有换行符的名称。只有$file的使用(未显示)会对空格敏感。OP希望选择N个随机文件,因此使用1有点误导。如果文件名带有换行符:find dirname-type f-print0 | shuf-zn1如果我必须将这些随机选择的文件复制到另一个文件夹,该怎么办?如何对这些随机选择的文件执行操作?它是否也可以直接打开其完整路径?如果您想使用完整路径:find$pwd | shuf-n 1在Unix和Linux上也是一个很好的答案:ls | shuf-n 5类似:第一个和第二个生成的错误替换;它不喜欢{1..42}部分留下尾随的1。另外,$RANDOM仅为15位,并且该方法无法处理超过32767个可供选择的文件。您不应该依赖ls的输出。例如,如果文件名包含换行符,这将不起作用。@b但您似乎被文件名中的换行符困扰:。它们真的那么普遍吗?换句话说,是否有某种工具可以创建名称中带有换行符的文件?因为作为一个用户,很难创建这样的文件名。对于来自internet@CiprianTomoiaga这就是你可能遇到的问题的一个例子。ls不能保证为您提供干净的文件名,所以您不应该依赖它。这些问题很少或不寻常的事实并不能改变问题;特别是考虑到有更好的解决方案,ls可能包括目录和空行。我建议你找一个-键入f | shuf-n10。@cherdt也-maxdepth 1如果文件名包含换行符,则此操作不起作用。那么,管道+读取与解析ls不存在相同的问题吗?也就是说,它逐行读取,所以它不适用于名称中有换行符的文件。我以前的解决方案不适用于包含换行符的文件名,并且可能会在其他文件名上使用某些特殊字符。我已经更新了我的答案,使用空终止,而不是换行符。
#!/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
find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/