Unixコマンドラインでファイルからランダムな行を読み取る簡単な方法は何ですか?


263

Unixコマンドラインでファイルからランダムな行を読み取る簡単な方法は何ですか?


各行は固定長にパディングされていますか?
Tracker1 09年

いいえ、各行には可変数の文字があります

回答:


383

使用できますshuf

shuf -n 1 $FILE

と呼ばれるユーティリティもありますrl。Debianではrandomize-lines、すべてのディストリビューションで利用できるわけではありませんが、まさにそれはあなたが望むものを実行するパッケージにあります。そのホームページでは、実際にshuf代わりに(作成時に存在しなかった)の使用を推奨しています。 shufGNU coreutilsの一部ですが、そうでrlはありません。

rl -c 1 $FILE

2
shufヒントをありがとう、それはFedoraに組み込まれています。
Cheng

5
また、かなり大きなファイル(80kk行)を処理する場合sort -Rは、必ずかなり待機することになりますが、shuf -n非常に瞬時に動作します。
ルーベンス2013年

23
coreutilsHomebrewからインストールすると、OS Xでshufを取得できます。のgshuf代わりに呼び出される場合がありshufます。
Alyssa Ross

2
同様に、randomize-linesOS Xでも使用できますbrew install randomize-lines; rl -c 1 $FILE
Jamie

4
これshufGNU Coreutilsの一部であるため、* BSDシステム(またはMac?)では必ずしも(デフォルトで)使用できるとは限らないことに注意してください。以下の@ Tracker1のperlワンライナーは、より移植性があります(私のテストでは、わずかに高速です)。
Adam Katz 2014

74

別の選択肢:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1

28
$ {RANDOM}は32768未満の数値のみを生成するため、大きなファイル(たとえば、英語の辞書)には使用しないでください。
ラルフ

3
これは、モジュロ演算のため、すべての行に対して正確に同じ確率を与えるわけではありません。これは、ファイルの長さが<< 32768の場合はほとんど問題になりません(その数を除算してもまったく問題ありません)が、注目に値するかもしれません。
Anaphory 2014年

10
を使用して、これを30ビットの乱数に拡張でき(${RANDOM} << 15) + ${RANDOM}ます。これにより、バイアスが大幅に軽減され、最大10億行を含むファイルで機能するようになります。
nneonneo

@nneonneo:とてもクールなトリックですが、このリンクによると、stackoverflow.com
Jay Taylor

+そして、|同じであるので${RANDOM}、定義により0..32767です。
nneonneo

71
sort --random-sort $FILE | head -n 1

(私は上記のshufアプローチがさらに好きです-それが存在することさえ知らなかったし、自分でそのツールを見つけることはなかったでしょう)


10
+1気に入ったのですがsort、ご使用のシステム(CentOS 5.5、Mac OS 10.7.2)で動作しない最新のが必要な場合があります。また、猫の無駄な使用は次のように減らすことができますsort --random-sort < $FILE | head -n 1
Steve Kehlet '16

sort -R <<< $'1\n1\n2' | head -1sort -R重複する行を一緒にソートするため、1と2を返す可能性が高くなります。同じことがsort -Ru、重複する行を削除するため、にも当てはまります。
Lri

5
ファイル全体をsortにパイプする前にファイル全体をシャッフルする必要があるため、これは比較的低速headです。shuf代わりに、ファイルからランダムな行を選択し、私にとってははるかに高速です。
Bengt

1
@SteveKehletをsort --random-sort $FILE | head使用すると、ファイルに直接アクセスでき、効率的な並列ソートが可能になる可能性があるため、
最善の方法です

5
--random-sortおよび-Rオプションは、ソートGNU(彼らはBSDやMac OSで動作しないように固有のものですsort)。GNUソートはこれらのフラグを2005年に学習したため、GNU coreutils 6.0以降(CentOS 6など)が必要です。
RJHunter 2015

31

これは簡単です。

cat file.txt | shuf -n 1

これは、それだけでは「shuf -n 1 file.txt」よりも少し遅いだけです。


2
ベストアンサー。私はこのコマンドについて知りませんでした。-n 1は1行を指定し、1行以上に変更できることに注意してくださいshuf。他のものにも使用できます。名前を部分的に一致させるプロセスをランダムに強制終了するために、パイプps auxで接続grepしました。
sudo

