问题描述
要求我们编写一个 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
如何修复 上面的脚本,以在 每种情况?
我认为使用 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