我将多个单独序列化的 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 实现此目的吗?
答案1
我仍然不确定你想要什么,但假设 Glenn Jackman 的解释是正确的,那么你需要进一步理解他的想法,以便能够搜索给定的字段名称。例如,
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”替换为“姓名”、“年龄”或任何您想要用于排序的字段。
当然,这个脚本并非万无一失。字段不能包含制表符,也不能包含“姓名”、“年龄”等关键字。
编辑:我将简要描述此脚本的功能。基本上,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)。然后,将读取下一行,整个过程将重新开始。
此脚本依赖于关键字不能出现在其他任何地方的假设。而这个假设并不容易解决。如果你想违反这个假设,就需要对这个输入文件的结构有更多了解。在大多数情况下,这种了解是可以实现的,因为这种歧义也会影响任何序列化解析器。例如,如果你知道字段名称(例如“age”)可以精确地出现在其他字段中,但只能出现在按以下顺序排列的字段中后age 字段,那么这个脚本就没问题了。在给定的示例中,如果 name 字段等于“age”(例如,没有大写字母等),那就很奇怪了。无论如何,这是一个难题,整本书都在处理它,所以我不会在这里总结它。如果您感兴趣,请 Google 搜索“编译器理论”。
您提到的其中一个见解可能是:了解字段的顺序。在这种情况下,整个脚本并不比 Glenn 的脚本好多少。您可以调整他的更简单的脚本以匹配您想要的每个字段。例如,考虑:
awk -F '"' '{print $8 "\t" $0}' filename |
sort |
cut -d $'\t' -f 2-
该脚本与 Glenn 提出的脚本几乎完全相同,只是它选择的是第八个字段(“年龄”)而不是第四个字段(“姓名”)。
答案2
有点像 Schwartz 变换:我假设名称始终是第四个引号分隔的字段
awk -F '"' '{print $4 "\t" $0}' filename |
sort |
cut -d $'\t' -f 2-
答案3
你可以做:
sort -t '"' -k4,4 filename
sort -t '"' -k8,8n filename
分别表示姓名和年龄,但是这不允许您通过名称选择字段,并且还需要繁琐的字段计数。
下面的脚本提供了一种更为强大的方法,可以通过以下任一方式运行:
./fieldsort "name" inputfile
some_prog | ./fieldsort "name"
您可以使用“name”或“age”作为字段名称(如果存在,也可以使用其他名称)。
仅需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 原生的方式来实现这个功能?