我想用sed
选择一个随机行。我知道shuf -n
和sort -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
它必须是随机的。
它在很大程度上取决于您希望伪随机概率分布的样子。 (不要随意尝试,满足于伪随机。如果你设法生成一个真正的随机值,去收集你的诺贝尔奖。)如果你只是想要一个统一的分布(例如,每一行有相同的概率)选中),然后你需要先了解文件中有多少行。获得该分布并不是那么容易,因为允许稍微更容易选择文件中的早期行,并且由于这很容易,我们将这样做。假设行数小于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
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
这似乎是大输入文件的最佳解决方案:
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
所以我们前面调用wc
和rand()
的解决方案比预期的每行调用rand()
更快。
在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++)) { ;}
使用GNU sed
和bash
;没有wc
或awk
:
f=input-file
sed -n $((RANDOM%($(sed = $f | sed '2~2d' | sed -n '$p')) + 1))p $f
注意:sed
中的三个$(...)
s是伪造wc -l < $f
的低效方法。也许有更好的方法 - 当然只使用sed
。
使用shuf
:
$ echo "$var" | shuf -n 1
输出:
Hi