虽然此问题的核心是链接问题的副本,但它确实说明了附加要求(无论它们是否完全由 OP 设计):
解决方案应打包为脚本。
解决方案应符合 POSIX(问题通常标记为 shell)
输入应该来自文件(如果指定)或默认来自标准输入。
-
可以在一个单个输入行上有多个数字(例如,echo 1 2)。
解决方案应使用while 或for 循环,即纯shell解决方案。
下面的解决方案解决了这些要求,除了最后一个 - 这很可能会破坏 OP 的交易,但也许其他人会发现它很有用。
使用外部实用程序偏离该要求意味着解决方案将在大量输入数据时表现良好 - shell 代码中的循环很慢。
如果你还想要一个shell while-loop 的解决方案,看这篇文章的底部;它还包括输入验证。
myprogram 的内容(符合 POSIX,但需要将标准输入表示为 /dev/stdin 的文件系统):
注意 no 输入验证被执行 - 输入中的所有标记都假定为十进制数(正数或负数);该脚本将因任何其他输入而中断。请参阅下面的 - 更复杂的 - 过滤掉非十进制数字标记的解决方案。
#!/bin/sh
{ tr -s ' \t\n' '+'; printf '0\n'; } < "${1-/dev/stdin}" | bc
${1-/dev/stdin} 使用第一个参数($1,假定为文件路径)(如果指定)或 /dev/stdin,表示标准输入 stdin。
-
tr -s ' \t\n' '+' 用单个+ 替换输入中的任何空格(空格、制表符、换行符);实际上,这会导致<num1>+<num2>+...+ - 请注意最后悬空的+,稍后会解决。
- 请注意,正是这种空白处理方法允许该解决方案使用每行一个数字和每行多个数字的任意混合输入
-
printf '0\n' 附加一个0,这样上面的表达式就变成了一个有效的加法运算。
- 分组 (
{ ...; ...; }) tr 和 printf 命令使它们充当管道 (|) 的单个输出源。
bc is a POSIX utility 可以执行(任意精度)算术运算。它评估输入表达式并输出其结果。
使用输入验证:简单地忽略不是十进制数字的输入标记。
#!/bin/sh
{ tr -s ' \t\n' '\n' |
grep -x -- '-\{0,1\}[0-9][0-9]*' |
tr '\n' '+'; printf '0\n'; } < "${1-/dev/stdin}" | bc
-
tr -s ' \t\n' '\n' 将输入中的所有单个标记(无论它们是在同一行还是在自己的行上)放到单独的行中。
-
grep -x -- '-\{0,1\}[0-9][0-9]*' 只匹配只包含十进制数字的行。
- 该命令的其余部分与没有验证的解决方案类似。
示例:
注意:如果您使 myprogram 本身可执行 - 例如,使用 cmod +x myprogram,您可以直接调用它 - 例如,.\myprogram 而不是 sh myprogram。
# Single input line with multiple numbers
$ echo '1 2 3' | sh myprogram
6
# Multiple input lines with a single number each
{ echo 1; echo 2; echo 3; } | sh myprogram
6
# A mix of the above
$ sh myprogram <<EOF
1 2
3
EOF
6
一个符合 POSIX 的 while-loop 基于解决方案,它测试并省略总和中的非数字:
注意:这是对 David C. Rankin's answer 的改编,以展示一个强大的替代方案。
但是请注意,除了小的输入文件外,此解决方案将比上述解决方案慢得多。
#!/bin/sh
ifile=${1:-/dev/stdin} ## read from file or stdin
sum=0
while read -r i; do ## read each token
[ $i -eq $i 2>/dev/null ] || continue ## test if decimal integer
sum=$(( sum + i )) ## sum
done <<EOF
$(tr -s ' \t' '\n' < "$ifile")
EOF
printf " sum : %d\n" "$sum"
-
该解决方案避免使用for 循环单个输入行,因为在未引用的字符串变量上使用for 会使生成的标记服从pathname expansion (globbing),这可能会导致使用诸如@ 之类的标记的意外结果987654359@.
- 但是,可以使用
set -f 禁用通配,并使用set +f 重新启用它。
-
要启用单个 while 循环,输入标记首先被拆分,以便每个标记位于其自己的行上,通过涉及此处文档内tr 的命令替换.
- 使用 here-document(而不是管道)向
while 提供输入允许 while 语句在 current shell 中运行,因此循环内的变量可以循环结束后保持在范围内(如果通过管道提供输入,while 将在 subshell 中运行,并且在循环退出时其所有变量都将超出范围)。
sum=$(( sum + i )) 使用arithmetic expansion 计算总和,比调用外部实用程序expr 更有效。
如果你真的,真的想要这样做不调用任何外部实用程序 - 我不明白你为什么会 - 试试这个:
#!/bin/sh
ifile=${1:-/dev/stdin} ## read from file or stdin
sum=0
while read -r line; do ## read each line
# Read the tokens on the line in a loop.
rest=$line
while [ -n "$rest" ]; do
read -r i rest <<EOF
$rest
EOF
[ $i -eq $i 2>/dev/null ] || continue ## test if decimal integer
sum=$(( sum + i )) ## sum
done
done < "$ifile"
printf " sum : %d\n" "$sum"
如果您不介意使用 set -f / set +f 盲目地禁用和重新启用路径名扩展(通配),您可以简化为:
#!/bin/sh
ifile=${1:-/dev/stdin} ## read from file or stdin
sum=0
set -f # temp.disable pathname expansion so that `for` can safely be used
while read -r line; do ## read each line
# Read the tokens on the line in a loop.
# Since set -f is in effect, this is now safe to do.
for i in $line; do
[ $i -eq $i 2>/dev/null ] || continue ## test if decimal integer
sum=$(( sum + i )) ## sum
done
done < "$ifile"
set +f # Re-enable pathname expansion
printf " sum : %d\n" "$sum"