常规 awk - 轻松对数组索引进行排序以按所选顺序输出它们

常规 awk - 轻松对数组索引进行排序以按所选顺序输出它们

[编辑:澄清我需要一个在 awk 中解决方案,并更正我需要对“索引”进行排序(或者更确切地说,以排序的方式输出它们)而不是模棱两可的“值”)]

在 awk 中,我经常对事物进行计数,或者在数组内存储一组值,使用这些值作为索引(利用 awk 的indexes_are_hashes 机制)

例如:如果我想知道我遇到了多少个不同的 $2 值,以及每个值出现的频率:

awk '
   ... several different treatments ...
   { count[$2]++ } 
   ... other treatments ...
   END { for(str in count) { 
           print "counted: " str " : " count[str] " times." 
           ... and other lines underneath, with additional infos ...
          }
       }
 '

问题是(非 GNU 或其他更好的版本)常规 awk (和常规 nawk):

  • [A] 不会按照“遇到”不同值的顺序输出它们,
  • [B] 也不提供按数字或字母顺序浏览索引的简单方法

对于[A]:做起来不太难..只需另一个数组来索引“新看到的”条目。

问题针对 [B]:我怎样才能通过简单的调用来对不同索引的显示进行重新排序?

(注意:我知道 gnu awk 对于 [B] 有一种“简单”的方法:https://www.gnu.org/software/gawk/manual/html_node/Controlling-Array-Traversal.html...但我想要在常规 awk/nawk 中执行类似操作的方法!)

(即:我需要做一个循环来输出看到的不同索引,对它们进行排序,将它们[在旧的 awk...] 中重新读取为“某物”(例如:另一个数组ordered_seen?)并使用该东西来显示所看到的内容必须按所选顺序排列。里面 awk在每个索引下我经常需要输出一段附加信息。 awk 之外的“排序”会重新排序所有内容)

到目前为止:我发现没有“公理化”的单行(或n行?)方法来做到这一点。

我最终得到了一个需要几行的拼凑,通过排序将每个值输出到一个文件,然后重新读取已排序的文件并将每行按顺序插入到sorted_countindexes [n ++]中,然后 for(i=0;i <=n;i++){ ...输出计数[sorted_countindexes[n]]... }

对于常规 awk (或 nawk),我欢迎更好/更简单/更“公理化”的根据排序输出索引

MCVE:这是一个简单的例子:按字母顺序输出索引会非常好:

# create the 2 basic files to be parsed by the awk:
printf 'a b a a a c c d e s s s s e f s a e r r f\ng f r e d e z z c s d r\n' >fileA
printf 's f g r e d f g e z s d v f e z a d d g r f e a\ns d f e r\n'>fileB
# and the awk loop: It outputs in 'whatever order', I want in 'alphabetical order'
for f in file? ; do printf 'for file: %s: ' "$f"
  tr ' ' '\n' < "$f" | awk ' 
       { count[$0]++ } 
   END { for(str in count){ 
           printf("%s:%d ",str,count[str]) 
          }; print "" 
       } '
done
#this outputs:
for file: fileA: d:3 e:5 f:3 g:1 r:4 s:6 z:2 a:5 b:1 c:3
for file: fileB: d:5 e:5 f:5 g:3 r:3 s:3 v:1 z:2 a:2
# I'd like to have the letters outputted in alphabetical order instead!

答案1

使用 GNU ,您可以与其“协处理”功能 ( )awk进行双向交互,您可以发送数据进行排序,并使用 获得结果,但这又是 gawk 特有的。sortinfo gawk coprocprint |& "sort""sort" |& getline

循环遍历数组遇到的顺序,你可以记录下来遭遇序列当你填写数组时:

awk '
  !seen[$1]++ {sequence[n++] = $1}
  END {
    for (i = 0; i < n; i++)
      print sequence[i], seen[sequence[i]]
  }'

您还可以在 中实现排序算法awk。你甚至可以借gawkquicksort.awk你甚至可以在它的手册中找到它(这里演示间接函数调用,这是另一个特定于 GNU 的功能,您可以将其替换为对比较例程的字面调用)。就像是:

awk '
  function less_than(left, right) {
    return "" left <= "" right
  }
  function quicksort(data, left, right,   i, last)
  {
    if (left >= right)
      return

    quicksort_swap(data, left, int((left + right) / 2))
    last = left
    for (i = left + 1; i <= right; i++)
      if (less_than(data[i], data[left]))
        quicksort_swap(data, ++last, i)
    quicksort_swap(data, left, last)
    quicksort(data, left, last - 1)
    quicksort(data, last + 1, right)
  }
  function quicksort_swap(data, i, j,   temp)
  {
    temp = data[i]
    data[i] = data[j]
    data[j] = temp
  }

  {seen[$1]++}
  END {
    for (i in seen) keys[n++]=i
    quicksort(keys, 0, n-1)
    for (i = 0; i < n; i++)
      print keys[i], seen[keys[i]]
  }'

就我个人而言,我只是使用perl而不是awk这里。

答案2

$ cat tst.awk
{ cnt[$0]++ }
END {
    n = sort(cnt,idxs)
    for (i=1; i<=n; i++) {
        idx = idxs[i]
        printf "%s:%d%s", idx, cnt[idx], (i<n ? OFS : ORS)
    }

}

function sort(arr, idxs, args,      i, str, cmd) {
    for (i in arr) {
        gsub(/\047/, "\047\\\047\047", i)
        str = str i ORS
    }

    cmd = "printf \047%s\047 \047" str "\047 |sort " args

    i = 0
    while ( (cmd | getline idx) > 0 ) {
        idxs[++i] = idx
    }

    close(cmd)

    return i
}

# create the 2 basic files to be parsed by the awk:
printf 'a b a a a c c d e s s s s e f s a e r r f\ng f r e d e z z c s d r\n' >fileA
printf 's f g r e d f g e z s d v f e z a d d g r f e a\ns d f e r\n'>fileB

for f in fileA fileB ; do
    printf 'for file: %s: ' "$f"
    tr ' ' '\n' < "$f" |
    awk -f tst.awk
done
for file: fileA: a:5 b:1 c:3 d:3 e:5 f:3 g:1 r:4 s:6 z:2
for file: fileB: a:2 d:5 e:5 f:5 g:3 r:3 s:3 v:1 z:2

上面只是从数组索引构建一个换行符分隔的字符串(为 适当引用它sh),创建一个将该字符串通过管道传输到 的 shell 脚本sort,然后循环输出。如果你想修改sorts 的行为,只需在函数调用中添加一串 Unixsort参数sort,例如sort(seen,"-fu")。显然可以将其修改为在函数内打印或执行您想要的任何其他操作,sort()而不是填充一个索引数组,以便您在返回时循环(如果这是您喜欢的),但该函数具有内聚性。

但请注意,它将限制为系统上的最大命令行长度。

\047代码中的 s 表示shell'不允许包含在'-delimited 字符串或脚本中,因此虽然我们可以'像我上面所做的那样直接在从文件读取的 awk 脚本中使用,但如果您要使用它在命令行上编写脚本,因为awk 'script' file您需要使用某些东西来代替,'并且\047当从命令行和文件解释脚本时都可以工作,因此它是'-replacement 的最便携的选择。

s '( \047s) 的存在是为了str在字符串通过管道进行排序时确保 shell 不会扩展变量、引号不匹配等,即它们这样做:

$ echo 'foo'\''bar $(ls) $HOME' | awk '{
    str=$0; gsub(/\047/, "\047\\\047\047", str); print "str="str
    cmd="printf \047%s\047 \047" str "\047"; print "cmd="cmd
}'
str=foo'\''bar $(ls) $HOME
cmd=printf '%s' 'foo'\''bar $(ls) $HOME'

所以我们不会得到这样的东西,它是脆弱的/有缺陷的,而是:

$ echo 'foo'\''bar $(ls) $HOME' | awk '{
    str=$0; print "str="str
    cmd="printf \"%s\" \"" str "\""; print "cmd="cmd
}'
str=foo'bar $(ls) $HOME
cmd=printf "%s" "foo'bar $(ls) $HOME"

相关内容