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


Answers:


180

这是一个使用GNU sort的random选项的脚本:

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

太酷了,不知道排序-R; 我以前使用过bogosort :-p
Alex

5
sort:无效的选项-R尝试`sort --help'以获取更多信息。

2
似乎不适用于其中包含空格的文件。
Houshalter

这应该适用于带有空格的文件(管道处理行)。对于其中包含换行符的名称,它不起作用。仅使用("$file"未显示)对空格敏感。
Yann Vernier


107

您可以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

4
OP希望选择N随机文件,因此使用1有点误导。
aioobe 2014年

4
如果文件名带有换行符:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

4
如果必须将这些随机选择的文件复制到另一个文件夹怎么办?如何对这些随机选择的文件执行操作?
利沙伯·阿格拉哈里(Rishabh Agrahari)

18

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。对已接受答案的评论指出Lhunath的出色答案,这显然表明了良好的作法,但并未完全回答OP。


第一和第二产生了“不良替代”;它不喜欢"{1..42}"留下尾随的部分"1"。此外,$RANDOM仅15位,并且该方法不适用于超过32767个文件可供选择。
Yann Vernier

12
ls | shuf -n 10 # ten random files

1
您不应该依赖的输出ls。如果文件名包含换行符,则此方法将无效。
bfontaine

3
@bfontaine您似乎在文件名:)中被换行符困扰。他们真的那么常见吗?换句话说,是否有一些工具可以创建名称中包含换行符的文件?由于作为用户,创建这样的文件名非常困难。对于来自互联网的文件也是如此
CiprianTomoiagă18年

3
@CiprianTomoiaga这是您可能遇到的问题的一个示例。ls不能保证为您提供“干净的”文件名,因此您不应该依赖它。这些问题很少或罕见的事实并不能改变问题。特别是考虑到有更好的解决方案。
bfontaine

ls可能包括目录和空白行。我会建议类似的东西find . -type f | shuf -n10
cherdt

9

选择5随机文件同时避免解析ls的简单解决方案。它还适用于包含空格,换行符和其他特殊字符的文件:

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

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


1
好吧,管道+ read与解析没有同样的问题ls吗?也就是说,它逐行读取,因此不适用于名称中带有换行符的文件
CiprianTomoiagă18年

3
你是对的。我以前的解决方案不适用于包含换行符的文件名,并且可能还会破坏带有某些特殊字符的其他文件名。我已经更新了答案,以使用空终止而不是换行符。
scai

4

如果您安装了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

如果文件名包含换行符,则此方法不起作用。
bfontaine

4

这是对@gniourf_gniourf的最新答案的一个更晚的答复,我刚刚投票赞成,因为它是迄今为止最好的答案,超过了两倍。(一次可以避免eval,一次可以安全地处理文件名。)

但是,我花了几分钟的时间才能弄清此答案使用的“记录不充分”的功能。如果您的Bash技能足够扎实,可以立即看到它的工作原理,请跳过此注释。但是我没有,并且理清了它,我认为值得解释。

功能#1是Shell自己的文件。a=(*)创建一个数组,$a其成员是当前目录中的文件。Bash理解文件名的所有怪异之处,因此可以确保列表正确无误,可以确保转义等。无需担心正确解析由返回的文本文件名ls

功能#2是击参数扩展阵列,一个嵌套在另一个。开头为${#ARRAY[@]},扩展为的长度$ARRAY

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

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

但是此解决方案只需一行即可完成,从而消除了不必要的变量分配。

功能#3Bash括号扩展,尽管我不得不承认我并不完全理解它。括号扩展使用,例如,产生的25个文件命名列表filename1.txtfilename2.txt等等:echo "filename"{1..25}".txt"

上面子外壳内的表达式"${a[RANDOM%${#a[@]}]"{1..42}"}"使用该技巧来产生42个单独的扩展。花括号扩展在]和之间放置了一个数字},起初我以为是在数组下标,但是如果是这样,它前面会加一个冒号。(它还会从数组中的随机点返回42个连续项,这与从数组中返回42个随机项完全不一样。)我认为这只是使Shell运行扩展42次,从而返回数组中的42个随机项。(但如果有人可以更全面地解释它,我很想听听。)

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

最后,如果您想递归地对目录层次结构执行此操作,请参见功能4

shopt -s globstar
a=( ** )

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


2

如果文件夹中有更多文件,则可以使用以下在unix stackexchange中找到的管道命令。

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

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


1

这是我在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

1

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

该脚本应该易于修改,以便在使用N的计数器或gniourf_gniourf的for循环使用N进行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

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;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.