AWK – 是否可以search模式,然后根据通配符对结果进行sorting?

我正在将一些单独序列化的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"*"*"

我想sorting基于第二个通配符的内容find的行。 awk可以做到这一点吗?

我仍然不确定你想要什么,但假设格伦·杰克曼的解释是正确的,那么你想要进一步思考他的想法,以便能够search给定的字段名称。 例如,

 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”replace为“name”,“age”或你想用于sorting的任何字段。

这个脚本当然不是万无一失的。 字段不能包含制表符,也不能包含“name”,“age”等关键字。

编辑:我将简要介绍一下这个脚本的function。 基本上,awk需要一个给定的字段名称,并为每一行提取该字段的值。 因此,对于每个input行,它会输出相同的行,但将该字段的值作为前缀,并用制表符分隔两个元素。 这个输出是由sort命令进行的,它按照字典顺序对它进行sorting,因此它大多数是根据所选的字段值进行sorting的。 一旦按照这种方式sorting,这是由剪切命令采取的,剪切命令将其拼接在制表符上,丢弃用于sorting的字段,并仅显示其余的(其对应于来自原始文件的行,但现在按照通缉)。

一些更多细节:

在AWK中(实际上,在Gawk变体中),-v开关定义了一个variables,在这种情况下被命名为FN。 -F开关定义了一个字段分隔符,它将AWK从其input文件中读取的每一行进行分割。 在花括号之间定义的主要块是AWK程序,每个input行运行一次。 每个行字段按照-F开关分割,被引用$ 1,$ 2,…,$(NF-1),$ NF。 (NF是一个内部variables,总是等于当前行的字段数)。

正如我所说的,AWK逐行读取input,并为每个input运行该程序。 例如,如果它采用这一行:

 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上search完全匹配。 所以如果例如我们已经定义了FN = age,那么循环将停在$ 6,然后打印$ 8(即$(6 + 2),这里是“25”)和一个制表符,然后与整个input线本身($ 0)。 然后下一行将被读取,整个过程将重新开始。

这个脚本依赖于关键字不能在其他地方发生的假设。 而这个假设并不容易解决。 如果你想违反这个假设,那么需要更多地了解这个input文件的结构。 对于大多数目的而言,这样的见解是可以实现的,因为这种歧义也会影响任何序列化分析器。 例如,如果您知道字段名称(比如说“年龄”)可以完全显示在其他字段中,但是只在字段sorting在年龄字段之后,则该脚本可以保持原样。 在给定的例子中,有一个名字段等于“年龄”(比如,没有大写字母等)会很奇怪。 无论如何,这是一个棘手的问题,整本书都在处理这个问题,所以我不会在这里总结一下。 Google对“编译原理”感兴趣。

一个这样的见解可能是你提到的那个:了解领域的秩序。 在这种情况下,这个剧本并不比格伦好很多。 你可以调整他简单的脚本来匹配你想要的每个字段。 例如,考虑:

 awk -F '"' '{print $8 "\t" $0}' filename | sort | cut -d $'\t' -f 2- 

这个脚本与Glenn提出的几乎相同,只是select了第八个字段(“年龄”)而不是第四个(“名字”)。

Schwartzian变换types:我假设名称总是第四个以引号分隔的字段

 awk -F '"' '{print $4 "\t" $0}' filename | sort | cut -d $'\t' -f 2- 

可以这样做:

 sort -t '"' -k4,4 filename sort -t '"' -k8,8n filename 

姓名和年龄,但不允许您通过名称来select字段,也需要繁琐的字段计数。

在下面的脚本中提供了一个更健壮的方法,可以通过以下任一方式运行:

 ./fieldsort "name" inputfile some_prog | ./fieldsort "name" 

您可以使用“名称”或“年龄”作为字段名称(或者其他名称)。

只使用gawk而不需要任何其他的工具。

由于只有第一个logging检查了所需字段的位置,并且必须有一个字段值与logging中较早出现的所需字段名称相匹配,所以减less了误报的可能性。 这两个条件(第一个logging中的第一个出现)也使该脚本更快。

缺点是它期望所有的logging都是相同的格式(字段数量等)。

没有检查确保字段名被选中(虽然它必须存在),所以“s”(“string”字段types)将被接受,但是没有用。

如果在命令行上给出了多个文件名,则它们必须都具有相同的格式。 如果您使用的是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在本地执行此操作?