Как отобразить TSV (csv) в консоли, когда пустые ячейки пропущены: 'столбец-t-s $ '\t''

Мы знаем, что это медленно от выполнения:

$ time ./junk.sh
Lines written: 14401
./junk.sh  2.27s user 3.31s system 21% cpu 25.798 total

(и это - версия, которая только печатает 4 часа, не 2 года.)

Получить лучшее понимание где bash проводит его время, мы можем использовать strace -c.

$ strace -c ./junk.sh
Lines written: 14401
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 79.01    0.128906           4     28806     14403 waitpid
 17.92    0.029241           2     14403           clone
  2.45    0.003999           0    158448           rt_sigprocmask
  0.33    0.000532           0     28815           rt_sigaction
  0.29    0.000479           0     14403           sigreturn

Таким образом, мы видим, что лучшие два вызова waitpid и clone. Они не занимают много времени самостоятельно (только 0,128906 секунды и 0,029241 секунды), но мы видим, что их называют много, таким образом, мы подозреваем, что проблемой является факт, мы должны запустить отдельное date управляйте для повторения каждого числа.

Таким образом я сделал некоторый поиск и узнал, что можно скомпилировать bash с gprof поддержка путем выполнения:

$ ./configure --enable-profiling --without-bash-malloc
$ make

Теперь использование этого:

$ ./bash-gprof junk.sh
Lines written: 14401
$ gprof ./bash-gprof gmon.out

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
  8.05      0.28     0.28    14403     0.00     0.00  make_child
  6.61      0.51     0.23                             __gconv_transform_utf8_internal
  5.75      0.71     0.20                             fork
  5.75      0.91     0.20   259446     0.00     0.00  hash_search
  5.17      1.09     0.18   129646     0.00     0.00  dispose_words

Так брать имена функций значимо, оно подтверждает, что проблема, мы делаем bash ветвление и вызов внешняя команда неоднократно.

Если мы перемещаемся >> в конец while цикл, это едва значительно потрудилось.

$ time ./junk2.sh
...
./junk2.sh  2.46s user 3.18s system 22% cpu 25.659 total

Но ответ Жабр находит способ только работать date однажды, и не удивительно, это намного быстрее:

$ time ./bash-gprof junk3.sh
Lines written: 14401
./bash-gprof junk3.sh  0.10s user 0.16s system 96% cpu 0.264 total

$ strace -c ./bash-gprof junk3.sh
Lines written: 14401
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.63    0.039538        5648         7         3 waitpid
  2.37    0.000961          37        26           writev
  0.00    0.000000           0         9           read
  ...
  0.00    0.000000           0         4           clone


$ gprof ./bash-gprof gmon.out 
Flat profile:

Each sample counts as 0.01 seconds.
 no time accumulated

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
  0.00      0.00     0.00     1162     0.00     0.00  xmalloc
  0.00      0.00     0.00      782     0.00     0.00  mbschr
  0.00      0.00     0.00      373     0.00     0.00  shell_getc

7 waitpids и 4 clones по сравнению с 28 806 и 14403 в оригинале!

Таким образом, мораль: Если необходимо назвать внешнюю команду в цикле, который много раз повторяется, Вы или должен найти способ переместить его из цикла или переключиться на язык программирования, который не должен называть внешнюю команду, чтобы сделать работу.


Согласно просьбе, тест на основе метода Iain (измененный для использования тех же имен переменной и цикличного выполнения):

#!/bin/bash
datein=junk.$$.datein
file=junk.$$
((secY2=3600*4))
cnt=0
secBeg=$(date --date="2010-01-01 00:00:00" +%s)
secEnd=$((secBeg+secY2))
((sec=secBeg))
while ((sec<=secEnd)) ; do
  echo @$sec >>"$datein"
  ((sec+=1))
  ((cnt+=1))
done
date --file="$datein" '+%Y-%m-%d %H:%M:%S' >>"$file"
ls -l "$file"
rm "$datein"
echo Lines written: $cnt

Результаты:

$ time ./bash-gprof ./junk4.sh 
Lines written: 14401
./bash-gprof ./junk4.sh  0.92s user 0.20s system 94% cpu 1.182 total

