Заполнение пустых полей
у меня есть очень большой файл с разделителями табуляции (около 12 миллионов строк), который выглядит так:
F1 1
2
700
F2 89
900
10000
19
F3 100
60001
есть ли способ сделать это так:
F1 1
F1 2
F1 700
F2 89
F2 900
F2 10000
F2 19
F3 100
F3 60001
Я пробовал использовать скрипты sed, но это занимает так много времени.
sed 's/^/F1/' FILE | cut -c3- > FILE1 ; mv FILE1 FILE
Я мог бы сделать это в excel, используя
=IF(a2=="",c1,a2)
и тянут вниз. Однако Excel позволит мне загрузить только определенное количество строк.
(предполагая, что я скопировал "F1" в C1)
конечно, есть более простой способ с awk или sed?
7 ответов
perl -F'\t' -lane'$h = $F[0] ||= $h; print join "\t", @F'
задания правоассоциативны, так
$h = $F[0] ||= $h;
эквивалентно
$h = ( $F[0] ||= $h );
и тем самым
$F[0] ||= $h;
$h = $F[0];
и
$F[0] = $h if !$F[0];
$h = $F[0];
awk
на помощь!
$ awk 'BEGIN {FS=OFS="\t"}
{if(!="") p=; else =p}1' file
F1 1
F1 2
F1 700
F2 89
F2 900
F2 10000
F2 19
F3 100
F3 60001
это входной файл, который я использовал
$ cat -A file
F1^I1$
^I2$
^I700$
F2^I89$
^I900$
^I10000$
^I19$
F3^I100$
^I60001$
команда Perl будет выглядеть примерно так:
perl -F'\t' -ple '$c1 = $F[0] if $F[0]; $F[0] ||= $c1; $_=join"\t",@F' 40982582.tsv > your_output.tsv
более читаемо:
#!/usr/bin/perl -pl -F\t
$c1 = $F[0] if $F[0]; # save off the first column if we have one.
$F[0] ||= $c1; # override empty first-columns.
$_ = join "\t", @F; # set the topic back to the full line for -p to print
и затем выполнить:
perl yourscript.pl input_file.tsv > output_file.tsv
(вы также можете использовать флаг "-i" для перезаписи файла "на месте", но это фактически не экономит ваше время или дисковое пространство во время выполнения.)
тем не менее, как долго ваш файл, вот сколько времени это займет.
это sed
устранение:
sed -r -n '/\w+\s+\w+/{p; s/^(\w+\s+).*//; h};/^\w/!{G;s/^\s+(\w+)\s+(\w+\s+)//;p}' file.dat
F1 1
F1 2
F1 700
F2 89
F2 900
F2 10000
F2 19
F3 100
F3 60001
потребление времени и сравнение с другими решениями awk
это код для тестирования (скрипт)
#!/bin/sh
## Input file with data to process
inputfile="bigdata3.txt"
## solutions dir, that contains
## - solution files, and
## - every solution file contains code to evaluate
solutions="solutions/"
file_size_kb=$(du -k "$inputfile" | cut -f1)
echo "Size of input file: $file_size_kb kB"
file_lines_count=$(wc -l $inputfile | sed -r 's/\s*([0-9]+)\s+.*//')
echo "Lines of input file: $file_lines_count"
test_code="time $code > out.txt"
echo "Test code: '$test_code'"
for solution in $solutions* ; do
## output file deletion
if [ -f out.txt ]; then
rm out.txt
fi;
code_content=$(cat $solution)
code="time $code_content $inputfile > out.txt"
echo "--------------------------------------------------"
echo "Solution: $solution"
echo "Code : $code"
res=$(sh -c "cd $PWD; $code")
echo $res
## check correctness of output
incorrect_lines_count=$(sed -r -n "/^[^[a-zA-Z0-9_]+/p" out.txt | wc -l | sed -r 's/\s*([0-9]+)\s*.*//')
total_lines=$(wc -l out.txt | sed -r 's/\s*([0-9]+)\s+.*//')
if [ $incorrect_lines_count -eq 0 ] && [ $total_lines -eq $file_lines_count ]; then
echo "TEST PASSED"
else
echo "INVALID SOLUTION:"
echo " - not processed lines: $incorrect_lines_count (spaces at line beginning)"
echo " - total processed lines: $total_lines (expecting: $file_lines_count)"
fi
done;
и результаты (для входного файла 46kB):
Size of input file: 46034 kB
Lines of input file: 8658000
Test code: 'time $code > out.txt'
--------------------------------------------------
Solution: solutions/Cyrus_awk
Code : time awk -F '\t' '{OFS=FS; ==""?=b:b=}1' bigdata3.txt > out.txt
real 0m8.072s
user 0m7.644s
sys 0m0.420s
TEST PASSED
--------------------------------------------------
Solution: solutions/Ed_Morton_awk
Code : time awk '{sub(/^\t/,p"&");p=}1' bigdata3.txt > out.txt
real 0m11.887s
user 0m11.434s
sys 0m0.389s
TEST PASSED
--------------------------------------------------
Solution: solutions/Marek_Nowaczyk_sed
Code : time sed -r -n '/\w+\s+\w+/{p; s/^(\w+\s+).*//; h};/^\w/!{G;s/^\s+(\w+)\s+(\w+\s+)//;p}' bigdata3.txt >
out.txt
real 0m30.239s
user 0m29.577s
sys 0m0.545s
TEST PASSED
--------------------------------------------------
Solution: solutions/Tanktalus_perl
Code : time perl -F'\t' -ple '$c1 = $F[0] if $F[0]; $F[0] ||= $c1; $_=join"\t",@F' bigdata3.txt > out.txt
real 0m6.992s
user 0m6.692s
sys 0m0.281s
TEST PASSED
--------------------------------------------------
Solution: solutions/ikeagami_perl
Code : time perl -F'\t' -lane'$h = $F[0] ||= $h; print join "\t", @F' bigdata3.txt > out.txt
real 0m12.977s
user 0m12.463s
sys 0m0.483s
TEST PASSED
--------------------------------------------------
Solution: solutions/karakfa_awk
Code : time awk 'BEGIN {FS=OFS="\t"} {if(!="") p=; else =p}1' bigdata3.txt > out.txt
real 0m7.545s
user 0m6.832s
sys 0m0.498s
TEST PASSED
--------------------------------------------------
Solution: solutions/slitvinov_awk
Code : time awk 'BEGIN { FS = OFS = "\t" } NF == 1 { print pre, } NF == 2 { print (pre = ), }' bigda
ta3.txt > out.txt
real 0m8.333s
user 0m7.908s
sys 0m0.404s
INVALID SOLUTION:
- not processed lines: 5772000 (spaces at line beginning)
- total processed lines: 8658000 (expecting: 8658000)
выводы
@Tanktalus perl
решение имеет лучшую производительность, но awk
@karakfa и awk
@Cyrus solutions также делает что ж.
Offtopic
этой sed
решение имеет лучшую производительность на меньшем файле (из этого примера и для файла 8k), но очень медленно на больших данных.
$ cat pre.awk
BEGIN { FS = OFS = "\t" }
NF == 1 { print pre, }
NF == 2 { print (pre = ), }
использование:
$ awk -f pre.awk file.dat