【问题标题】:Getting "numeric argument required" when running a script with arithmetic operations运行具有算术运算的脚本时获取“需要数字参数”
【发布时间】:2018-01-27 15:35:15
【问题描述】:

我正在尝试编写一个函数,该函数应该将 hr:min:sec,ms(即 15:41:47,757)形式的时间戳转换为毫秒。函数如下:

#!/bin/sh
mili () {

    hr=$(echo "$1" | cut -c1-2)
    echo "hr is: " $hr
    min=$(echo "$1" | cut -c4-5)
    echo "min is: " $min
    sec=$(echo "$1" | cut -c7-8)
    echo "sec is: " $sec
    ms=$(echo "$1" | cut -c10-12)
    echo "ms is: " $ms
    total=$(($hr \* 3600 + $min \* 60 + $sec) \* 1000 + $ms)

    return "$total"
    #echo "Result is: "$total" "
}

mili $1

但是,当我运行它时:

./mili.sh "15:41:47,757"

我收到以下输出消息:

./mili.sh: command substitution: line 15: syntax error near unexpected token 
`\*'
./mili.sh: command substitution: line 15: `($hr \* 3600 + $min \* 60 + $sec) 
\* 1000 + $ms'
./mili.sh: line 17: return: : numeric argument required

我尝试过使用和不使用单引号、双引号和反引号的 expr 变体,但似乎永远无法让它计算算术。我可以确认一个像这样的简单命令有效:expr 2 * 3 但是当我尝试在我的脚本中使用类似的东西时它失败了。

我怎样才能让它简单地计算我的表达式?

【问题讨论】:

  • 有什么特别的理由使用 mili 而不是 milli (如millisecond)?

标签: shell sh


【解决方案1】:

在算术内部,* 不需要转义。此外,缺少一些括号。因此,替换:

total=$(($hr \* 3600 + $min \* 60 + $sec) \* 1000 + $ms)

与:

total=$((($hr * 3600 + $min * 60 + $sec) * 1000 + $ms))

另类

可以简化代码,避免多次调用cut:

mili() {
    IFS=':,' read hr min sec ms <<<"$1"
    echo "hr is: " $hr
    echo "min is: " $min
    echo "sec is: " $sec
    echo "ms is: " $ms
    total=$((($hr * 3600 + $min * 60 + $sec) * 1000 + $ms))
    echo "Total=$total"
    return "$total"
}

旁白:Bash 算术和美元符号

在 Bash 算术上下文中,变量前的美元符号是可选的。例如:

$ a=1; echo "$((1 + a)) and $((1+ $a))"
2 and 2

虽然一些风格指南建议在算术上下文中省略 $,但有一个关键区别。正如cmets中的Chepner points out,对未定义变量的处理方式大不相同:

$ unset a
$ echo $((1 + $a))
bash: 1 + : syntax error: operand expected (error token is "+ ")
$ echo $((1 + a))
1

总结:

  1. 如果您希望未定义的变量默认为零,则省略$。

  2. 如果您希望将未定义的变量替换为空,可能会导致表达式无效,请包含$。

在 shell 函数 mili 中,未定义的变量 hr、min 等表示代码错误,我们可能需要一条错误消息来警告我们,我们希望包含 @ 987654335@。在其他情况下,默认值为零是合理的,我们不会这样做,并且省略 $ 是正确的。

【讨论】:

  • 另外,在$((...)) 中,您可以使用不带$ 的变量名——total=$(( ((hr * 60 + min) * 60 + sec) * 1000 + ms ))——我发现在双括号内添加空格有助于提高可读性。
  • 哇...像个魅力一样工作。谢谢大佬,真的很感谢。 掌心
  • @glennjackman 是的,你是对的,许多风格指南更喜欢删除 $。 (如果 bash 是为符号一致性而设计的,我会更喜欢它。)
  • 使用$ 和不使用$ 之间有一个非常微妙的区别:使用$,在计算表达式之前扩展变量。未定义变量产生的空字符串将产生一个无效的表达式,或者至少会评估一个与您预期不同的表达式。如果没有$,则字符串被假定为一个变量during 评估,未定义的变量默认为整数值0。
  • @chepner 这是一个很好的观点!我只是在答案中添加了对此的讨论。
【解决方案2】:

另外几点:

  1. 不要return "$total":返回值是0到255之间的int。你需要echo "$total"

  2. 当小时/分钟/秒为 08 或 09 时,您将遇到错误 -- bash 将前导零的数字视为八进制,而 8 和 9 是无效的八进制数字。

    $ mili 11:22:09,456
    hr is:  11
    min is:  22
    sec is:  09
    ms is:  456
    bash: (11 * 3600 + 22 * 60 + 09: value too great for base (error token is "09")
    

我会写:

mili () {     
    IFS=":,." read -r hr min sec ms <<<"$1"
    echo "hr is:   $hr" >&2
    echo "min is:  $min" >&2
    echo "sec is:  $sec" >&2
    echo "ms is:  $ms" >&2
    echo "$(( ((10#$hr * 60 + 10#$min) * 60 + 10#$sec) * 1000 + 10#$ms ))"
}

10# 强制使用 base-10 数字

然后

$ ms=$(mili 11:22:09.456)
hr is:   11
min is:  22
sec is:  09
ms is:  456

$ echo $ms
40929456

【讨论】:

  • 感谢您的宝贵意见!我不知道这一点。
  • 10#$hr 很笨拙。替代${hr#0} 也好不到哪里去。 bash 不漂亮。
【解决方案3】:

这是一个疯狂的选择:

$ mili () {
    IFS=., read -r time ms <<<"$1"
    ms3=$(cut -c 1-3 <<<"${ms}000")
    echo "$(date -u -d "1970-01-01 $time" +%s)$ms3"
}

$ mili 15:41:47,757
56507757

$ mili 15:41:47,75
56507750

$ mili 15:41:47
56507000

【讨论】:

  • 我会将$ms3 合并到格式字符串中以避免echo 和命令替换:date -u -d "1970-01-01 $time" +"%s$ms3"。
猜你喜欢
  • 2011-03-02
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多