$ strace -c ./junk4.sh
Lines written: 14401
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 91.71    0.116007       14501         8         4 waitpid
  3.70    0.004684           0     14402           write
  1.54    0.001944           0     28813           close
  1.35    0.001707           0     72008         1 fcntl64
  0.88    0.001109           0     43253           rt_sigprocmask
  0.45    0.000566           0     28803           dup2
  0.36    0.000452           0     14410           open

$ gprof ./bash-gprof gmon.out 
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 22.06      0.15     0.15                             __gconv_transform_utf8_internal
 16.18      0.26     0.11                             mbrtowc
  7.35      0.31     0.05                             _int_malloc
  5.88      0.35     0.04                             __profile_frequency
  4.41      0.38     0.03   345659     0.00     0.00  readtok
  4.41      0.41     0.03                             _int_free
  2.94      0.43     0.02   230661     0.00     0.00  hash_search
  2.94      0.45     0.02    28809     0.00     0.00  stupidly_hack_special_variables
  1.47      0.46     0.01   187241     0.00     0.00  cprintf
  1.47      0.47     0.01   115232     0.00     0.00  do_redirections

Так close и open обнаруживаются.

Теперь наблюдение Eelvex о >> на строку по сравнению с > вокруг while цикл начинает иметь значение.

Давайте факторизуем его...

#!/bin/bash
datein=junk.$$.datein
file=junk.$$
((secY2=3600*4))
cnt=0
secBeg=$(date --date="2010-01-01 00:00:00" +%s)
secEnd=$((secBeg+secY2))
for ((sec=secBeg; sec<=secEnd; sec=sec+1)) ; do
  echo @$sec
  ((cnt+=1))
done >"$datein"
date --file="$datein" '+%Y-%m-%d %H:%M:%S' >>"$file"
ls -l "$file"
rm "$datein"
echo Lines written: $cnt

$ time ./junk6.sh
Lines written: 14401
./junk6.sh  0.58s user 0.14s system 95% cpu 0.747 total

$ strace -c junk6.sh
Lines written: 14401
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.41    0.092263       11533         8         4 waitpid
  2.06    0.001949           0     43252           rt_sigprocmask
  0.53    0.000506           0     14402           write
  0.00    0.000000           0        13           read
  0.00    0.000000           0        10           open
  0.00    0.000000           0        13           close
  0.00    0.000000           0         1           execve

$ gprof ./bash-gprof gmon.out
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 10.00      0.05     0.05    72025     0.00     0.00  expand_word_internal
 10.00      0.10     0.05                             __gconv_transform_utf8_internal
  8.00      0.14     0.04                             __profile_frequency
  8.00      0.18     0.04                             _int_malloc
  4.00      0.20     0.02  1355024     0.00     0.00  xmalloc
  4.00      0.22     0.02   303217     0.00     0.00  mbschr

Который является также очень, намного быстрее, чем исходный сценарий, но немного медленнее что Gilles.

12
13.01.2012, 18:13
2 ответа

Можно просто использовать Debian column. Это предоставляет возможность -n который заставляет его работать точно, как Вы хотите.

С другой стороны, можно поместить пространство в пустые столбцы, с помощью sed:

sed ':x s/\(^\|\t\)\t/\1 \t/; t x' < in.tsv | column -t -s $'\t'

пример:

$ sed ':x s/\(^\|\t\)\t/\1 \t/; t x' < in.tsv | column -t -s $'\t'
A   B   C  D
    b1     d1
           d2
a3         d3
12
27.01.2020, 19:56
  • 1
    , я боюсь, как sed будет вести себя с альтернативным '^\\| \t'... из-за '^', не указывает атомы. (Так, это собирающийся занимать место \1 с пустой строкой? относительно –  Grzegorz Wierzowiecki 13.01.2012, 16:22
  • 2
    Да, \(^\) один соответствия пустая строка, привязанная на начале строки. \1 "производит копию" той пустой строки. форма жизни –  angus 13.01.2012, 18:22
sed 's/||/| |/g;s/||/| |/g' filename-here

Вышеприведенная команда предназначена для трубы, поэтому замените ее на табличку.

Нужно просто заменить пустые столбцы на пустое место и передать вывод на уже используемую команду.

0
27.01.2020, 19:56

Теги

Похожие вопросы