【问题标题】:Displaying formatted records using AWK使用 AWK 显示格式化记录
【发布时间】:2019-07-26 12:14:39
【问题描述】:

我一直在提供一个输入文件,它看起来像这样:

ID          PID   PPID   C STIME  TTY            TIME  CMD
adz110     5344   5334   0 08:47  pts/2     00:00:00  bash
dmq292     6908   6854   0 08:53  pts/1     00:00:00  bash
adz110     7227   7150   0 08:54  pts/9     00:00:00  who
erg474     7466   7461   0 08:54  pts/10    00:00:00  ls
dmq292     7966   7960   0 08:55  pts/13    00:00:00  assign1.sh if of
xle135     8636   8628   0 08:58  pts/15    00:00:00  bash
xle135     8983   8636   0 08:59  pts/15    00:00:00  ssh ctf.cs.utsarr.net
zeh458     9057   1980   0 08:59  pts/7     00:00:00  vim prog.c
HanSolo    9150   9139   0 08:59  pts/16    00:00:00  ps -af

它需要像这样结束:

User: adz110
    bash
    who
User: dmq292
    bash
    assign1.sh if of
User: erg474
    ls
User: xle135
    bash
    ssh ctf.cs.utsarr.net
User: zeh458
    vim prog.c
Earliest  Start  Time:
adz110    5344   5334 0 08:47  pts/2     00:00:00  bash

Latest  Start  Time
xle135    8983   8636   0 08:59  pts /15    00:00:00  ssh ctf.cs.utsarr.net

到目前为止,我在一个我写下的 file.awk 中找到了:

/[a-z]{3}[0-9]{3}/    
{   
    print $1
    if( match($1 , arg1) )
    {
    for(i=8; i <= NF ; i ++) 

       printf("%s", $i); 

    printf("\n"); 

    }

}
END {
        print " Earliest Start Time:" 

        print "Latest Start Time: "
 }

因此,它不是为每个用户名与 [a-z][0-9]{3} 匹配的用户打印,而是打印 cmd 以及不带任何空格的用户名。我在理解关联数组和使用特殊变量(如 NR、RS 等)时遇到了很多麻烦。

【问题讨论】:

  • pts后面真的是空格吗?
  • 这里没有关联数组。它正在精确地打印您要求它打印的内容。
  • 所以我可以通过执行“if (a[$1]++)”来取消用户是否重复的逻辑部分,那么这算作关联数组吗?因为我还需要考虑最早开始时间和最晚开始时间的线路对吗? @Cyrus 我修复了那些错误添加的多余空格
  • 为什么vim prog.c中有两个空格?
  • 如果用户 zbc123 正在运行 3 个 bash 实例,bash 应该在该用户的输出中出现 3 次还是只出现一次?

标签: sorting awk associative-array program-files


【解决方案1】:

试试这个,file.awk:

$1 !~ /[a-z]{3}[0-9]{3}/ {next;}
!fstTime {fstTime=$5; lstTime=$5; first=$0; last = $0;}
!($1 in a) {a[$1];users[++ind]=$1;}
{   cmd=$8; for(i=9;i<=NF;i++) cmd=cmd OFS $i;
    cmds[$1] = cmds[$1] ? cmds[$1] "\n    " cmd : "    " cmd;
    if ($5 < fstTime) { fstTime=$5; first=$0; }
    if ($5 > lstTime) { lstTime=$5; last = $0; }
}
END { 
    for(i=1;i<=length(a);i++) {
        print "User: " users[i];
        print cmds[users[i]];
    }
    print "Earliest  Start  Time:\n" first "\n\nLatest  Start  Time:\n" last; 
}

awk -f file.awk fileToTest.txt 产生:

User: adz110
    bash
    who
User: dmq292
    bash
    assign1.sh if of
User: erg474
    ls
User: xle135
    bash
    ssh ctf.cs.utsarr.net
User: zeh458
    vim prog.c
Earliest  Start  Time:
adz110     5344   5334   0 08:47  pts/2     00:00:00  bash

Latest  Start  Time:
xle135     8983   8636   0 08:59  pts/15    00:00:00  ssh ctf.cs.utsarr.net

如果您希望zeh458 9057 1980 0 08:59 pts/7 00:00:00 vim prog.c 成为Latest Start Time: 行,只需将($5 &gt; lstTime) 更改为($5 &gt;= lstTime) 即可。

【讨论】:

  • 输出的排序外观在这里只是巧合吧?
  • @tripleee 不,我维护了两个数组,一个用于索引。和两个时间变量。我使用索引来保持稳定。
  • 啊,但是您依赖输入进行排序。
  • @tripleee 请定义sorted。据我所知,OP没有要求排序,imso根据输入保持输出相对稳定是这里需要的。
  • 哦,我是从 cmets 上捡来的,但它实际上不在要求中... NVM。
【解决方案2】:

您只想对第一个字段应用正则表达式,并以适合打印的格式收集内存中每个用户的值。

观察列表是固定宽度的;因此程序名称和参数位于从第 55 列开始的字段中。同样,时间戳位于第 28-32 列中。