18

perlfaq5:ファイルからランダムな行を選択するにはどうすればよいですか?ラクダ本のリザーバーサンプリングアルゴリズムを次に示します。

perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' file

これは、ファイル全体を読み取るよりもスペースの点で大きな利点があります。この方法の証拠は、Donald E. KnuthによるThe Art of Computer Programming、Volume 2、Section 3.4.2にあります。


1
含めるため(参照サイトがダウンした場合に備えて)、Tracker1がポイントしたコードは次のとおりです: "cat filename | perl -e 'while(<>){push(@ _、$ _);} print @ _ [rand()* @ _]; '; "
Anirvan、2009年

3
これは猫の無駄な使い方です。以下は、perlfaq5に含まれるコードのわずかな変更です(およびCamelブックの厚意により)。perl -e 'srand; rand($。)<1 &&($ line = $ _)while <>; $ lineを印刷する; ' ファイル名
マスクラット氏、

errは...あるリンク先サイト、
ネイサンFellman

私はこのコードのN行バージョンをに対してベンチマークしましたshuf。perlコードは非常にわずかに高速です(ユーザー時間で8%高速、システム時間で24%高速)。
Adam Katz 2014

2
考えるためのより多くの食べ物: このコードは1行しか保存しないので、shuf入力ファイル全体をメモリ保存しますが、これは恐ろしい考えです。したがって、このコードの制限はINT_MAXの行数です(2 ^ 31または2 ^ 63 arch)、その選択された潜在的な行のいずれかがメモリに収まると仮定します。
Adam Katz 2014

11

bashスクリプトを使用する:

#!/bin/bash
# replace with file to read
FILE=tmp.txt
# count number of lines
NUM=$(wc - l < ${FILE})
# generate random number in range 0-NUM
let X=${RANDOM} % ${NUM} + 1
# extract X-th line
sed -n ${X}p ${FILE}

1
ランダムは0にすることができ、sedは最初の行に1を必要とします。sed -n 0pはエラーを返します。
asalamon74 2009年

mhm-"tmp.txt"の$ 1とNUMの$ 2はどうですか?
blabla999 2009年

ただし、perlやpythonを必要とせず、可能な限り効率的であるため、バグのあるポイントでも価値があります(ファイルを正確に2回読み取るが、メモリには読み込まないため、巨大なファイルでも機能します)。
blabla999 2009年

@ asalamon74:感謝@ blabla999:関数からそれを作成する場合、$ 1で問題ありませんが、なぜNUMを計算しないのですか?
Paolo Tedesco、

sed行を次のように変更:head-$ {X} $ {FILE} | 尾-1はそれを行う必要があります
JeffK

4

単一のbash行:

sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt

わずかな問題:ファイル名が重複しています。


2
わずかな問題。/ usr / share / dict / wordsでこれを実行すると、「A」で始まる単語が優先される傾向があります。それをいじってみると、「A」の単語の90%から「B」の単語の10%に達しています。ファイルの先頭を構成する数字で始まるものはまだありません。
bibby

wc -l < test.txtにパイプする必要がなくなりcutます。
fedorqui 'SO stop harming'

3

これがその仕事をする簡単なPythonスクリプトです:

import random, sys
lines = open(sys.argv[1]).readlines()
print(lines[random.randrange(len(lines))])

使用法:

python randline.py file_to_get_random_line_from

1
これはうまくいきません。1行後に停止します。それを機能させるために、私はこれを行いました:import random, sys lines = open(sys.argv[1]).readlines() for i in range(len(lines)):rand = random.randint(0、len(lines)-1)print lines.pop(rand)、
Jed Daniels

くだらないフォーマットの愚かなコメントシステム。昔はコメントのフォーマットが機能しませんでしたか?
Jed Daniels

randintは包括的であるためlen(lines)、IndexErrorが発生する可能性があります。使用できますprint(random.choice(list(open(sys.argv[1]))))。また、メモリ効率の高いリザーバーサンプリングアルゴリズムもあります
jfs 2014

2
かなりのスペースが必要です。3TBのファイルを検討してください。
マイケルキャンベル

@MichaelCampbell:上記のリザーバーサンプリングアルゴリズムは、3TBファイルで動作する可能性があります(行サイズが制限されている場合)。
jfs 2015

2

awk」を使用する別の方法

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name

