2017-01-13 10 views
4

У меня есть файл csv ввода. На самом деле мне нужно выбрать значения 2-го и 3-го столбцов во входном файле и вам необходимо преобразовать зону часового пояса обоих значений (от PT до CT). После преобразования мне нужно заменить преобразованные значения часовых поясов в файл.Unix-скрипт - Нуждаются предложения по повышению производительности (сценарий оболочки)

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

Каждая строка состоит из 5 столбцов - разделенный запятыми файл

CHID-123456323,2017-01-09 17:17:58-08:00,2017-01-09 17:39:25-08:00,hello,123456733 
CHID-123456733,2017-01-09 17:16:58-08:00,2017-01-09 18:04:09-08:00,hello,123456734 
CHID-123433589,2017-01-09 17:16:55-08:00,2017-01-09 17:40:29-08:00,hello,123456735 
CHID-123000789,2017-01-09 17:16:52-08:00,2017-01-09 17:46:41-08:00,hello,123456736 

Сценарий: я написал ниже сценарий, это дает точный результат, что я ожидал. Но при увеличении количества входных записей требуется больше времени. Например, 20 тысяч записей занимает 1 час 15 минут.

может ли кто-нибудь посмотреть этот скрипт и предложить, как улучшить производительность?

Сценарий:

while read i 
do 
    var1=`echo $i | awk -F',' '{ print $2 }'` 

    var1_EPOCH=`date --date="${var1}" +%s` 
    var1_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" [email protected]$var1_EPOCH` 
    sed -i "${cnt}s/${var1}/${var1_CTZ}/" filename 

    var2=`echo $i | awk -F',' '{ print $3 }'` 
    var2_EPOCH=`date --date="${var2}" +%s` 
    var2_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" [email protected]$var2_EPOCH` 
    sed -i "${cnt}s/${var2}/${var2_CTZ}/" filename 

    cnt=$(($cnt+1)) 
done < filename 

здесь является ожидаемым из положить файл

Окончательных выходной файл:

CHID-123456323,2017-01-09 19:17:58,2017-01-09 19:39:25,hello,123456733 
CHID-123456733,2017-01-09 19:16:58,2017-01-09 20:04:09,hello,123456734 
CHID-123433589,2017-01-09 19:16:55,2017-01-09 19:40:29,hello,123456735 
CHID-123000789,2017-01-09 19:16:52,2017-01-09 19:46:41,hello,123456736 
+0

