所以我有这个语法的脚本:
./script number file
数字,我想从文件文件获取的行数。这些线是随机选择的,然后打印两次。考虑到一个非常大的文件~1 000 000行,这个算法运行得太慢。我不知道为什么因为打印只是访问数组。
#!/bin/bash
max=`wc -l $2 | cut -d " " -f1`
users=(`shuf -i 0-$max -n $1`)
pages=(`shuf -i 0-$max -n $1`)
readarray lines < $2
for (( i = 0; i < $1; i++ )); do
echo L ${lines[${users[i]}]} ${lines[${pages[i]}]}
done
for (( i = 0; i < $1; i++ )); do
echo U ${lines[${users[i]}]} ${lines[${pages[i]}]}
done
答案 0 :(得分:4)
只需使用shuf
选择行,这就是它的设计目标。例如(见注释):
readarray users < <(shuf -n $1 "$2")
readarray pages < <(shuf -n $1 "$2")
for (( i = 0; i < $1; i++ )); do
echo L ${users[i]} ${pages[i]}
done
for (( i = 0; i < $1; i++ )); do
echo U ${users[i]} ${pages[i]}
done
这仍然会很慢,因为shuf
需要读取整个文件才能找到行尾,并且你要调用它两次,但它可能比读取整个文件更快内存作为bash数组,特别是如果你没有足够的内存。 (如果脚本的第二个参数不是常规文件,它也将无法工作;如果是管道,则无法读取它两次。)
你可以通过一次选择两组线然后在users
和pages
之间划分它们来加快速度,但是你需要做一些工作来获得无偏见的分布,假设你关心这一点。
注1:
正如@gniourf_gniourf在评论中所指出的那样,通过使用-t
readarray
选项然后引用echo
的参数,您可以更准确地呈现这些行。另外,mapfile
是readarray
的首选名称:
mapfile -t users < <(shuf -n $1 "$2")
mapfile -t pages < <(shuf -n $1 "$2")
for (( i = 0; i < $1; i++ )); do
echo L "${users[i]}" "${pages[i]}"
done
for (( i = 0; i < $1; i++ )); do
echo U "${users[i]}" "${pages[i]}"
done
注2:
如果$1
很大,你最好不要使用数组。这是一个可能的解决方案:
lines="$(paste -d' ' <(shuf -n $1 "$2") <(shuf -n $1 "$"))"
sed 's/^/L /' <<<"$lines"
sed 's/^/U /' <<<"$lines"
答案 1 :(得分:2)
也许你可以完全没有数组,只需使用文件实用程序和临时文件:
# Put the shuf outputs in two separate files:
shuf -n "$1" "$2" > shuf_users
shuf -n "$1" "$2" > shuf_pages
# paste the two:
paste -d ' ' shuf_users shuf_pages | sed 's/^/L /'
paste -d ' ' shuf_pages shuf_users | sed 's/^/U /'
在@ rici的解决方案中,罪魁祸首也可能在两个输出行的循环中(这种for
循环非常慢)。
您应该使用mktemp
来创建临时文件shuf_users
和shuf_pages
。这项练习留待读者阅读。
答案 2 :(得分:1)
以下应该可以很快地完成你想要的事情,bash数组很慢并且使用临时文件构建,所以你的性能应该没有更好的使用它们 - 如果它们是由Bash维护者正确实现的话它们将是一个不错的功能但它们是还没到那里:
File (make sure to name it the same, this is recursive):
ranlines.bsh
#!/bin/bash
declare -i max=$(wc -l $2 | cut -d " " -f1)+1
declare STR=""
declare -i random_line=0
declare tmp_file="/tmp/_$$_$(date)"
declare -r usr_file="/tmp/_user_3434"
declare -r pgs_file="/tmp/_pgs_4343"
## create our tmp_file and tell it dont use 0
echo "0" >> "$tmp_file"
for (( i = 0; i < $1; i++ )); do
while :; do
random_line=$(($RANDOM*30%$max));
## if you find an entry already in the tmp_file then continue
## get a new number, loop until you find a new number
(($(grep -c "$random_line" "$tmp_file"))) && continue;
echo "$random_line" >> "$tmp_file"
break;
done
## build the sed print string
STR="$STR${random_line}p;"
done
rm "$tmp_file"
if [[ $# -eq 2 ]]; then
#usr_file
eval "sed -n '$STR' $2" > "$usr_file"
## call us again, this time for the U
ranlines.bsh $1 $2 "U"
else
## we know already we are processing the U because args is not 2
declare -i random_slct=$1+1
eval "sed -n '$STR' $2" > "$pgs_file"
paste <(sed -n "${random_slct}q; a L" "$2") "$usr_file" "$pgs_file"
paste <(sed -n "${random_slct}q; a U" "$2") "$pgs_file" "$usr_file"
rm "$pgs_file" "$usr_file"
fi
exit 0