В AWK почему несуществующее поле типа $ (NF + 1) не равно нулю?
При использовании AWK я пытаюсь понять, почему несуществующее поле (поле после $NF
) не сравнивается с числовым нулем.
В приведенном ниже примере строка ввода имеет два поля, поэтому согласно спецификации $3
должно быть "неинициализированное значение" и сравнивать равным 0. Другими словами, $3 == 0
должно возвращать true, но, как вы можете видеть ниже, возвращает false:
$ echo '1 2' | awk '{ print($3 == 0 ? "t" : "f") }'
f
Оба "One True AWK" (версия 20121220) и GNU AWK (версия 4.2.1) ведут себя одинаково. Здесь вывод GNU AWK:
$ echo '1 2' | gawk '{ print($3 == 0 ? "t" : "f") }'
f
Согласно спецификации POSIX AWK, несуществующие поля, такие как $3
должны быть неинициализированными значениями:
Ссылки на несуществующие поля (то есть поля после $ NF) должны оцениваться неинициализированным значением.
Кроме того, сравнения типа ==
должны выполняться численно, если один операнд является числовым, а другой - неинициализированным значением:
Сравнения (с операторами "<", "<=", "! =", "==", "> и"> = "должны выполняться численно, если оба операнда являются числовыми, если они являются числовыми и другой имеет строковое значение, которое является числовой строкой, или если оно числовое, а другое имеет неинициализированное значение. В противном случае операнды должны быть преобразованы в строки по мере необходимости...
И, наконец, неинициализированное значение "числовое значение" должно быть равно нулю:
Неинициализированное значение должно иметь как числовое значение нуля, так и строковое значение пустой строки.
Сравните это с неинициализированной переменной, которая сравнивается с равным нулю:
$ awk 'BEGIN { print(x == 0 ? "t" : "f") }'
t
Итак, в нашем первом примере $3
должно быть неинициализированным значением, ==
должно сравнить его численно, а его числовое значение должно быть равно нулю. Следовательно, мне кажется, что $3 == 0? "t": "f"
$3 == 0? "t": "f"
должен выводить t
вместо f
.
Может ли кто-нибудь помочь мне понять, почему это не так, или помочь мне понять, как я неправильно читаю спецификацию?
Ответы
Ответ 1
Есть интересный отрывок на языке программирования AWK Альфреда В. Ахо, Брайана У. Кернигана и Питера Дж. Вайнбергера (1988) (книга здесь):
Неинициализированные переменные создаются с числовым значением 0 и строковым значением ""
. Необязательные поля и поля, которые явно имеют нуль, имеют только строковое значение ""
; они не являются числовыми, но при принуждении к номерам они получают числовое значение 0.
источник: язык программирования AWK, раздел 2.2, стр. 45
Более того:
Неинициализированные переменные имеют числовое значение 0
и строковое значение ""
. Соответственно, если x
неинициализируется,
if (x) ...
является ложным и
if (!x) ...
if (x == 0) ...
if (x == "") ...
все верно. Но учтите, что
if (x == "0") ...
false.
Тип поля определяется по возможности, когда это возможно; например, $1++
подразумевает, что при необходимости необходимо принудительно $1++
$1
, а $1 = $1 "," $2
означает, что при необходимости будут принудительно привязаны к строкам $1
и $2
.
В контексте, где типы не могут быть надежно определены, например,
if {$1 == $2) ...
тип каждого поля определяется на входе. Все поля являются строками; кроме того, каждое поле, содержащее только число, также считается числовым. Поля, которые явно имеют нулевое значение, имеют строковое значение ""
; они не являются числовыми. Аналогичным образом обрабатываются несуществующие поля (т.е. Поля за пределами NF
) и $0
для пустых строк.
Как и для полей, так это для элементов массива, созданных split
.
источник: язык программирования AWK, приложение A, инициализация, сравнение и принуждение типа, стр. 192
На мой взгляд, эти строки хорошо объясняют наблюдаемое поведение, и, похоже, большинство программ следуют этому.
Кроме того, в добавление к сообщению rici:
При исследовании исходного кода GNU Awk 4.2.1 я обнаружил, что:
-
Неинициализированные переменные присваиваются Node
именем Nnull_string
который имеет флаги:
main.c: Nnull_string->flags = (MALLOC|STRCUR|STRING|NUMCUR|NUMBER);
-
Необязательным полям присваивается Node с именем Null_field
который является переопределенным Nnull_string
как:
field.c: *Null_field = *Nnull_string;
field.c: Null_field->valref = 1;
field.c: Null_field->flags = (STRCUR|STRING|NULL_FIELD); /* do not set MALLOC */
Если поля имеют значения (от awk.h
):
# define STRING 0x0002 /* assigned as string */
# define STRCUR 0x0004 /* string value is current */
# define NUMCUR 0x0008 /* numeric value is current */
# define NUMBER 0x0010 /* assigned as number */
# define NULL_FIELD 0x2000 /* this is the null field */
Функция сравнения int cmp_nodes(NODE *t1, NODE *t2, bool use_strcmp)
определенная в eval.c
, просто проверяет, установлен ли флаг NUMBER
как в t1
и в t2
:
if ((t1->flags & NUMBER) != 0 && (t2->flags & NUMBER) != 0)
return cmp_numbers(t1, t2);
Поскольку Null_field
не имеет числового поля, он просто предположит, что он представляет строку. Все это похоже на то, что цитирует книга!
Кроме того, из awk.h
:
* STRING and NUMBER are mutually exclusive, except for the special
* case of an uninitialized value, represented internally by
* Nnull_string. They represent the type of a value as assigned.
* Nnull_string has both STRING and NUMBER attributes, but all other
* scalar values should have precisely one of these bits set.
*
* STRCUR and NUMCUR are not mutually exclusive. They represent that
* the particular type of value is up to date. For example,
*
* a = 5 # NUMBER | NUMCUR
* b = a "" # Adds STRCUR to a, since a string value
* # is now available. But the type hasn't changed!
*
* a = "42" # STRING | STRCUR
* b = a + 0 # Adds NUMCUR to a, since numeric value
* # is now available. But the type hasn't changed!
Ответ 2
Насколько я вижу, вы правильно читаете спецификацию Posix. Спецификация Posix основана на языке программирования AWK (который включен в качестве справочной информации), но направлен на то, чтобы уточнить некоторые аспекты языка. В частности, предыдущие практики обработки строковых значений и числовых значений приводят к некоторым любопытным последствиям, некоторые из которых отмечены в разделе "Обоснование" описания утилиты Posix. По мнению авторов Posix, "поведение исторических реализаций было воспринято как слишком непредсказуемое и непредсказуемое", и, глядя на один из примеров, трудно не согласиться:
$ seq 1 4 | nawk '{
> a = "+2"
> b = 2
> if (NR % 2)
> c = a + b
> if (a == b)
> print "numeric comparison"
> else
> print "string comparison"
> }
> '
numeric comparison
string comparison
numeric comparison
string comparison
Точная обработка пустых и неопределенных значений полей является одной из отличий между спецификацией Posix и языком awk, определяемой языком программирования Awk. Поэтому, в конце концов, вам придется решить, какую спецификацию вы считаете окончательной.
Как вы заметили, Posix четко говорит, что: (переменные и специальные значения)
Ссылки на несуществующие поля (то есть поля после $ NF) должны оцениваться неинициализированным значением....
На самом деле это не просто недействительные поля, которые получают это лечение. Хотя пустые строки не являются "числовыми строками", как определено Posix [Примечание 1], исключение делается для пустых полей (которые возможны, если вы явно устанавливаете разделитель полей):
Каждая переменная поля должна иметь строковое значение или неинициализированное значение при создании. Переменные поля должны иметь неинициализированное значение при создании из $0
с использованием FS, и переменная не содержит никаких символов.
Операторы сравнения являются числовыми, если один аргумент является числом, а другой - числом, "числовой строкой" или неинициализированным значением: (Выражения в awk, добавленный курсором):
Сравнения (с операторами '<'
, "<="
, "!="
, "=="
, '>'
и ">="
должны выполняться численно, если оба операнда являются числовыми, если они являются числовыми и другой имеет строковое значение, которое является числовой строкой, или если оно числовое, а другое имеет неинициализированное значение. В противном случае операнды должны быть преобразованы в строки по мере необходимости, и должно быть выполнено сравнение строк...
Однако это не реализация Gnu awk, и, по-видимому, это не реализация многих других awks. Общие реализации:
-
Обрабатывать пустые и недопустимые поля как пустую строку (которая не является числовой строкой), а не унифицированное значение; а также
-
Сравните две "числовые строки", используя числовое сравнение, а не сравнение строк.
Я не могу найти архив списка рассылки awk, который достаточно далеко уходит вовремя, а исходная история в Savannah восходит только к 2006 году или около того, но Changelog включает следующую запись с 1997 года:
Sun Jan 19 23:37:03 1997 Арнольд Д. Роббинс
* field.c (get_field): Add new var that is like Nnull_string but
does not have numeric attributes, so that new fields are strings.
И код все еще отражает это решение. (Nnull_string
- неинициализированное значение gawk. Указанная переменная теперь является глобальным Null_field
.)
Интересно, что в правиле BEGIN
gawk (правильно) рассматривает $0
как неинициализированный, а не пустой:
$ gawk 'BEGIN{print $0 == 0, $1 == 0}'
1 0
Заметки
-
"Числовая строка" представляет собой строку, исходящую от пользовательского ввода, форма которого представляет собой номер. Это не включает цитированные литералы в awk-программе; "1"
- это строка, а не числовая строка. Возможные происхождение числовой строки перечислены в разделе "Выражения в awk", упомянутом выше; они включают поля, переменные среды и параметры командной строки, а атрибут сохраняется при назначении.
Имея форму номера, также определяется в этом разделе, где реализациям даются два варианта:
-
Используйте эквивалент strtod с дополнительным ограничением на то, что число обработанных чисел должно состоять по крайней мере из одного символа и что все конечные символы являются пробелами;
-
Используйте лексическое определение NUMBER из грамматики awk.
Ни одна из этих возможностей не позволяет пустой строке быть числовой строкой.
Ответ 3
Стандарт POSIX кажется более запутанным, чем необходимо при обсуждении этого вопроса, но посмотрите на это утверждение в таблице в разделе "Выражения в awk" стандарта POSIX:
Syntax | Name | Type of Result | Associativity
$expr | Field reference | String | N/A
поэтому тип $<whatever>
по умолчанию - String. Теперь давайте посмотрим, что говорится в этом разделе о том, как он может стать Numeric-String:
A string value shall be considered a numeric string if it comes from one of the following:
Field variables
<other N/A stuff - Ed.>
and an implementation-dependent condition corresponding to either case (a) or (b) below is met.
a) After the equivalent of the following calls to functions defined by the ISO C standard, string_value_end would differ from string_value, and any characters before the terminating null character in string_value_end would be <blank> characters:
char *string_value_end;
setlocale(LC_NUMERIC, "");
numeric_value = strtod (string_value, &string_value_end);
При передаче строки NULL strtod() возвращает 0, но string_value_end не будет отличаться от string_value, поэтому вышеупомянутый тест не будет распознавать NULL в виде числовой строки.
b) After all the following conversions have been applied, the resulting string would lexically be recognized as a NUMBER token as described by the lexical conventions in Grammar :
All leading and trailing <blank> characters are discarded.
If the first non- <blank> is '+' or '-', it is discarded.
Each occurrence of the decimal point character from the current locale is changed to a <period>.
NULL НЕ будет распознаваться как токен NUMBER для вышеупомянутого анализа.
Итак, в соответствии с вышеизложенным, поле ввода считается только числовым-строковым, если входное значение "похоже" на число, которое, конечно, NULL не является таким образом, что неопытный $<whatever>
является просто строкой со значением NULL и любым сравнением с использованием String - сравнение String (см. таблицу в https://www.gnu.org/software/gawk/manual/gawk.html#Variable-Typing для IMHO - самое четкое изображение типов сравнения), поэтому оно никогда не будет быть равно любому числу, включая 0, так как $X == 0
фактически рассматривается как $X == "0"
который совпадает с "" == "0"
когда $X
равно NULL.