awk "дата" | getline var кэширует свое значение, но только иногда

В рамках более крупного сценария awkмне нужно было преобразовать произвольную строку даты в секунды, поскольку эпоха. Это недоступно как функция awk, поэтому я подумал, что могу вернуться к вызову dateдля каждой строки ввода. (Оглядываясь назад, я мог бы использовать perl, но давайте отложим эту мысль.)

Увидев некоторые неожиданные результаты, я свел проблему к следующему ( bashи GNU awk)

for f in {1..5}; do echo $f; sleep 2; done | awk '{ "date" | getline x; printf ">>%s<<\n", x }'

Все тот же результат, хотя я подтвердил, что Цикл awkдействительно выполняется только раз в две секунды

>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<

Возможно, кэширует getline. Итак, я попробовал это

for f in {1..5}; do echo $f; sleep 2; done | awk '{ "date; : " NR | getline x; printf ">>NR=%d - %s<<\n", NR, x }'

>>NR=1 - 29 Jun 2020 10:44:05<<
>>NR=2 - 29 Jun 2020 10:44:07<<
>>NR=3 - 29 Jun 2020 10:44:09<<
>>NR=4 - 29 Jun 2020 10:44:11<<
>>NR=5 - 29 Jun 2020 10:44:13<<

Все выглядит хорошо. Кэширование (если это так) отключено, и я получаю ожидаемые значения от date.

Затем я продолжил этот путь еще раз, предоставив повторяющиеся значения в команде, переданной в getline

for f in 1 2 1 1 2 3; do echo $f; sleep 2; done | awk '{ "date; : " $1 | getline x; printf ">>NR=%d - f=%d - %s<<\n", NR, $1, x }'

>>NR=1 - f=1 - 29 Jun 2020 10:43:01<<
>>NR=2 - f=2 - 29 Jun 2020 10:43:03<<
>>NR=3 - f=1 - 29 Jun 2020 10:43:03<<
>>NR=4 - f=1 - 29 Jun 2020 10:43:03<<
>>NR=5 - f=2 - 29 Jun 2020 10:43:03<<
>>NR=6 - f=3 - 29 Jun 2020 10:43:11<<

Я ожидал, что строка 3 либо приведет к новой оценке команды (доставке нового значения даты), либо иначе повторяя значение из первой строки. Ни то, ни другое не происходит.

Это поставило меня в тупик. Я не понимаю, почему я получаю одинаковые значения для строк 2-5. Изменение fс 1на 2явно отключило любое кэширование. Но изменение fс 2обратно на 1не дало мне кэшированную копию первого f=1, а повторило значение для f=2. Изменение командной строки на новое значение с f=3инициировало новый вызов date.

Почему?

9
29.06.2020, 12:49
2 ответа

В руководстве по GNU awk упоминается , что:

If the same file name or the same shell command is used with getline more than once during the execution of an awk program (see section Explicit Input with getline), the file is opened (or the command is executed) the first time only. At that time, the first record of input is read from that file or command. The next time the same file or command is used with getline, another record is read from it, and so on.

Таким образом, он запускает команду только один раз, а при дальнейшем чтении получает EOF, оставляя старое значение xбез изменений. Сравните с тем, что произойдет, если мы будем удалять xпосле каждого чтения :

.
$ for f in {1..3}; do echo $f; sleep 2; done |
   awk '{ "date" | getline x; printf ">>%s<<\n", x; x ="done" }'
>>Mon Jun 29 13:37:53 EEST 2020<<
>>done<<
>>done<<

Если мы заменим здесь команду dateчем-то, что хранит запись о том, когда она выполняется, мы также увидим, что запись показывает, что она выполняется только один раз.

getlineтакже возвращает ноль в конце EOF и -1 в случае ошибки,чтобы мы могли проверить, что:

$ for f in {1..3}; do echo $f; sleep 2; done |
    awk '{ if ("date" | getline x > 0) printf ">>%s<<\n", x; else printf "error or eof\n"; }'
>>Mon Jun 29 13:46:58 EEST 2020<<
error or eof
error or eof

Вам нужно close()явным образом указать канал, чтобы awk снова открыл его в следующий раз.

$ for f in {1..3}; do echo $f; sleep 2; done |
   awk '{ "date" | getline x; printf ">>%s<<\n", x; x = "done"; close("date") }'
>>Mon Jun 29 13:39:19 EEST 2020<<
>>Mon Jun 29 13:39:21 EEST 2020<<
>>Mon Jun 29 13:39:23 EEST 2020<<

С помощью "date; : " NR | getline x;все командные строки различны, так что вы получаете отдельный канал для каждой.

С "date; : " $1 | getline x;, когда $1повторяется, вы получаете ту же проблему, что и в первом случае, второе чтение в тот же канал достигает EOF.

8
18.03.2021, 23:23

Мне не ясно, что означает «произвольное форматирование», но подпрограммы времени GNU/awk могут делать все, что может делать команда date, и даже больше. Если вы хотите показать свои фактические входные данные, я могу объяснить, как адаптировать эту демонстрацию к реальному приложению.

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

Скрипт:

#! /bin/bash

AWK='
BEGIN { 
    #.. Set up conversion from month names to numeric.
    split ("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", V, / /);
    for (k in V) iMth[V[k]] = k;
    #.. These are only needed to verify the epoch using /bin/date.
    fmtSys = "echo -n \047/bin/date: \047; date -d \047 %s + %d secs\047 \047%s\047\n";
    Base = "1970-01-01 00:00:00 UTC";
    Date = "+%Y-%m-%d %T";
}
function Show (V, Local, ds, epoch) {
    printf ("\n");
    ds = sprintf ("%d %d %d %d %d %d %d", V[8], V[10], V[2], V[4], V[5], V[6], -1);
    epoch = mktime (ds);
    printf ("datespec: %s; epoch: %d\n", ds, epoch);
    printf ("ISO: %s\n", strftime ("%F %T %Z Week %W Day %w", epoch));
    #.. Call date command to verify.
    system (sprintf (fmtSys, Base, epoch, Date));
}
function Fix (tx, Local, ds, V) {
    split (tx, V, /[^A-Za-z0-9]/);
    V[10] = iMth[V[3]];
    Show( V);

    printf ("\n.. Go back 10 months and 43 days\n");
    V[10] -= 10; V[2] -= 43;
    Show( V);

    printf ("\n.. and forward 427 minutes\n");
    V[5] += 427;
    Show( V);
} 
{ printf ("\n.. Input date ::%s::\n", $0); }
{ Fix( $0); }
'

    printf 'Mon 29 Jun 16:04:42 BST 2020\n' | awk "${AWK}"

Испытание:

paul $./myDate

.. Input date ::Mon 29 Jun 16:04:42 BST 2020::

datespec: 2020 6 29 16 4 42 -1; epoch: 1593443082
ISO: 2020-06-29 16:04:42 BST Week 26 Day 1
/bin/date: 2020-06-29 16:04:42

.. Go back 10 months and 43 days

datespec: 2020 -4 -14 16 4 42 -1; epoch: 1563375882
ISO: 2019-07-17 16:04:42 BST Week 28 Day 3
/bin/date: 2019-07-17 16:04:42

.. and forward 427 minutes

datespec: 2020 -4 -14 16 431 42 -1; epoch: 1563401502
ISO: 2019-07-17 23:11:42 BST Week 28 Day 3
/bin/date: 2019-07-17 23:11:42
paul $ 
1
18.03.2021, 23:23

Теги

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