Ответ 1
Здесь функция wee bash для вас. Он захватывает, как вы говорите, "пакет" строк со случайной начальной точкой внутри файла.
randline() {
local lines c r _
# cache the number of lines in this file in a symlink in the temp dir
lines="/tmp/${1//\//-}.lines"
if [ -h "$lines" ] && [ "$lines" -nt "${1}" ]; then
c=$(ls -l "$lines" | sed 's/.* //')
else
read c _ < <(wc -l $1)
ln -sfn "$c" "$lines"
fi
# Pick a random number...
r=$[ $c * ($RANDOM * 32768 + $RANDOM) / (32768 * 32768) ]
echo "start=$r" >&2
# And start displaying $2 lines before that number.
head -n $r "$1" | tail -n ${2:-1}
}
При необходимости отредактируйте строки echo
.
Это решение имеет то преимущество, что меньше труб, менее ресурсоемких каналов (т.е. no | sort ... |
), меньше зависимости от платформы (т.е. no sort -R
, которая является специфичной для GNU).
Обратите внимание, что это зависит от переменной bash $RANDOM
, которая может или не может быть случайной. Кроме того, он будет пропускать строки, если ваш исходный файл содержит более 32768 ^ 2 строки, и есть случай края сбоя, если количество строк, которые вы определили (N), равно > 1, а случайная начальная точка меньше, чем N строк из начало. Решение, которое остается для упражнения читателем.:)
ОБНОВЛЕНИЕ # 1:
mklement0 задает отличный вопрос в комментариях о потенциальных проблемах производительности с подходом head ... | tail ...
. Я честно не знаю ответа, но я надеюсь, что оба head
и tail
будут оптимизированы настолько, что они не будут буферизировать ВСЕ входные данные до отображения их вывода.
В случае, если моя надежда не будет выполнена, вот альтернатива. Это awk-based "скользящее окно". Я вложу его в более раннюю функцию, которую я написал, чтобы вы могли проверить ее, если хотите.
randline() {
local lines c r _
# Line count cache, per the first version of this function...
lines="/tmp/${1//\//-}.lines"
if [ -h "$lines" ] && [ "$lines" -nt "${1}" ]; then
c=$(ls -l "$lines" | sed 's/.* //')
else
read c _ < <(wc -l $1)
ln -sfn "$c" "$lines"
fi
r=$[ $c * ($RANDOM * 32768 + $RANDOM) / (32768 * 32768) ]
echo "start=$r" >&2
# This simply pipes the functionality of the `head | tail` combo above
# through a single invocation of awk.
# It should handle any size of input file with the same load/impact.
awk -v lines=${2:-1} -v count=0 -v start=$r '
NR < start { next; }
{ out[NR]=$0; count++; }
count > lines { delete out[start++]; count--; }
END {
for(i=start;i<start+lines;i++) {
print out[i];
}
}
' "$1"
}
Вложенный awk script заменяет конвейер head ... | tail ...
в предыдущей версии функции. Он работает следующим образом:
- Он пропускает строки до "начала", как определено ранней рандомизацией.
- Он записывает текущую строку в массив.
- Если массив больше числа строк, которые мы хотим сохранить, он исключает первую запись.
- В конце файла он печатает записанные данные.
В результате процесс awk не должен увеличивать объем памяти, потому что выходной массив обрезается так быстро, как только он был создан.
ПРИМЕЧАНИЕ. Я не проверял это с вашими данными.
ОБНОВЛЕНИЕ # 2:
Hrm, с обновлением вашего вопроса, что вам нужны N случайных строк, а не блок строк, начинающихся с произвольной точки, нам нужна другая стратегия. Системные ограничения, которые вы наложили, довольно серьезны. Следующим может быть опция, также использующая awk со случайными номерами, все еще из Bash:
randlines() {
local lines c r _
# Line count cache...
lines="/tmp/${1//\//-}.lines"
if [ -h "$lines" ] && [ "$lines" -nt "${1}" ]; then
c=$(ls -l "$lines" | sed 's/.* //')
else
read c _ < <(wc -l $1)
ln -sfn "$c" "$lines"
fi
# Create a LIST of random numbers, from 1 to the size of the file ($c)
for (( i=0; i<$2; i++ )); do
echo $[ $c * ($RANDOM * 32768 + $RANDOM) / (32768 * 32768) + 1 ]
done | awk '
# And here inside awk, build an array of those random numbers, and
NR==FNR { lines[$1]; next; }
# display lines from the input file that match the numbers.
FNR in lines
' - "$1"
}
Это работает путем подачи списка случайных номеров строк в awk как "первый" файл, а затем с awk печатайте строки из "второго" файла, номера строк которого были включены в "первый" файл. Он использует wc
для определения верхнего предела генерируемых случайных чисел. Это означает, что вы будете читать этот файл дважды. Если у вас есть другой источник для количества строк в файле (например, базы данных), включите его здесь.:)
Ограничивающим фактором может быть размер этого первого файла, который должен быть загружен в память. Я считаю, что случайные числа 30000 должны принимать только около 170 Кбайт памяти, но как массив представлен в RAM, зависит от реализации awk, который вы используете. (Хотя обычно реализация awk (включая Gawk в Ubuntu) довольно хороша в том, чтобы свести потери памяти к минимуму.)
Это работает для вас?