テキストファイルからランダムな行を表示する方法は?


26

シェルスクリプトを記述しようとしています。アイデアは、テキストファイルからランダムに1行を選択し、Ubuntuデスクトップ通知として表示することです。

ただし、スクリプトを実行するたびに異なる行を選択する必要があります。これを行う解決策はありますか?スクリプト全体は必要ありません。単純なことだけです。


また、アクセス:askubuntu.com/q/492572/256099
Pandya

回答:


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ダス14

4
@AnanduMDasいいえn、印刷する行数を示す必要はありません。(つまり、1行だけにするか2行にするか)。行番号ではありません(つまり、1行目、2行目)。
aneeshep

@AnanduMDas:回答にいくつかの例を追加しました。明確になりました。
aneeshep 14

1
その明確なありがとう:)また、別のアルゴリズムを見つけました。現在の時間(秒のみ、by date +%S)を変数xに保存し、テキストファイルのheadand tailコマンドを使用してx番目の行を選択します。とにかくあなたの方法はより簡単です。おかげで
Anandu Mダス

+1:shufcoreutilsにあるため、デフォルトで使用可能です。注:入力ファイルをメモリにロードします。それを必要としない効率的なアルゴリズムがあります
jfs 14

13

sortコマンドを使用して、ファイルからランダムな行を取得することもできます。

sort -R filename | head -n1

注:入力に重複行がある場合sort -Rshuf -n1またはselect-random重複行がある場合に異なる結果を生成します。@EliahKaganのコメントを参照してください。
jfs 14

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とにかく使用する方がはるかに優れています。それを考えると、shuf以前に書いたように、純粋なbashがを使用するよりも実際に効率的であるとは思いません。外部ツールを起動するときに最も小さい(一定の)オーバーヘッドが発生する場合がありますが、解釈されたbashよりも速く実行されます。したがって、shuf確かにスケーリングが向上します。それで、スクリプトが教育目的に役立つとしましょう:それができることを見るのは素晴らしいことです;)
マルテスコルッパ14

GNU / Linux / Un * xには、純粋にアカデミックな演習でない限り、再発明したくない、非常によくロードテストされたホイールがたくさんあります。「シェル」は、入出力と豊富なオプションを介してさまざまな方法で(再)組み立てることができる多くの小さな既存の部品を組み立てるために使用することを目的としていました。スポーツ以外の場合(codegolf.stackexchange.com/tourなど)、それ以外の場合は悪い形式です。
マイケル14

2
@michael_n「純粋なbash」の方法は主に教育や他のタスクの変更に役立ちますが、これは見かけよりも合理的な「実際の」実装です。Bashは広く利用可能ですが、shufのGNU Coreutils –特定(たとえば、FreeBSD 10.0ではありません)。sort -Rは移植可能ですが、別の(関連する)問題を解決します。複数の行として表示される文字列は、一度だけ表示される文字列と等しい確率を持ちます。(もちろん、wcその他のユーティリティも使用できます。)ここでの主な制限は、これが32768行目以降に何も選択しないことです(そして、やや早くランダムになりません)。
エリアケイガン

2
Malte Skoruppa:bash PRNGの質問をU&Lに移動ましたね。クール。ヒント:$((RANDOM<<15|RANDOM))0..2 ^ 30-1です。@JFSebastianそれshufsort -R、ではなく、より頻繁な入力に偏っています。のshuf -n 1代わりに置き、sort -R | head -n1比較します。(ところで10 ^ 3回の反復より早く10 ^ 6との違いを示すためにまだかなり十分に超えています。)も参照してください粗く、より視覚的なデモ、それはすべての文字列は、高周波あり、大きな入力で動作を示す愚かさのこのビットを
エリアケイガン

1
@JFSebastianそのコマンドでは、への入力はdieharderすべてゼロのようです。これが単に私の側の奇妙な間違いではないと仮定すると、それが間違いなくランダムではない理由を確実に説明するでしょう!while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outしばらく実行outしてから16進エディタで内容を調べると、見栄えの良いデータが得られますか?(または、好きなように表示します。)私はすべてゼロを取得しRANDOM、犯人ではありません:に置き換える$(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))100、すべてゼロを取得します。
エリアケイガン

4

fileがあるとします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 ' '

スタイルの代替(bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
マイケル14


たとえば、グレーのビットマップ使用したPRNGのテストの最後の図を見% nて、乱数に適用するのが得策ではない理由を理解してください。
jfs 14

2

入力ファイルまたは標準入力からランダムな行を選択するPythonスクリプトを次に示します。

#!/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などが行った作業には感心しましたが、これを行うためのはるかに単純な「純粋なbash」方法があります。

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はランダムではありません。ただし、32767行のファイルサイズ制限は、必要に応じて$ RANDOMを文字列化することで克服されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.