Заполнение пустых полей

у меня есть очень большой файл с разделителями табуляции (около 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 ответов


Я предлагаю:

awk -F '\t' '{OFS=FS; ==""?=b:b=}1' file

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

$ awk '{sub(/^\t/,p"&");p=}1' file
F1      1
F1      2
F1      700
F2      89
F2      900
F2      10000
F2      19
F3      100
F3      60001