awk 'NR > 1 && $1 ~ /^[a-z]{3}[0-9]{3}$/ {
  when = substr($0, 28, 5)
  command = substr($0, 55)  
  if ($1 in user) {
      # Append this command to previous value
      user[$1] = user[$1] ORS "    " command
  } else {
      # Create a new item in this associative array
      user[$1] = "    " command
  }
  if (NR==2 || when > max) { max=when; maxcmd = $0 }
  if (NR==2 || when < min) { min=when; mincmd = $0 }
}
END {
    # XXX TODO: sort array?
    for (u in user) printf "User %s:\n%s\n", u, user[u]
    print "Earliest start time"; print mincmd
    print "Lastest start time"; print maxcmd
}' filename

所以当我们第一次看到特定用户时,他们不会在users,所以我们只需将他们的第一个命令缩进四个空格作为关联数组中该键的值。如果我们再次看到它们,我们会在值后面附加另一行,在前面添加一个换行符 (ORS) 和四个空格。

NR 是当前行号 -- 我们跳过NR==1 以避免捕获标题行,而NR==2 我们知道这是我们正在处理的第一行,所以我们设置max 和@ 987654328@ 到他们的基线值。

理想情况下,您还应该将时间戳标准化为规范形式,以便您可以在“21:23”之前排序“昨天 21:24”,但我不会用这个简单的答案去那里。您可能还想在其他各个地方添加装饰。 (或者也许您可以依赖输入似乎按进程开始时间排序的事实?)

如果您希望对用户名进行排序,GNU Awk 内置了数组排序;对于其他 awks,您将需要编写自己的简单排序函数,或使用外部管道。

【讨论】:

    【解决方案3】:

    您的输入文件中有一处混淆(您的命令输出同时显示了几个进程,例如xle135zeh458)。因此,我提出的解决方案将对您的输入文件进行排序(如果您也需要,您可以使用命令代替它),然后它会从所有条目中计算出最大和最小时间段。如果某些事物具有相同的时间戳,它不会连接它们的值(尽管我们也可以这样做);相反,它将打印最新排序的一个(仅带有最新的时间戳)。

    awk '
    /^ID/{
      next
    }
    {
      split($5,array,":")
      seconds=array[1]*60+array[2]
    }
    FNR==NR{
      a[$1]++
      tim[seconds]=$0
      next
    }
    a[$1]==2{
      print "User: " $1  ORS "    " $NF
      getline
      sub(/.*:00/,"")
      print"  " $0
    }
    a[$1]==1{
      val=$0
      sub(/.*:00/,"",val)
      print "User: " $1 ORS "    " val
    }
    {
      min=min<seconds?(min?min:seconds):seconds
      max=max>seconds?max:seconds
    }
    END{
      print "Earliest  Start  Time:" ORS tim[min] ORS "Latest  Start  Time" ORS tim[max]
    }
    ' <(sort -k1,5 Input_file)  <(sort -k1,5 Input_file)
    

    输出如下。

    User: adz110
        bash
        who
    User: dmq292
        bash
        assign1.sh if of
    User: erg474
          ls
    User: HanSolo
          ps -af
    User: xle135
        bash
        ssh ctf.cs.utsarr.net
    User: zeh458
          vim  prog.c
    Earliest  Start  Time:
    adz110     5344   5334   0 08:47  pts/2     00:00:00  bash
    Latest  Start  Time
    zeh458     9057   1980   0 08:59  pts/7     00:00:00  vim  prog.c
    

    【讨论】:

    • 为什么需要将排序后的输入读取两次?对第五个字段进行排序然后一次性提取其他内容不是更容易吗?
    • @tripleee,这是因为要处理第一个字段只有 1 行的情况(如果你看到给定的示例总是多于 1 行),所以在这种情况下我们不需要跳转在下一行等上处理我已经完成的事情。如果 OP 更加明确,也可以尝试单读。
    【解决方案4】:

    假设如果给定用户多次运行同一命令,您希望该命令多次出现在该用户的输出中:

    $ cat tst.awk
    NR == 1 { next }
    /^[a-z]{3}[0-9]{3}/ {
        user = $1
        users[user]
    
        cmd = $0
        sub(/([^[:space:]]+[[:space:]]+){7}/,"",cmd)
        cmds[user,++numCmds[user]] = cmd
    
        stime = $5
        if ( (earliestStime == "") || (stime < earliestStime) ) {
            earliestStime = stime
            earliestData  = $0
        }
        if ( (latestStime == "") || (stime > latestStime) ) {
            latestStime = stime
            latestData  = $0
        }
    }
    END {
        for (user in users) {
            printf "User: %s\n", user
            for (cmdNr=1; cmdNr<=numCmds[user]; cmdNr++) {
                printf "   %s\n", cmds[user,cmdNr]
            }
        }
    
        print "\nEarliest  Start  Time:"
        print earliestData
    
        print "\nLatest  Start  Time:"
        print latestData
    }
    

    .

    $ awk -f tst.awk file
    User: xle135
       bash
       ssh ctf.cs.utsarr.net
    User: zeh458
       vim prog.c
    User: dmq292
       bash
       assign1.sh if of
    User: erg474
       ls
    User: adz110
       bash
       who
    
    Earliest  Start  Time:
    adz110     5344   5334   0 08:47  pts/2     00:00:00  bash
    
    Latest  Start  Time:
    xle135     8983   8636   0 08:59  pts/15    00:00:00  ssh ctf.cs.utsarr.net
    

    如果输出中用户或命令的顺序很重要,那么请告诉我们应该是什么顺序,这样就很容易适应,例如通过保留输入顺序或在 END 部分的每个循环之前使用 GNU awk 设置 PROCINFO["sorted_in"]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-29
      • 1970-01-01
      • 2019-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多