【问题标题】:How do I pick random unique lines from a text file in shell?如何从 shell 中的文本文件中选择随机的唯一行?
【发布时间】:2012-04-12 06:47:47
【问题描述】:

我有一个行数未知的文本文件。我需要随机抓取其中一些行,但我不希望有任何重复的风险。

我试过这个:

jot -r 3 1 `wc -l<input.txt` | while read n; do
  awk -v n=$n 'NR==n' input.txt
done

但这很丑陋,并且不能防止重复。

我也试过这个:

awk -vmax=3 'rand() > 0.5 {print;count++} count>max {exit}' input.txt

但这显然也不是正确的方法,因为我什至不能保证得到max 行。

我被困住了。我该怎么做?

【问题讨论】:

    标签: shell sed awk


    【解决方案1】:

    这可能对你有用:

    shuf -n3 file
    

    shuf 是 GNU coreutils 之一。

    【讨论】:

    • 唉,GNU coreutils 没有内置在 FreeBSD 中。
    • @ghoti OP 不仅没有提到 FreeBSD,而且他也没有提到反对安装新软件。在 OS X 上,这些都可以通过 brew install coreutils 获得。
    • @Johann 事实上,OP 确实 提到他在多个 cmets 中使用 FreeBSD(在我的回答和 Glenn 的回答中),他的问题提到了本机工具到 FreeBSD。
    【解决方案2】:

    如果jot 在您的系统上,那么我猜您运行的是 FreeBSD 或 OSX 而不是 Linux,因此您可能没有像 rlsort -R 这样的工具可用。

    不用担心。我不得不在不久前这样做。试试这个:

    $ printf 'one\ntwo\nthree\nfour\nfive\n' > input.txt
    $ cat rndlines
    #!/bin/sh
    
    # default to 3 lines of output
    lines="${1:-3}"
    
    # default to "input.txt" as input file
    input="${2:-input.txt}"
    
    # First, put a random number at the beginning of each line.
    while read line; do
      printf '%8d%s\n' $(jot -r 1 1 99999999) "$line"
    done < "$input" |
    sort -n |               # Next, sort by the random number.
    sed 's/^.\{8\}//' |     # Last, remove the number from the start of each line.
    head -n "$lines"        # Show our output
    
    $ ./rndlines input.txt 
    two
    one
    five
    $ ./rndlines input.txt 
    four
    two
    three
    $
    

    这是一个 1 行示例,它还使用 awk 更干净地插入了随机数:

    $ printf 'one\ntwo\nthree\nfour\nfive\n' | awk 'BEGIN{srand()} {printf("%8d%s\n", rand()*10000000, $0)}' | sort -n | head -n 3 | cut -c9-
    

    请注意,不同版本的sed(在 FreeBSD 和 OSX 中)可能需要 -E 选项而不是 -r 来处理 ERE,或者如果您想明确地使用正则表达式中的 BRE 方言,尽管一切我已经在 BRE 中测试了带有转义边界的作品。 (sed(HP/UX 等)的旧版本可能不支持这种表示法,但只有在您已经知道如何执行此操作的情况下才会使用这些表示法。)

    【讨论】:

    • 这看起来对我有用,无需安装任何额外的工具或语言(perl、python、bash)。你是对的,我正在运行 FreeBSD。谢谢你把所有的东西都拼出来。我将用管道重新组合命令以使其更加紧凑。
    • cat /path/to/file |awk 'BEGIN { srand() } { print rand() "\t" $0 }' |排序-n | cut -f2- > /path/to/random.file
    • @CodeReaper - 是的,使用 awk 和 cut 让事情变得更干净。我将这些步骤分成单独的行以便于记录。
    【解决方案3】:

    如果您可以访问 Python(将 10 更改为您想要的):

    python -c 'import random, sys; print("".join(random.sample(sys.stdin.readlines(), 10)).rstrip("\n"))' < input.txt
    

    (这将适用于 Python 2.x 和 3.x。)

    另外,(再次将10 更改为适当的值):

    sort -R input.txt | head -10
    

    【讨论】:

      【解决方案4】:

      这应该可以解决问题,至少对于 bash 并假设您的环境有其他可用命令:

      cat chk.c | while read x; do
          echo $RANDOM:$x
      done | sort -t: -k1 -n | tail -10 | sed 's/^[0-9]*://'
      

      它基本上输出你的文件,在每一行的开头放置一个随机数。

      然后它对该数字进行排序,抓取最后 10 行,并从中删除该数字。

      因此,它会从文件中为您提供十个随机行,没有重复。

      例如,下面是使用 chk.c 文件运行 3 次的记录:

      ====
      pax$ testprog chk.c
      } else {
      }
      newNode->next = NULL;
      colm++;
      
      ====
      pax$ testprog chk.c
      }
      
      arg++;
      printf (" [%s] n", currNode->value);
      free (tempNode->value);
      
      ====
      pax$ testprog chk.c
      
      char tagBuff[101];
      }
      return ERR_OTHER;
      #define ERR_MEM 1
      
      ===
      pax$ _
      

      【讨论】:

      • 我没有安装 bash,只是 sh。如果其他解决方案不起作用,我会尝试安装它。谢谢。
      • 这更有可能从文件末尾选择行,因为可以有超过 n 行以 32767 开头。
      • @Lri,不会的。随机数被随机分配(伪)。行数 32767 的可能性不大。只有当文件很大时,速度才会成为问题,在这种情况下,我什至可能不会使用 shell。
      • 试试cat /usr/share/dict/words | while read x; do echo $RANDOM:$x; done | sort -t: -k1 -n | tail -3
      • @Lri,我得到了32693:barfs32664:lurker32687:asbestos。你得到的其他结果是否让你怀疑它是否有效?
      【解决方案5】:
      sort -Ru filename | head -5
      

      将确保没有重复。并非所有sort 的实现都具有-R 选项。

      【讨论】:

      • sort -Ru &lt;&lt;&lt; $'1\n1\n2' | head -2 删除重复行,因此它永远不会返回 1 和 1。如果没有 -u,重复行将被排序在一起,因此它将返回 1 和 1 或 2 和 1。
      【解决方案6】:

      使用 Perl 从 FILE 获取 N 随机行:

      perl -MList::Util=shuffle -e 'print shuffle <>' FILE | head -N
      

      【讨论】:

      • Perl 可以完成head 的工作:perl -MList::Util=shuffle -e '$n = 3; @foo = shuffle &lt;&gt;; print @foo[0..$n]' FILE
      • 当然可以,但我相信这样更方便。我不介意混合使用不同的工具,有时我在一个命令中使用带有 awk/sed/perl 和 unix 实用程序的过滤器。曾经我什至使用过 python :)。
      【解决方案7】:

      如果您不想安装其他任何东西,这里有一个使用 ruby​​ 的答案:

      cat filename | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")' 
      

      例如,给定一个文件 (dups.txt),如下所示:

      1 2
      1 3
      2
      1 2
      3
      4
      1 3
      5
      6
      6
      7
      

      您可能会得到以下输出(或一些排列):

      cat dups.txt| ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
      4
      6
      5
      1 2
      2
      3
      7
      1 3
      

      来自 cmets 的更多示例:

      printf 'test\ntest1\ntest2\n' | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
      test1
      test
      test2
      

      当然,如果你有一个包含重复测试行的文件,你只会得到一行:

      printf 'test\ntest\ntest\n' | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
      test
      

      【讨论】:

      • 如何随机化输出?此外,如果相同的内容在输入文件的多行上,随机输出应该可以显示该数据两次,因为我想选择唯一的 lines,而不是唯一的 content .
      • 抱歉,我无意中遗漏了随机播放。该 uniq 只是对行数组进行操作,因此您应该得到唯一的行而不是内容。
      • 谢谢,但我认为uniq 的行为并非如此。如果我将printf 'test\ntest\ntest\n' 的输出提供给您的ruby 脚本,我仍然应该看到三行test 作为我的输出。我只看到一个。根据ruby docuniq 返回唯一的,这不是我想要处理输入数据的方式。
      • 但我以为你想要 uniq 行?如果你输入 test\ntest\ntest\n 当然你会得到一个独特的'test'行。添加到上面的示例中。
      • 不,您将“行”与“内容”混淆了。如果“test”出现在第 1 行,则对输出有效。如果它也出现在第 2 行,那也对输出有效。我想要的是避免显示 X 行两次,无论它是否与 Y 行的内容相同。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      • 2012-01-10
      • 2012-03-03
      相关资源
      最近更新 更多