如何使用while
或for
循环在shell中添加数字?
我只想要一个非常简单的程序,它适用于标准输入和文件。
示例:
$ echo 1 2 | sh myprogram
3
如果文件myfile
包含数字列表,我希望能够这样做:
sh myprogram myfile
并将数字的总和作为输出。
答案 0 :(得分:2)
虽然此问题的核心是链接问题的副本,但它确实声明了其他要求(无论它们是否完全由OP完成):
解决方案应打包为脚本。
解决方案应符合POSIX标准(问题通常标记为shell)
输入应来自文件(如果已指定)或默认情况下来自stdin。
输入行 >
解决方案应使用echo 1 2
或while
循环,即纯shell 解决方案。
下面的解决方案解决了这些要求,除了最后一个 - 这可能是OP的交易破坏者,但也许其他人会觉得它很有用。
使用外部实用程序偏离该要求意味着解决方案将在大量输入数据中运行良好 - shell代码中的循环速度很慢。
如果你还想要一个shell for
- 循环解决方案,请参阅这篇文章的底部;它还包括输入验证。
while
的内容(符合POSIX标准,但需要一个代表标准输入的文件系统为myprogram
):
注意执行 no 输入验证 - 输入中的所有标记都假定为十进制数(正数或负数);该脚本将与任何其他输入打破。请参阅下文,了解更复杂的过滤掉非十进制数标记的解决方案。
/dev/stdin
#!/bin/sh
{ tr -s ' \t\n' '+'; printf '0\n'; } < "${1-/dev/stdin}" | bc
使用第一个参数(${1-/dev/stdin}
,假设是文件路径),如果指定,或$1
,它表示标准输入stdin。 / p>
/dev/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
。< / SUP>
sh myprogram
符合POSIX标准的 # 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
- 基于循环的解决方案 从总和中测试并忽略非数字:
注意:这是David C. Rankin's answer的改编,用于演示强大的替代方案 但请注意,除小输入文件外,此解决方案将比上述解决方案慢得多。
while
该解决方案避免使用#!/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
会使得到的标记受pathname expansion (globbing)的约束,这可以使用for
等标记会导致意外结果。
*
禁用通配,并使用set -f
重新启用它。要启用单个 set +f
循环,首先会对输入标记进行拆分,以便每个标记都在其自己的行上,通过涉及{{1的命令替换在here-document中。
while
提供输入,允许tr
语句在当前 shell中运行,因此对于变量在循环结束后循环内部保持在范围内(如果输入是通过管道提供的,while
将在子shell 中运行,并且当循环时其所有变量都将超出范围退出)。 while
使用arithmetic expansion来计算总和,这比调用外部实用程序while
更有效。
如果你真的,想要这样做而不需要调用任何外部工具 - 我不明白你为什么会这样做 - 试试这个:
sum=$(( sum + i ))
如果您不介意使用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
答案 1 :(得分:1)
此解决方案需要Bash,因为以下功能不兼容POSIX shell:数组,正则表达式,此处字符串,复合[[ ]]
条件运算符。有关POSIX兼容的解决方案,请参阅David's answer。
假设我们有一个空格分隔数字的行,我们想要总结它们。为此,我们将read -a
与nums
一起读入数组sum
,然后我们循环播放read -a nums
for num in "${nums[@]}"; do
(( sum += num ))
done
echo $sum
:
$ echo -e "1 2 3\n4 5 6" | ./sum
6
这适用于从stdin输入或通过管道传输到脚本的单行:
while read -a nums; do
for num in "${nums[@]}"; do
(( sum += num ))
done
done
echo $sum
注意第二行是如何被忽略的。现在,对于多行,我们将其包装在while循环中:
$ echo -e "1 2 3\n4 5 6" | ./sum
21
现在它适用于通过管道连接脚本的多行:
while read -a nums; do
# Loop here
done < "$1"
要从文件中读取,我们可以使用
$ cat infile
1 2 3
4 5 6
$ ./sum infile
21
将作为参数给出的文件重定向到标准输入:
$ ./sum <<< "1 2 3"
./sum: line 7: : No such file or directory
但是现在,管道已停止工作了!
while read -a nums; do
# Loop here
done < "${1:-/dev/stdin}"
要解决此问题,我们使用parameter expansion。我们说“如果参数设置为非空,则从文件重定向,否则从标准输入读取”:
$ ./sum infile
21
$ ./sum < infile
21
现在,标准输入和文件参数都有效:
#!/bin/bash
re='^[0-9]+$' # Regex to describe a number
while read -a line; do
for num in "${line[@]}"; do
# If we encounter a non-number, print to stderr and exit
if [[ ! $num =~ $re ]]; then
echo "Non-number found - exiting" >&2
exit 1
fi
(( sum += num ))
done
done < "${1:-/dev/stdin}"
echo $sum
如果我们遇到的实际上不是一个数字,我们可以添加支票来投诉。所有这些都在一个脚本中完成:
{{1}}
答案 2 :(得分:0)
要在while
循环内求和,您需要一种方法来分隔每一行的值,并在将它们添加到总和之前确认它们是整数值。脚本形式的POSIX shell的一种方法是:
#!/bin/sh
ifile=${1:-/dev/stdin} ## read from file or stdin
sum=0
while read -r a || test -n "$a" ; do ## read each line
for i in $a ; do ## for each value in line
[ $i -eq $i >/dev/null 2>&1 ] || continue ## test if integer
sum=$(expr $sum + $i) ## sum
done
done <"$ifile"
printf " sum : %d\n" "$sum"
exit 0