AWK - можно ли искать шаблон, а затем сортировать результаты по шаблону?
Я храню несколько индивидуально сериализованных массивов PHP в файл. Каждая строка файла содержит один сериализованный массив. Например:
a:2:{s:4:"name";s:8:"John Doe";s:3:"age";s:2:"20";}
a:2:{s:4:"name";s:8:"Jane Doe";s:3:"age";s:2:"15";}
a:2:{s:4:"name";s:12:"Steven Tyler";s:3:"age";s:2:"35";}
a:2:{s:4:"name";s:12:"Jim Morrison";s:3:"age";s:2:"25";}
a:2:{s:4:"name";s:13:"Apple Paltrow";s:3:"age";s:2:"75";}
a:2:{s:4:"name";s:12:"Drew Nickels";s:3:"age";s:2:"34";}
a:2:{s:4:"name";s:11:"Jason Proop";s:3:"age";s:2:"36";}
Вот мой вопрос:
Можно ли "awk" этот файл по следующей схеме: "name"*"*"
Я хотел бы отсортировать найденные строки на основе содержимого второго подстановочного знака. Можно ли сделать это с помощью awk?
3 ответа
Я до сих пор не уверен в том, что вы хотите, но если предположить, что интерпретация Гленна Джекмана верна, то вы захотите развить его идею немного дальше, чтобы иметь возможность искать заданное имя поля. Например,
awk -v FN="xxxx" -F '"' '{
i=1;
while (i<=NF-2) {
if ($i==FN) {
print $(i+2) "\t" $0;
next
} else {
i++
}
}
}' filename | sort | cut -d $'\t' -f 2-
Здесь вы бы заменили "xxxx" на "name", "age" или любое другое поле, которое вы хотите использовать для сортировки.
Этот сценарий не является надежным, конечно. Поля не могут содержать символы табуляции и не могут содержать ключевые слова, такие как "имя", "возраст" и т. Д.
Изменить: я кратко опишу, что делает этот сценарий. По сути, awk принимает заданное имя поля и для каждой строки извлекает значение этого поля. Таким образом, для каждой строки ввода она выводит ту же строку, но с добавленным к ней значением этого поля, и разделяет оба элемента символом табуляции. Этот вывод берется командой sort, которая сортирует ее лексикографически, и, таким образом, она в основном сортируется на основе этого предварительно добавленного значения, которое является значением поля, которое вы выбрали. После сортировки таким образом это делается командой cut, которая объединяет его с символом табуляции, отбрасывая поле, которое использовалось для сортировки, и показывая только остальное (что соответствует строкам из исходного файла, но теперь сортируется по мере того, как вы в розыске).
Еще несколько деталей:
В AWK (фактически в варианте Gawk) ключ -v определяет переменную, в данном случае с именем FN. Ключ -F определяет разделитель полей, который разделяет каждую строку, которую AWK читает из своего входного файла. Основным блоком, определенным между фигурными скобками, является программа AWK, которая запускается один раз для каждой строки ввода. Каждое поле строки, разделенное в соответствии с ключом -F, ссылается на $1, $2, ..., $(NF-1), $NF. (NF - встроенная переменная, которая всегда равна количеству полей в текущей строке).
Как я уже сказал, AWK читает строку ввода построчно и запускает эту программу для каждого. Например, если он принимает эту строку:
a:2:{s:4:"name";s:12:"Jim Morrison";s:3:"age";s:2:"25";}
Затем он разбивает его на двойные кавычки, вот так:
$1 = a:2:{s:4:
$2 = name
$3 = ;s:12:
$4 = Jim Morrison
$5 = ;s:3:
$6 = age
$7 = ;s:2:
$8 = 25
$9 = ;}
Затем скрипт перебирает каждое поле в поисках точного соответствия в FN. Так, например, если мы определили FN=age, цикл остановится на $6, тогда он напечатает $8 (т. Е. $(6+2), который здесь равен "25"), объединенный с символом табуляции, а затем со всем сама строка ввода ($0). Затем будет прочитана следующая строка, и весь процесс начнется снова.
Этот сценарий основан на предположении, что ключевые слова не могут встречаться где-либо еще. И это предположение не легко обойти. Если вы хотите нарушить это допущение, необходимо больше понять, как устроен этот входной файл. Для большинства целей такое понимание достижимо, потому что эта неоднозначность также затронет любой анализатор сериализации. Например, если вы знаете, что имя поля (скажем, "возраст") может появляться точно внутри других полей, но только в полях, упорядоченных после поля возраста, тогда этот сценарий работает как есть. В данном примере было бы странно иметь поле имени, равное "возрасту" (например, без заглавных букв и т. Д.). В любом случае, это сложная проблема, и целые книги справляются с ней, поэтому я не буду здесь ее резюмировать. Google для "теории компилятора", если вам интересно.
Возможно, вы упомянули одно такое понимание: зная порядок полей. В этом случае весь этот сценарий не намного лучше, чем у Гленна. Вы можете адаптировать его более простой сценарий, чтобы соответствовать каждому полю, которое вы хотите. Например, рассмотрим:
awk -F '"' '{print $8 "\t" $0}' filename |
sort |
cut -d $'\t' -f 2-
Этот сценарий практически идентичен предложенному Гленном, только он выбирает в восьмом поле ("возраст") вместо четвертого ("имя").
Вид преобразования Шварца: я предполагаю, что имя всегда является четвертым разделенным кавычками полем
awk -F '"' '{print $4 "\t" $0}' filename |
sort |
cut -d $'\t' -f 2-
Вы могли бы сделать:
sort -t '"' -k4,4 filename
sort -t '"' -k8,8n filename
для имени и возраста, соответственно, но это не позволяет вам выбирать поле по его имени, а также требует утомительного подсчета полей.
Более надежный метод представлен в приведенном ниже скрипте, который можно запустить одним из следующих способов:
./fieldsort "name" inputfile
some_prog | ./fieldsort "name"
Вы можете использовать "имя" или "возраст" в качестве имени поля (или другие, если они присутствуют).
Только gawk
используется без каких-либо других утилит.
Вероятность ложных срабатываний снижена, поскольку только позиция первой записи проверяется на предмет позиции требуемого поля, и должно быть значение поля, совпадающее с именем нужного поля, появившимся ранее в записи. Эти два условия (первое появление в первой записи) также делают этот сценарий быстрее.
Недостатком является то, что все записи должны быть в одном и том же формате (количество полей и т. Д.).
Не выполняется проверка, чтобы убедиться, что выбрано имя поля (хотя оно должно существовать), поэтому, например, "s" (тип поля "строка") будет принят, но не будет полезен.
Если в командной строке указано несколько имен файлов, все они должны иметь одинаковый формат. Если вы используете Gawk 4, вы можете изменить BEGIN
в BEGINFILE
а также END
в ENDFILE
(и передвиньте строки перед getline
и его комментарии к новому BEGIN
пункт), чтобы обойти это ограничение.
#!/usr/bin/gawk -f
func isnum(x) {
# not foolproof
return(x == x + 0)
}
BEGIN {
fieldname = ARGV[1]
delete ARGV[1]
FS = "[;:\"]"
# since gawk doesn't have a numeric sort, pad numbers
padstr = "000000000000"
# process the first line to see which field we want
# do this in the BEGIN clause to avoid repeating it for every record
getline
split($0, fields, FS)
for (f = 1; f <= length(fields); f++) {
if (fields[f] == fieldname) {
field = f + 5
break
}
}
if (field == 0) {
print "field '" fieldname "' not found in file '" FILENAME "'"
exit
}
if (isnum($field))
# pad will be null for non-numeric data
pad = substr(padstr, 1, length(padstr) - length($field))
# since we burned the first line, we need to go ahead and save it here
# the record number is included in the index to prevent losing records
# that have duplicate values in the field of interest
array[pad $field, NR] = $0
}
{
# save each of the rest of the lines in the array indexed by the field of interest
if (isnum($field))
pad = substr(padstr, 1, length(padstr) - length($field))
array[pad $field, NR] = $0
}
END {
# sort and output
c = asorti(array, indices)
for (i = 1; i <= c; i++)
print array[indices[i]]
}
Но мне интересно, почему вы не делаете это изначально в PHP?