从文件和标准输入中添加数字 [英] Add numbers from file and standard input
问题描述
如何使用while
或for
循环在壳内将数字加在一起?
How do I add numbers together inside the shell using a while
or for
loop?
我只想要一个能够与标准输入和文件一起使用的非常简单的程序.
I just want a really simple program that works with standard input and files.
示例:
$ echo 1 2 | sh myprogram
3
如果文件myfile
包含数字列表,我希望能够做到这一点:
And if a file myfile
contains a list of numbers, I want to be able to do this:
sh myprogram myfile
并获得数字的总和作为输出.
and get the sum of the numbers as output.
推荐答案
此问题在其核心处是链接问题的副本,但它确实声明了其他要求 (是否全部由OP完全使用):
While this question is at its core a duplicate of the linked question, it does state additional requirements (whether they were all fully intended by the OP or not):
-
解决方案应打包为 script .
解决方案应符合POSIX(问题通常被标记为外壳)
The solution should be POSIX-compliant (question is generically tagged shell)
输入应来自文件(如果已指定)或默认情况下来自stdin.
Input should either come from a file, if specified, or from stdin by default.
单个输入行(例如,echo 1 2
)上的可以是多个数字.
There can be multiple numbers on a single input line (e.g., echo 1 2
).
解决方案应使用while
或for
循环,即纯外壳解决方案.
The solution should use a while
or for
loop, i.e. a pure shell solution.
下面的解决方案满足了这些要求,除了最后一个要求 ,这可能是OP的交易突破口,但也许其他人会发现它很有用.
The solution below addresses these requirements, except for the last one - which may well be a deal-breaker for the OP, but perhaps others will find it useful.
通过使用外部实用程序偏离该要求,这意味着解决方案在大量输入数据中表现良好-Shell代码中的循环很慢.
Deviating from that requirement by using external utilities means the solution will perform well with large sets of input data - loops in shell code are slow.
如果仍然需要shell while
循环解决方案,请参见本文的底部;它还包括输入验证.
If you still want a shell while
-loop solution, see the bottom of this post; it also includes input validation.
myprogram
的内容(符合POSIX,但需要一个文件系统,该文件系统将标准输入表示为/dev/stdin
):
请注意,将执行 no 输入验证-输入中的所有令牌均假定为十进制数字(正数或负数);该脚本将与其他任何输入一起中断.请参阅下面的-一种更复杂的解决方案-过滤掉非十进制数字的令牌.
Note that no input validation is performed - all tokens in the input are assumed to be decimal numbers (positive or negative); the script will break with any other input. See below for a - more complex - solution that filters out non-decimal-number tokens.
#!/bin/sh
{ tr -s ' \t\n' '+'; printf '0\n'; } < "${1-/dev/stdin}" | bc
-
${1-/dev/stdin}
使用第一个参数(假定为文件路径的$1
)(如果已指定)或/dev/stdin
表示标准输入stdin.${1-/dev/stdin}
uses either the first argument ($1
, assumed to be a file path), if specified, or/dev/stdin
, which represents stdin, the standard input.tr -s ' \t\n' '+'
用单个+
替换输入中所有空格(空格,制表符,换行符);实际上,这会导致<num1>+<num2>+...+
-请注意最后悬空的+
,稍后再解决.tr -s ' \t\n' '+'
replaces any run of whitespace in the input (spaces, tabs, newlines) with a single+
; in effect, this results in<num1>+<num2>+...+
- note the dangling+
at the end, which is addressed later.- 请注意,正是这种处理空格的方法使解决方案能够与每行一个数字输入和每行多个数字输入的任何组合一起工作
printf '0\n'
附加了0
,以便上述表达式成为有效的加法运算.printf '0\n'
appends a0
so that the above expression becomes a valid addition operation.- 对
tr
和printf
命令进行分组({ ...; ...; }
),使它们充当管道(|
)的单个输出源.
- Grouping (
{ ...; ...; }
) thetr
andprintf
commands makes them act as a single output source for the pipeline (|
).
bc
是POSIX实用程序可以执行(任意精度)算术.它计算输入表达式并输出结果.bc
is a POSIX utility that can perform (arbitrary-precision) arithmetic. It evaluates the input expression and outputs its result.具有输入验证功能:仅忽略非十进制数字的输入令牌.
With input validation: Simply ignores input tokens that aren't decimal numbers.
#!/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]*'
仅匹配仅包含十进制数字的行. - 该命令的其余部分类似于未经验证的解决方案.
tr -s ' \t\n' '\n'
puts all individual tokens in the input - whether they are on the same line or on their own line - onto individual lines.grep -x -- '-\{0,1\}[0-9][0-9]*'
only matches lines containing nothing but a decimal number.- The remainder of the command works analogously to the solution without validation.
示例:
注意:如果使
myprogram
本身可执行-例如,使用cmod +x myprogram
,则可以直接调用它-例如.\myprogram
而不是sh myprogram
.Note: If you make
myprogram
itself executable - e.g., usingcmod +x myprogram
, you can invoke it directly - e.g.,.\myprogram
rather thansh 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解决方案,从总和中测试并忽略非数字:
A POSIX-compliant
while
-loop based solution that tests for and omits non-numbers from the sum:注意:这是 David C. Rankin的答案的改编,以证明健壮另类.
但是请注意,除了较小的输入文件之外,此解决方案将比上述解决方案慢得多.Note: This is an adaptation of David C. Rankin's answer to demonstrate a robust alternative.
Note, however, that this solution will be much slower than the solution above, except for small input files.#!/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
会使生成的令牌受The solution avoids use of
for
to loop over a single input line, as usingfor
on an unquoted string variable makes the resulting tokens subject to pathname expansion (globbing), which can lead to unexpected results with tokens such as*
.- 但是,可以使用
set -f
禁用globbing,并使用set +f
重新启用它.
- It is, however, possible to disable globbing with
set -f
, and to reenable it withset +f
.
要启用单个
while
循环,请首先对输入令牌进行分割,以使每个令牌都可以通过位于内的tr
的命令替换而位于各自的行上.此处文档.To enable use of a single
while
loop, the input tokens are first split so that each token is on its own line, via a command substitution involvingtr
inside a here-document.- 使用here文档(而不是管道)向
while
提供输入,允许while
语句在 current 外壳程序中运行,从而使循环内的变量能够在循环结束后仍保持作用域(如果通过管道提供输入,则while
将在 subshell 中运行,并且当循环退出时,其所有变量都将超出作用域).
- Using a here-document (rather than a pipeline) to provide input to
while
allows the thewhile
statement to run in the current shell and thus for the variables inside the loop to remain in scope after the loop ends (if input were provided via a pipeline,while
would run in a subshell, and all its variables would go out of scope when the loop exits).
sum=$(( sum + i ))
使用算术扩展计算总和,这比调用外部实用程序expr
更为有效.sum=$(( sum + i ))
uses arithmetic expansion to calculate the sum, which is more efficient than calling external utilityexpr
.如果您真的真的想要执行此操作而无需调用任何外部实用程序-我不知道您为什么会这样做-尝试以下操作:
If you really, really want do this without calling any external utilities - I don't see why you would - try this:
#!/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
盲目地禁用和重新启用路径名扩展(globbing),则可以简化为:
If you don't mind blindly disabling and re-enabling pathname expansion (globbing) with
set -f
/set +f
, you can simplify to:#!/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"
这篇关于从文件和标准输入中添加数字的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- 但是,可以使用