随机线使用sed

问题描述 投票:2回答:6

我想用sed选择一个随机行。我知道shuf -nsort -R | head -n完成这项工作,但是对于shuf,你必须安装coreutils,而对于sort solution,它对大数据来说不是最佳的:

这是我测试的:

echo "$var" | shuf -n1

这给出了最佳解决方案,但我担心可移植性,这就是为什么我想用sed尝试它。

`var="Hi
 i am a student
 learning scripts"`

output:
i am a student

output:
hi

它必须是随机的。

bash sed
6个回答
3
投票

它在很大程度上取决于您希望伪随机概率分布的样子。 (不要随意尝试,满足于伪随机。如果你设法生成一个真正的随机值,去收集你的诺贝尔奖。)如果你只是想要一个统一的分布(例如,每一行有相同的概率)选中),然后你需要先了解文件中有多少行。获得该分布并不是那么容易,因为允许稍微更容易选择文件中的早期行,并且由于这很容易,我们将这样做。假设行数小于32769,您可以简单地执行:

N=$(wc -l < input-file)
sed -n -e $((RANDOM % N + 1))p input-file

- 编辑 -

在考虑了一下之后,我意识到你不需要知道行数,所以你不需要读取数据两次。我没有做过严格的分析,但我相信以下内容给出了统一的分布:

awk 'BEGIN{srand()} rand() < 1/NR { out=$0 } END { print out }' input-file

- 编辑 - Ed Morton在评论中建议我们应该只能调用一次rand()。这似乎应该有效,但似乎没有。好奇:

$ time for i in $(seq 400); do awk -v seed=$(( $(date +%s) + i)) 'BEGIN{srand(seed); r=rand()} r < 1/NR { out=$0 } END { print out}'  input; done | awk '{a[$0]++} END { for (i in a) print i, a[i]}' | sort
1 205
2 64
3 37
4 21
5 9
6 9
7 9
8 46

real    0m1.862s
user    0m0.689s
sys     0m0.907s
$ time for i in $(seq 400); do awk -v seed=$(( $(date +%s) + i)) 'BEGIN{srand(seed)} rand() < 1/NR { out=$0 } END { print out}'  input; done | awk '{a[$0]++} END { for (i in a) print i, a[i]}' | sort
1 55
2 60
3 37
4 50
5 57
6 45
7 50
8 46

real    0m1.924s
user    0m0.710s
sys     0m0.932s

2
投票
var="Hi
i am a student
learning scripts"

mapfile -t array <<< "$var"      # create array from $var

echo "${array[$RANDOM % (${#array}+1)]}"
echo "${array[$RANDOM % (${#array}+1)]}"

输出(例如):

learning scripts
i am a student

见:help mapfile


1
投票

这似乎是大输入文件的最佳解决方案:

awk -v seed="$RANDOM" -v max="$(wc -l < file)" 'BEGIN{srand(seed); n=int(rand()*max)+1} NR==n{print; exit}' file

因为它使用标准的UNIX工具,它不限于长度为32,769行或更少的文件,它对输入的任何一端都没有任何偏差,即使在1秒内调用两次也会产生不同的输出,并且它在打印目标行后立即退出,而不是继续到输入结束。


更新:

说到上面的内容,我没有解释为什么每行调用一次rand()并读取每一行输入的脚本大约是调用rand()一次并在第一个匹配行退出的脚本的两倍:

$ seq 100000 > file

$ time for i in $(seq 500); do
    awk -v seed="$RANDOM" -v max="$(wc -l < file)" 'BEGIN{srand(seed); n=int(rand()*max)+1} NR==n{print; exit}' file;
done > o3

real    1m0.712s
user    0m8.062s
sys     0m9.340s

$ time for i in $(seq 500); do
    awk -v seed="$RANDOM" 'BEGIN{srand(seed)} rand() < 1/NR{ out=$0 } END { print out}' file;
done > o4

real    0m29.950s
user    0m9.918s
sys     0m2.501s

它们都产生了非常相似的输出类型:

$ awk '{a[$0]++} END { for (i in a) print i, a[i]}' o3 | awk '{sum+=$2; max=(NR>1&&max>$2?max:$2); min=(NR>1&&min<$2?min:$2)} END{print NR, sum, min, max}'
498 500 1 2

$ awk '{a[$0]++} END { for (i in a) print i, a[i]}' o4 | awk '{sum+=$2; max=(NR>1&&max>$2?max:$2); min=(NR>1&&min<$2?min:$2)} END{print NR, sum, min, max}'
490 500 1 3

最后更新:

事实证明它正在调用wc(至少对我来说意外!)占用了大部分时间。当我们把它从循环中取出时,这是改进:

$ time { max=$(wc -l < file); for i in $(seq 500); do awk -v seed="$RANDOM" -v max="$max" 'BEGIN{srand(seed); n=int(rand()*max)+1} NR==n{print; exit}' file; done } > o3

real    0m24.556s
user    0m5.044s
sys     0m1.565s

所以我们前面调用wcrand()的解决方案比预期的每行调用rand()更快。


0
投票

在bash shell上,首先将种子初始化为#line cube或您的选择

$ i=;while read a; do let i++;done<<<$var; let RANDOM=i*i*i

$ let l=$RANDOM%$i+1 ;echo -e $var |sed -En "$l p"

如果将您的数据移动到varfile

$ echo -e $var >varfile
$ i=;while read a; do let i++;done<varfile; let RANDOM=i*i*i

$ let l=$RANDOM%$i+1 ;sed -En "$l p" varfile

把最后一个内循环放在例如for((c=0;c<9;c++)) { ;}


0
投票

使用GNU sedbash;没有wcawk

f=input-file
sed -n $((RANDOM%($(sed = $f | sed '2~2d' | sed -n '$p')) + 1))p $f

注意:sed中的三个$(...)s是伪造wc -l < $f的低效方法。也许有更好的方法 - 当然只使用sed


0
投票

使用shuf

$ echo "$var" | shuf -n 1

输出:

Hi
© www.soinside.com 2019 - 2024. All rights reserved.