【问题标题】:How can I select random files from a directory in bash?如何从 bash 的目录中选择随机文件?
【发布时间】:2010-09-29 16:10:44
【问题描述】:

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

【问题讨论】:

标签: bash random


【解决方案1】:

这是一个使用 GNU 排序的随机选项的脚本:

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
  • sort: 无效选项 -- R 尝试使用 `sort --help' 获取更多信息。
  • 似乎不适用于包含空格的文件。
  • 这应该适用于带有空格的文件(管道处理行)。它不适用于带有换行符的名称。只有使用"$file"(未显示)会对空格敏感。
【解决方案2】:

您可以为此使用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

【讨论】:

  • OP 想选择N 随机文件,所以使用1 有点误导。
  • 如果文件名带有换行符:find dirname -type f -print0 | shuf -zn1
  • 如果我必须将这些随机选择的文件复制到另一个文件夹怎么办?如何对这些随机选择的文件进行操作?
  • 它也可以打开它的完整路径目录吗?
  • 请注意,如果您使用的是装有 OS X 和 zsh 的 Mac,则可能无法安装 GNU shuf 命令。所以你必须先安装 shuf,或者直接使用 Josh Lee 的方法。
【解决方案3】:

以下是一些不解析 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 个文件可供选择。
【解决方案4】:
ls | shuf -n 10 # ten random files

【讨论】:

  • 你不应该依赖ls的输出。如果例如,这将不起作用文件名包含换行符。
  • @bfontaine 你似乎被文件名中的换行符困扰:)。它们真的那么普遍吗?换句话说,是否有一些工具可以创建名称中带有换行符的文件?由于作为用户很难创建这样的文件名。来自互联网的文件也是如此
  • @CiprianTomoiaga 这是您可能遇到的问题的一个示例。 ls 不能保证给你“干净”的文件名,所以你不应该依赖它,句号。这些问题很少见或不寻常的事实并不能改变问题;特别是考虑到对此有更好的解决方案。
  • ls 可能包括目录和空行。我会建议像find . -type f | shuf -n10 这样的东西。
  • @cherdt 还有-maxdepth 1
【解决方案5】:

avoiding to parse ls 时选择5 随机文件的简单解决方案。它也适用于包含空格、换行符和其他特殊字符的文件:

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

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

【讨论】:

  • 好吧,管道+read和解析ls有同样的问题吗?也就是说,它逐行读取,因此它不适用于名称中带有换行符的文件
  • 你是对的。我以前的解决方案不适用于包含换行符的文件名,并且可能会在其他具有某些特殊字符的文件名上中断。我已经更新了我的答案以使用空终止而不是换行符。
  • 我喜欢它。 shuf -ezn 10 ${RAWDATA_DIR}/* | xargs -0 -n1 echo 。谢谢
【解决方案6】:

这是对@gniourf_gniourf 迟到的答案的更晚回应,我只是赞成,因为它是迄今为止最好的答案,两次。 (一次用于避免eval,一次用于安全文件名处理。)

但是我花了几分钟来解开这个答案使用的“没有很好记录”的功能。如果您的 Bash 技能足够扎实,可以立即看到它的工作原理,请跳过此评论。但我没有,解开后我认为值得解释。

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

功能 #2 是 Bash parameter expansions for arrays,一个嵌套在另一个中。这以${#ARRAY[@]} 开头,扩展为$ARRAY 的长度。

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

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

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

功能#3Bash brace expansion,尽管我不得不承认我并不完全理解它。例如,使用大括号扩展来生成名为filename1.txtfilename2.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=( ** )

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

【讨论】:

    【解决方案7】:

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

    【讨论】:

    • 如果您的文件名包含换行符,这将不起作用。
    【解决方案8】:

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

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

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

    【讨论】:

      【解决方案9】:

      这是我可以在 MacOS 上很好地使用 bash 的唯一脚本。我结合并编辑了以下两个链接的 sn-ps:

      ls command: how can I get a recursive full-path listing, one line per file?

      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
      

      【讨论】:

        【解决方案10】:

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

        脚本应该很容易修改,以便在使用带有 if 的计数器或带有 N 的 gniourf_gniourf 的 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
        

        【讨论】:

          【解决方案11】:

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

          # 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;
          

          【讨论】:

            【解决方案12】:

            这里的 Perl 解决方案如何从康先生那里稍微修改一下:
            How can I shuffle the lines of a text file on the Unix command line or in a shell script?

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

            【讨论】:

              猜你喜欢
              • 2020-08-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-06-03
              相关资源
              最近更新 更多