2014-02-12 6 views
0

я должен выполнить относительно простую задачу, в основном у меня есть огромное количество файлов в следующем форматеСомнения эффективности Баш скрипт

«2014-01-27», «7:20:38»,» «данные», «данные», «данные»

В основном я хотел бы извлечь первые 2 поля, преобразовать их в дату эпохи unix, добавить к нему 6 часов (разницу в часовом поясе) и заменить первые 2 оригинала столбцы с полученными миллисекундами (эпоха unix, начиная с 19700101, преобразованная в мельницы) Я написал сценарий, который отлично работает, но проблема в том, что это очень медленно, мне нужно запустить это более 150 файлов с общим количеством строк более 5.000.000 и я был wonderin г, если у вас какие-либо советы о том, как я мог бы сделать это быстрее, вот она:

#!/bin/bash 

function format() 
{ 
while read line; do 
entire_date=$(echo ${line} | cut -d"," -f1-2); 
trimmed_date=$(echo ${entire_date} | sed 's/"//g;s/,/ /g'); 
seconds=$(date -d "${trimmed_date} + 6 hours" +%s); 
millis=$((${seconds} * 1000)); 
echo ${line} | sed "s/$entire_date/\"$millis\"/g" >> "output" 
done < $* 
} 

format $* 
+0

Эпоха Unix - это секунды, а не миллисекунды. Это традиционно (long?) Целое число, но представления с десятичной частью были использованы для представления подсеточной точности. – tripleee

+0

Вы должны использовать '' $ @ "' вместо '$ *'. В настоящее время ваш код не может обрабатывать имена файлов с пробелами в них и т. Д. – tripleee

ответ

2

Использование функции существует mktime в awk, проверено, оно быстрее, чем perl.

awk '{t=$2 " " $4;gsub(/[-:]/," ",t);printf "\"%s\",%s\n",(mktime(t)+6*3600)*1000,substr($0,25)}' FS=\" OFS=\" file 

Вот результат теста.

$ wc -l file 
    1244 file 
    $ time awk '{t=$2 " " $4;gsub(/[-:]/," ",t);printf "\"%s\",%s\n",(mktime(t)+6*3600)*1000,substr($0,25)}' FS=\" OFS=\" file > /dev/null 

real 0m0.172s 
user 0m0.140s 
sys  0m0.046s 

    $ time perl -MDate::Parse -pe 'die "$0:$ARGV:$.: Unexpected input $_" 
     unless s/(?<=^")([^"]+)","([^"]+)(?=")/ (str2time("$1 $2")+6*3600)*1000 /e' file > /dev/null 

real 0m0.328s 
user 0m0.218s 
sys  0m0.124s 
3

Я попытался избежать внешних команд (кроме даты), чтобы выиграть время. Тесты показывают, что он в 4 раза быстрее, чем ваш код. (Хорошо, Perl решения в tripleee в 40 раз быстрее, чем у меня!)

#! /bin/bash 

function format() 
{ 
    while IFS=, read date0 date1 datas; do 
     date0="${date0//\"/}" 
     date1="${date1//\"/}" 
     seconds=$(date -d "$date0 $date1 + 6 hours" +%s) 
     echo "\"${seconds}000\",$datas" 
    done 
} 

output="output.txt" 

# Process each file in argument 
for file ; do 
    format < "$file" 
done >| "$output" 

exit 0 
+1

Исходная 'read line' не нужна; 'while IFS =, read date0 date1 datas; do' позволяет удалить первую строку тела. Конечный цикл 'for' может быть' для файла; do format <"$ file"; done> «$ output» ', устраняя необходимость предварительного создания файла. – chepner

+0

Спасибо! Сначала я прочитал первый скрипт.Исправлено: –

+1

Для любознательных '' | 'идентично' '', но позволяет '' $ output "' перезаписываться, если он существует, и установлена ​​опция 'noclobber'. – chepner

3

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

perl -MDate::Parse -pe 'die "$0:$ARGV:$.: Unexpected input $_" 
    unless s/(?<=^")([^"]+)","([^"]+)(?=")/ (str2time("$1 $2")+6*3600)*1000 /e' 

Я хотел бы рекомендовать Text::CSV но не он установлен здесь, и если у вас есть требования не касаются полей после второй вообще, это не могло бы быть то, что вам нужно в любом случае. Это быстро и грязно, но, вероятно, намного проще, чем «правильное» решение CSV.

Реальное мясом является str2time функцией от Date::Parse, который я представляю будет намного быстрее, чем неоднократно вызывая date (ISTR это делает некоторые запоминание внутри, так что он может сделать близлежащие даты быстро). Регулярное выражение заменяет первые два поля на выход; обратите внимание на флаг /e, который позволяет оценивать код Perl в заменяемой части. Утверждения (?<=^") и (?=") нулевой ширины требуют наличия этих совпадений, но не включают их в операцию замещения. (Я первоначально Подставив ограждающих двойные кавычки, но с этим изменением, они сохраняются, так как, очевидно, вы хотите, чтобы держать их.)

Изменение die к warn, если вы хотите, чтобы скрипт продолжать, несмотря на ошибки (возможно, перенаправить стандартную ошибку в файл!)

+0

thx, действительно работает быстро, но отсутствует первая строка каждого файла, он редактирует 99999 строк за 4 секунды вместо 9 – JBoy

+0

Я не вижу, как это может пропустить первую строку, если у вас нет строки заголовка с именами полей или что-то? Если вы хотите напечатать первую строку дословно, добавьте 'next if $. == 1; 'перед остальной частью скрипта (непосредственно перед' die'). – tripleee

+0

Или, может быть, 's /", "//и далее, если $. == 1; 'просто объединить первые два поля в первой строке. – tripleee