如何显示文本文件中的随机行?


26

我正在尝试编写一个shell脚本。这个想法是从文本文件中随机选择一行,并将其显示为Ubuntu桌面通知。

但是我希望每次执行脚本时都选择不同的行。有解决方案吗?我不要整个剧本。只是那简单的事情而已。



Answers:


40

您可以使用shuf实用程序从文件中打印随机行

$ shuf -n 1 filename

-n :要打印的行数

例子:

$ shuf -n 1 /etc/passwd

git:x:998:998:git daemon user:/:/bin/bash

$ shuf -n 2 /etc/passwd

avahi:x:84:84:avahi:/:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false

但是通过使用它,我必须手动更改n的值,对吗?我希望该外壳自动随机选择另一行。并非完全需要随机。但是另一些线。
阿南杜M Das 2014年

4
@AnanduMDas不,您不必n表示要打印的行数。(即,您只需要一行还是两行)。不是行号(即第一行第二行)。
aneeshep 2014年

@AnanduMDas:我在回答中添加了一些示例。希望现在清除。
aneeshep

1
感谢ü其明确的现在:)我还发现了另一种算法,它像,存储当前时间(仅次于由date +%S)为变量x,然后选择使用的是第x线headtail命令从文本文件中。无论如何,您的方法更容易。谢谢
Anandu M Das 2014年

+1:shuf在coreutils中,因此默认情况下可用。注意:它将输入文件加载到内存中。有一种不需要的有效算法
jfs


8

只是为了好玩,这里是一个纯bash的解决方案不使用shufsortwcsedheadtail或任何其他外部工具。

相对于shuf变体的唯一优点是它速度更快,因为它是纯bash。在我的机器上,对于1000行的文件,shuf变体大约需要0.1秒,而以下脚本大约需要0.01秒;)因此,虽然shuf是最简单,最短的变体,但是速度更快。

老实说shuf,除非高效率是一个重要问题,否则我仍然会寻求解决方案。

#!/bin/bash

FILE=file.txt

# get line count for $FILE (simulate 'wc -l')
lc=0
while read -r line; do
 ((lc++))
done < $FILE

# get a random number between 1 and $lc
rnd=$RANDOM
let "rnd %= $lc"
((rnd++))

# traverse file and find line number $rnd
i=0
while read -r line; do
 ((i++))
 [ $i -eq $rnd ] && break
done < $FILE

# output random line
printf '%s\n' "$line"

@EliahKagan感谢您的建议和要点。我承认有很多我没有真正考虑过的极端情况。我写这本书的确更多是出于乐趣。shuf无论如何,使用会更好。考虑到这一点,我不相信纯粹的bash实际上比shuf我以前写过的效率更高。触发外部工具时可能会有最微小(恒定)的开销,但是它比解释的bash快得多。因此,shuf当然可以更好地扩展。因此,假设该脚本用于教育目的:很高兴看到它可以完成;)
Malte Skoruppa 2014年

GNU / Linux / Un * x拥有很多经过道路测试的轮子,我不希望对其进行重新发明,除非这纯粹是学术上的练习。“外壳”旨在用于组装许多小的现有零件,这些零件可以通过输入/输出和大量选项以多种方式(重新)组装。其他任何形式都是不好的形式,除非用于运动(例如codegolf.stackexchange.com/tour),在这种情况下,请继续玩...!
迈克尔

2
@michael_n尽管“纯bash”方式主要用于教学和修改其他任务,但这比看起来更合理的“真实”实现。Bash是广泛可用的,但是shufGNU Coreutils是特定的(例如,不在FreeBSD 10.0中)。sort -R是可移植的,但是解决了一个不同的(相关)问题:以多行出现的字符串的概率等于只出现一次的字符串的概率。(当然,wc仍然可以使用其他实用程序。)我认为这里的主要限制是,它永远不会在第32768行之后选择任何东西(并且随机性会更快些)。
伊利亚·卡根

2
Malte Skoruppa:我看到您已经将bash PRNG问题移至U&L。凉。提示:$((RANDOM<<15|RANDOM))位于0..2 ^ 30-1。@JFSebastian 偏向更频繁的输入是shuf,而不是sort -R。将shuf -n 1代替sort -R | head -n1和比较。(顺便说一下,10 ^ 3迭代比10 ^ 6更快,但仍然足以显示出差异。)另请参见更粗略,更直观的演示这有点愚蠢,表明它适用于所有字符串都是高频的大型输入
伊利亚·卡根

1
@JFSebastian在该命令中,to的输入dieharder似乎全为零。假设这不仅仅是我个人的一个奇怪错误,那肯定可以解释为什么它不是随机的!如果您运行while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > out了一段时间,然后out使用十六进制编辑器检查的内容,是否能获得美观的数据?(或者查看其他但是你喜欢。)我得到的所有零,而RANDOM不是罪魁祸首:我得到的所有零,当我更换$(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))使用100了。
伊利亚·卡根

4

说您有文件notifications.txt。我们需要计算总行数,以确定随机数生成器的范围:

$ cat notifications.txt | wc -l

让我们写入变量:

$ LINES=$(cat notifications.txt | wc -l)

现在要生成从0到的数字,$LINE我们将使用RANDOM变量。

$ echo $[ $RANDOM % LINES]

让我们将其写入变量:

$  R_LINE=$(($RANDOM % LINES))

现在我们只需要打印此行号:

$ sed -n "${R_LINE}p" notifications.txt

关于RANDOM:

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.  The sequence of random numbers may be
          initialized by assigning a value to RANDOM.  If RANDOM is unset,
          it  loses  its  special  properties,  even if it is subsequently
          reset.

确保文件的行号少于32767。见这个,如果你需要更大的随机生成的作品的开箱即用。

例:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '

一种风格选择(重击):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
迈克尔


例如,使用灰色位图查看Test PRNG中的最后一张图片,以了解为什么将其应用于% n随机数不是一个好主意。
jfs

2

这是一个Python脚本,可从输入文件或stdin中选择一条随机行:

#!/usr/bin/env python
"""Usage: select-random [<file>]..."""
import random

def select_random(iterable, default=None, random=random):
    """Select a random element from iterable.

    Return default if iterable is empty.
    If iterable is a sequence then random.choice() is used for efficiency instead.
    If iterable is an iterator; it is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    try:
        return random.choice(iterable) # O(1) time and space
    except IndexError: # empty sequence
        return default
    except TypeError: # not a sequence
        return select_random_it(iter(iterable), default, random.randrange)

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from /programming//a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

if __name__ == "__main__":
    import fileinput
    import sys

    random_line = select_random_it(fileinput.input(), '\n')
    sys.stdout.write(random_line)
    if not random_line.endswith('\n'):
        sys.stdout.write('\n') # always append newline at the end

该算法是O(n)时间,O(1)空间。它适用于大于32767行的文件。它不会将输入文件加载到内存中。它只读取一次每个输入行,即,您可以通过管道将任意大(但有限)的内容输入其中。这是该算法说明


1

马尔特·斯科鲁帕(Malte Skoruppa)和其他人所做的工作给我留下了深刻的印象,但这是一种更简单的“纯重打击”方法:

IFS=$'\012'
# set field separator to newline only
lines=( $(<test5) )
# slurp entire file into an array
numlines=${#lines[@]}
# count the array elements
num=$(( $RANDOM$RANDOM$RANDOM % numlines ))
# get a (more-or-less) random number within the correct range
line=${lines[$num]}
# select the element corresponding to the random number
echo $line
# display it

如某些人所述,$ RANDOM不是随机的。但是,可以通过根据需要将$ RANDOM串在一起来克服32767行的文件大小限制。

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.