2
これはawkとbashを使用しています(これ$RANDOMバシズムです)。ここでは上記Tracker1の引用perlfaq5コードは@と同じロジックを使用して、純粋なawk(のmawk)メソッドである:awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(うわー、それもだ短い Perlコードよりは!)
アダム・カッツ

そのコードはwc、行数を取得するためにファイルを読み取る()必要があります。次に、ファイル(の一部awk)を再度読み取り()、指定されたランダムな行番号の内容を取得する必要があります。I / Oは、乱数を取得するよりもはるかにコストがかかります。私のコードはファイルを1回だけ読み取ります。awkの問題rand()は、秒単位でシードするため、連続して実行しすぎると重複が発生することです。
Adam Katz 2014

1

MacOSXでも動作し、Linux(?)でも動作するソリューション:

N=5
awk 'NR==FNR {lineN[$1]; next}(FNR in lineN)' <(jot -r $N 1 $(wc -l < $file)) $file 

どこ:

  • N 必要なランダム線の数です

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 ->書き込まれた行番号を保存してfile1から、対応する行を印刷するfile2

  • jot -r $N 1 $(wc -l < $file)-> で範囲内のN数値をランダムに(-r)描画(1, number_of_line_in_file)jotます。プロセスの置換<()により、インタプリタのファイルのように見えるのでfile1、前の例では。

0
#!/bin/bash

IFS=$'\n' wordsArray=($(<$1))

numWords=${#wordsArray[@]}
sizeOfNumWords=${#numWords}

while [ True ]
do
    for ((i=0; i<$sizeOfNumWords; i++))
    do
        let ranNumArray[$i]=$(( ( $RANDOM % 10 )  + 1 ))-1
        ranNumStr="$ranNumStr${ranNumArray[$i]}"
    done
    if [ $ranNumStr -le $numWords ]
    then
        break
    fi
    ranNumStr=""
done

noLeadZeroStr=$((10#$ranNumStr))
echo ${wordsArray[$noLeadZeroStr]}

$ RANDOMは/ usr / share / dict / words内の単語数よりも少ない数を生成します。これは235886(とにかく私のMacで)なので、0から9までの6つの個別の乱数を生成し、それらをつなぎ合わせるだけです。次に、数値が235886未満であることを確認します。次に、先行ゼロを削除して、配列に格納した単語にインデックスを付けます。各単語は独自の行であるため、これは任意のファイルがランダムに行を選択するために簡単に使用できます。
ケン

0

私のMac OSは簡単な答えをすべて使用しているわけではないので、これが私が発見したものです。$ RANDOM変数ソリューションはテストではあまりランダムではないようなので、jotコマンドを使用して数値を生成しました。私のソリューションをテストするとき、出力で提供されるソリューションに大きなばらつきがありました。

  RANDOM1=`jot -r 1 1 235886`
   #range of jot ( 1 235886 ) found from earlier wc -w /usr/share/dict/web2
   echo $RANDOM1
   head -n $RANDOM1 /usr/share/dict/web2 | tail -n 1

変数のエコーは、生成された乱数のビジュアルを取得することです。


0

バニラsedとawkのみを使用し、$ RANDOMを使用しない場合、FILENAMEという名前のファイルから単一行を擬似ランダムに選択するためのシンプルでスペース効率が高く、適度に高速な「ワンライナー」は次のとおりです。

sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' FILENAME)p FILENAME

(これは、FILENAMEが空の場合でも機能します。その場合、行は出力されません。)

このアプローチの考えられる利点の1つは、rand()を1回しか呼び出さないことです。

コメントで@AdamKatzが指摘したように、別の可能性は各行に対してrand()を呼び出すことです。

awk 'rand() * NR < 1 { line = $0 } END { print line }' FILENAME

(帰納法に基づいて、正当性の簡単な証明を与えることができます。)

注意事項 rand()

「gawkを含むほとんどのawk実装では、rand()はawkを実行するたびに、同じ開始番号またはシードから番号の生成を開始します。」

- https://www.gnu.org/software/gawk/manual/html_node/Numeric-Functions.html


この回答の1年前に投稿したコメントを参照してください。これには、sedを必要としないシンプルなawkソリューションがあります。また、秒単位でシードするawkの乱数ジェネレータについての私の注意点にも注意してください。
Adam Katz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.