Я не спал из-за этого. Эта команда R
, на которую указывает Sundeep, действительно классная, но реализация разочаровывает. Поэтому я немного покопался в документах Седа и нашел это first~step
first~step
Match every step'th line starting with line first. For example, ``sed -n 1~2p'' will print all the odd-numbered lines in the input stream,
and the address 2~5 will match every fifth line, starting with the second. first can be zero; in this case, sed operates as if it were equal
to step. (This is an extension.)
И я попробовал это
sed '1~3R headers' lines
Но результат оказался не таким, как ожидалось
line1.1
header1
line1.2
line1.3
line2.1
header2
line2.2
line2.3
line3.1
header3
line3.2
Поскольку R
добавляет строки, этого можно избежать, добавив дополнительную первую строку к файлу строк
sed -i '1s/^/\n/' lines
Чем мы обрабатываем файлы
sed '1~3R headers' lines > output
И удаляем эта дополнительная строка из вывода
sed -i '1d' output
Но эта необходимость добавления удаления строк также разочаровывает. Есть ли лучший способ?
Интересно, достаточно ли grep? Не могли бы вы попробовать?
headers=( $(cat headers) )
for header in ${headers[@]}; {
echo $header >> output
digit=${header//[!0-9]/}
grep .*$digit. lines >> output
}
Хорошо, какой метод быстрее? Я сделал тестовые файлы с этими
for i in {1..100}; { echo "header$i" >> headers; }
for i in {1..100}; { for e in {1..3}; { echo "line$i.$e" >> lines;}; }
100 и 300 строками и протестировал все методы
paste -d '\n' headers - - - <lines
real 0m0,003s
user 0m0,000s
sys 0m0,003s
sed -e 'R lines' -e 'R lines' -e 'R lines' headers
real 0m0,003s
user 0m0,000s
sys 0m0,003s
awk -v r=3 '1;{for(i=1;i<=r;++i) {getline < "-"; print}}' headers <lines
awk -v r=3 '(NR==FNR){b[FNR]=$0;next}(FNR%r==1){print b[++c]}1' headers lines
real 0m0,005s
user 0m0,000s
sys 0m0,005s
Это явные победители. И это тоже быстро, но неверно.
$ time awk 'NR==FNR{hdrs[NR]=$0; next} NR%3 == 1{print hdrs[++c]} 1' headers lines | head -n7
line1.1
line1.2
header1
line1.3
line2.1
line2.2
header2
real 0m0,004s
user 0m0,002s
sys 0m0,003s
Хорошо, это то, что используют TS.
fun1 () {
new_reads_no="$(wc -l headers | awk '{print $1}')"
sequence="$(seq 1 $new_reads_no)"
for i in $sequence
do
start=$((3*($i-1)+1))
end=$(($start+2))
awk -v c1=$i 'FNR==c1' headers
awk -v s="$start" -v e="$end" 'NR>=s&&NR<=e' lines
done
}
real 0m0,341s
user 0m0,219s
sys 0m0,132s
Ну, это совсем не быстро)
fun2 () {
headers=( $(cat headers) )
for header in ${headers[@]}; {
echo $header
digit=${header//[!0-9]/}
grep .*$digit. lines
}
}
real 0m0,167s
user 0m0,105s
sys 0m0,068s
Мой тоже не работает, но все же он быстрее 1-го) Так что я получил то, что действительно нужно здесь, и сделал это.
fun3 () {
exec 3< headers
exec 4< lines
while read -u3 head do;
echo $head
for i in {1..3}; {
read -u4 line; echo $line
}
done
exec 3<&-
exec 4<&-
}
real 0m0,009s
user 0m0,009s
sys 0m0,000s
И это довольно быстро)
import sys
with open(sys.argv[1]) as small, open(sys.argv[2]) as large:
for line in small:
print(line, end='')
for x in range(3):
print(large.readline(), end='')
real 0m0,021s
user 0m0,016s
sys 0m0,004s
Python тоже неплохо. И эти два тоже хорошо.
fun4 () {
while IFS= read -r; do
# Map 3 lines of big_file.txt without capturing newline character
mapfile -t -n 3 -u 3
# Output header $REPLY from small_file.txt
# followed by ${MAPFILE[@]} mapped lines of big_file.txt
printf '%s\n%s' "$REPLY" "${MAPFILE[@]}"
done <lines 3<headers
}
real 0m0,017s
user 0m0,012s
sys 0m0,004s
fun5 () {
while IFS= read -r; do
mapfile -t -n 3 -u 3 -C'echo "$REPLY";:' -c 3
printf '%s\n' "${MAPFILE[@]}"
done <headers 3<lines
}
real 0m0,011s
user 0m0,011s
sys 0m0,000s