Не используйте awk для разграничения строки: используйте функции строковой строки $ {i # *,} и $ {%} (которая является нотарией bash) для извлечения дат. Вместо того, чтобы запускать sed дважды, объедините две подстановки в одну: s/D1/D2/g; s/D3/D4/g. Кроме того, время начала два раза в строке немного тяжело, PT и CT переключаются в летнее время в тот же день, поэтому разница во времени всегда одинакова. – user1666959

+0

Это, вероятно, будет намного быстрее, если вы перепишете его на Perl, просто потому, что вы не будете запускать 10 подпроцессов на строку (или переписывать весь файл дважды для каждой строки ввода). – melpomene

ответ

1

Ksh имеет достаточно встроенных функций для вас.

Образец входного файла:

[STEP 100] $ echo $BASH_VERSION 
4.4.5(2)-release 
[STEP 101] $ cat file 
CHID-123456323,2017-01-09 17:17:58-08:00,2017-01-09 17:39:25-08:00,hello,123456733 
CHID-123456733,2017-01-09 17:16:58-08:00,2017-01-09 18:04:09-08:00,hello,123456734 
CHID-123433589,2017-01-09 17:16:55-08:00,2017-01-09 17:40:29-08:00,hello,123456735 
CHID-123000789,2017-01-09 17:16:52-08:00,2017-01-09 17:46:41-08:00,hello,123456736 

Сценарий:

[STEP 102] $ cat time.ksh 
tz=America/Chicago 
pattern='(.+),(.+),(.+),(.+),(.+)' 
while read -r line; do 
    if [[ $line =~ $pattern ]]; then 
     c1=${.sh.match[1]} 
     c2=${.sh.match[2]} 
     c3=${.sh.match[3]} 
     c4=${.sh.match[4]} 
     c5=${.sh.match[5]} 

     TZ=$tz printf '%(%Y-%m-%d %T)T' "$c2" | read c2 
     TZ=$tz printf '%(%Y-%m-%d %T)T' "$c3" | read c3 

     print -r -- "$c1,$c2,$c3,$c4,$c5" 
    else 
     print -r -- "$line" 
    fi 
done 

Пример вывода:

[STEP 103] $ ksh time.ksh < file 
CHID-123456323,2017-01-09 19:17:58,2017-01-09 19:39:25,hello,123456733 
CHID-123456733,2017-01-09 19:16:58,2017-01-09 20:04:09,hello,123456734 
CHID-123433589,2017-01-09 19:16:55,2017-01-09 19:40:29,hello,123456735 
CHID-123000789,2017-01-09 19:16:52,2017-01-09 19:46:41,hello,123456736 

Сделать 20000 строк файла:

[STEP 104] $ rm -f bigfile 
[STEP 105] $ fourlines=$(<file) 
[STEP 106] $ for ((i=0; i<5000; ++i)); do printf '%s\n' "$fourlines" >> bigfile; done 
[STEP 107] $ wc -l bigfile 
    20000 bigfile 
Тест производительности

Давайте его:

[STEP 108] $ time ksh time.ksh <bigfile> newfile 

real 1m36.849s 
user 0m27.376s 
sys  0m46.741s 
[STEP 109] $ tail -n 4 newfile 
CHID-123456323,2017-01-09 19:17:58,2017-01-09 19:39:25,hello,123456733 
CHID-123456733,2017-01-09 19:16:58,2017-01-09 20:04:09,hello,123456734 
CHID-123433589,2017-01-09 19:16:55,2017-01-09 19:40:29,hello,123456735 
CHID-123000789,2017-01-09 19:16:52,2017-01-09 19:46:41,hello,123456736 
[STEP 110] $ ksh --version 
    version   sh (AT&T Research) 93u+ 2012-08-01 
[STEP 111] $ 
0

Вы не должны пытаться sed файл внутри вашего цикла, а просто написать к выходу. Вы можете избежать некоторых команд awk, когда знаете, что read может читать больше переменных за один вызов. Эти улучшения изменит свой код в

while IFS=, read -r chid d1 d2 rest 
do 
    var1_EPOCH=`date --date="${d1}" +%s` 
    var1_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" [email protected]$var1_EPOCH` 
    var2_EPOCH=`date --date="${d2}" +%s` 
    var2_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" [email protected]$var2_EPOCH` 
    printf "%s,%s,%s,%s\n" "${chid}" "${var1_CTZ}" "${var2_CTZ}" "${rest}" 
done < filename 

Вы можете перейти к $(command) нотации, избежать некоторых переменных и двойных заданий с

tz=":America/Chicago" 
tformat="%Y-%m-%d %T" 
while IFS=, read -r chid d1 d2 rest 
do 
    printf "%s,%s,%s,%s\n" "${chid}" \ 
     "$(TZ=${tz} date +"${tformat}" [email protected]$(date --date="${d1}" +%s))" \ 
     "$(TZ=${tz} date +"${tformat}" [email protected]$(date --date="${d2}" +%s))" \ 
     "${rest}" 
done < filename 

Логическим Следующим усовершенствованием является использование awk (быстрее, чем писать while- петля).

EDIT: добавить AWK решение

В этом случае awk трудно, так как вы не хотите использовать system() вызов для преобразования даты (не вызывать другие инструменты). Когда ваш csv имеет 1 часовой пояс, вы можете избежать поиска информации о часовом поясе с фиксированными значениями.
Пропустив много расчета будет сделать следующий awk явного победителя:

awk -F, '{ 
    split($2,A,"[-: ]"); 
    T1=mktime(A[1] " " A[2] " " A[3] " " A[4] " " A[5] " " A[6]); 
    split($3,B,"[-: ]"); 
    T2=mktime(B[1] " " B[2] " " B[3] " " B[4] " " B[5] " " B[6]); 
    printf("%s,%s,%s,%s,%s\n",$1, 
     strftime("%Y-%m-%d %T",T1+7200), 
     strftime("%Y-%m-%d %T",T2+7200), 
     $4, 
     $5); 
} filename 

При необходимости вы можете вычислить другое значение для смещения по времени с использованием A[7] и B[7].

Когда выше работы пропустите здесь. Внизу - это только идея, когда вам все еще нужны новые возможности.
Другая стратегия заключается в том, чтобы избежать многократной конвертации одной и той же даты:
Когда ваш входной файл имеет много одинаковых временных меток (равный день + час), и у вас есть большой входной файл, вы можете сначала преобразовать уникальные часы и использовать их, когда вы обрабатываете большой файл.
Сделать «перевод помощник» с фиксированными строками, что-то вроде

# Becomes dirty when you want to cut out the minutes/seconds: 
cut -d, -f2,3 filename | tr "," "\n" | sort -u 
# Hard to read/debug/maintain 
sed 's/^[^,]*,\([^:]*\)[^-]*\([^,]*\).*/\1\2/' filename 

Преобразовать эти даты и хранить в каком-то файле отображение, и использовать это для перевода файла может улучшить решение больше, но это должно быть последним вещь, чтобы попробовать (сначала попробуйте awk).