不使用awk编写bash脚本吗?

问题描述

要求我们编写一个 shell脚本,该脚本将玩家记录的输入作为标准输入。

示例输入:

id|name|time
23|Jordan|45:17
14|Jason|4:50
12|Bryan|
24|Cody|00:12
33|kobe|41
55|rocky|0

我们需要阅读脚本中的每个记录跳过标头),然后分别输出 记录方法是将时间(秒)转换为定界符,从'|'更改为'' >( space )。

正如我们在上面的示例测试用例中看到的那样,某些记录具有空时间字段第三条记录),而那些记录以秒为单位的时间将被视为 0

示例案例输出

23 Jordan 2717
14 Jason 290
12 Bryan 0
24 Cody 12
33 kobe 2460
55 rocky 0

my_solution_script.sh

#!/bin/bash


read -r header
while IFS="|" read -r pid pname time || [[ -n $pid ]]
do  
    min=$(cut -d ':' -f 1 <<< "$time")
    sec=$(cut -d ':' -f 2 <<< "$time")
    ((min*=60))
    ((min+=sec))
    
    echo "$pid $pname $min"
done

错误输出

23 Jordan 2717
14 Jason 290
12 Bryan 0
24 Cody 12
33 kobe 2501
55 rocky 0

我们可以看到上面的脚本为第5条记录提供了错误输出

如何修复 上面的脚本,以在 每种情况?

我认为使用 awk 可能有一个更简单的解决方案,但是我现在对'awk脚本编制'不太了解, 因此,我正在寻找一种使用基本shell命令解决此问题的方法,但是也欢迎使用 awk命令解决方案。

谢谢。

解决方法

问题是cut -d: -f2 <<< "$time"在不包含$time分隔符的情况下返回所有:。因此,对于神户来说,您正在计算41*60+41而不是41*60

因此,在获取秒数之前,您需要检查$time是否包含:

read -r header
while IFS="|" read -r pid pname time || [[ -n $pid ]]
do  
    min=$(cut -d ':' -f 1 <<< "$time")
    if [[ $time =~ : ]]
        sec=$(cut -d ':' -f 2 <<< "$time")
    else
        sec=0
    fi
    ((min*=60))
    ((min+=sec))
    
    echo "$pid $pname $min"
done
,

使用GNU awk:

awk 'NR>1{$3=$3*60+$4; NF=3; print}' FS='[|:]' file

输出:

23 Jordan 2717
14 Jason 290
12 Bryan 0
24 Cody 12
33 kobe 2460
55 rocky 0

NF=3将GNU awk的print限制为三列。


请参阅:8 Powerful Awk Built-in Variables – FS,OFS,RS,ORS,NR,NF,FILENAME,FNR

,

能否请您尝试以下。在https://ideone.com/9RkGvJ

中进行了编写和测试
awk '
BEGIN{
  FS="|"
}
FNR==1{  next  }
{
  split($3,arr,":")
  $3=(arr[1]*60)+arr[2]
}
1;
' Input_file

说明: 将所有行的字段分隔符设置为|。然后检查FNR==1,在其中放置next将跳过该行。然后,在每行上用:分隔符分割第三列,并重新创建具有数组的第一元素乘以60的第三字段,并将其第二元素相加得到第三列中的秒数。然后提及1将打印行。

,

直接解决方案是,如果值中没有冒号,则将分钟设置为零。您可以完全避免丑陋和中等昂贵的外部流程。

sec=${time#*}
min=${time%:"$sec"}
min=${min:-0}

这使用外壳程序的内置参数扩展工具来拆分值。简而言之,${time#pattern}返回$time的值,而没有匹配pattern的任何前缀; %运算符对后缀的作用相同。

几乎可以肯定,使用Awk脚本是一个更好的主意。您应该可以在不到一个小时的时间内学习基础知识,也许已经足以自己解决这个问题。这是一种快速而肮脏的未经测试的尝试。

awk -F '|' 'NR>1 && ($3 ~ /:/) {m = s = $3;
    sub(/:.*/,"",m); sub(/.*:/,s);
    $3 = m*60+s } 1'
,

重击:

{
  read header
  while IFS='|' read -r id name time; do
    IFS=':' read -r mins secs <<<"$time"
    echo "$id $name $((60 * 10#$mins + 10#$secs))"
  done
} < file

我们在这里两次使用模式IFS=delim read -r field1 field2 ...进行解析。

算术表达式中的10#强制将值解释为以10为底的数字。否则,由于前导零,所以08和09将被解释为无效的八进制数字。

,

假设其他字段没有嵌入的冒号,您可以在读取时解析分钟和秒,然后使用参数解析默认的零来表示分钟或秒。您还可以一次在echo内部进行所有数学运算。

read -r header
while IFS="|:" read -r pid pname min secs || [[ -n $pid ]]
do echo "$pid $pname $(( 10#${secs:-0} + 10#${min:-0}*60 ))"
done

如果名称可以包含冒号,则此操作无效。

如前所述,前导零也会引起问题,因此我添加了一个基数选择指示符(10#)以确保以10为基数的数学运算。 c.f. https://mywiki.wooledge.org/ArithmeticExpression