逐行读取文件并分别对每一行求和

read file line by line and sum each line individually

我正在尝试创建一个脚本来创建一个文件,比如 file01.txt 在每一行写一个数字。

001
002
...
998
999

然后我想逐行读取文件并对每一行求和并说出数字是偶数还是奇数。
对每一行求和,如 0+0+1 = 1 是奇数
9+9+8 = 26 所以偶数

001 odd
002 even
..
998 even
999 odd

我试过了

while IFS=read -r line; do sum+=line >> file02.txt; done <file01.txt

但是这是对整个文件而不是每一行的总和。

使用 GNU awk:

awk -vFS='' '{sum=0; for(i=1;i<=NF;i++) sum+=$i;
              print [=10=], sum%2 ? "odd" : "even"}' file01.txt

FS awk 变量定义字段分隔符。如果它被设置为空字符串(这就是 -vFS='' 选项的作用)那么每个字符都是一个单独的字段。

剩下的很简单:对输入的每一行执行大括号之间的块。它用 for loop 计算字段的总和(NF 是另一个 awk 变量,它的值是当前记录的字段数)。然后打印原始行 ([=15=]),如果总和是偶数,则后跟字符串 even,否则 odd.

您可以在 bash 本身中很容易地做到这一点,利用内置参数扩展到 trim 从每行的开头开始前导零,以便对奇数/偶数的数字求和.

从文件(默认情况下是命名文件或 stdin)读取时,您可以使用默认初始化来使用第一个参数(位置参数)作为文件名(如果给定),如果不,只读 stdin,例如

#!/bin/bash

infile="${1:-/dev/stdin}"     ## read from file provide as  or stdin

您将在 while 循环中使用 infile,例如

while read -r line; do        ## loop reading each line
  ...
done < "$infile"

对于trim前导零,首先获取前导零的子串trim从右边开始计算所有数字,直到只剩下零,例如

  leading="${line%%[1-9]*}"                         ## get leading 0's

现在使用相同类型的参数扩展 # 而不是 %% trim line 前面的前导零子字符串将结果数字保存在 value,例如

  value="${line#$leading}"                          ## trim from front

现在将 sum 归零并遍历 value 中的数字以获得数字总和:

  for ((i=0;i<${#value};i++)); do                   ## loop summing digits
    sum=$((sum + ${value:$i:1}))
  done

剩下的就是您的偶数/奇数测试。将它完全放在一个简短的示例脚本中,除了你想要的 "odd" / "even" 输出之外,还有意输出数字总和,你可以这样做:

#!/bin/bash

infile="${1:-/dev/stdin}"     ## read from file provide as  or stdin

while read -r line; do                              ## read each line
  [ "$line" -eq "$line" 2>/dev/null ] || continue   ## validate integer
  
  leading="${line%%[1-9]*}"                         ## get leading 0's
  value="${line#$leading}"                          ## trim from front
  sum=0                                             ## zero sum
  
  for ((i=0;i<${#value};i++)); do                   ## loop summing digits
    sum=$((sum + ${value:$i:1}))
  done
  
  printf "%s (sum=%d) - " "$line" "$sum"            ## output line w/sum
                                                    ## (temporary output)
  if ((sum % 2 == 0)); then                         ## check odd / even
    echo "even"
  else
    echo "odd"
  fi
done < "$infile"

(注意: 你实际上可以遍历 line 中的数字并跳过删除前导零子字符串。删除确保如果使用整个值不被解释为八进制值 -- 由您决定)

示例Use/Output

使用快速进程替换在 stdin 上提供 001 - 020 的输入,您可以这样做:

$ ./sumdigitsoddeven.sh < <(printf "%03d\n" {1..20})
001 (sum=1) - odd
002 (sum=2) - even
003 (sum=3) - odd
004 (sum=4) - even
005 (sum=5) - odd
006 (sum=6) - even
007 (sum=7) - odd
008 (sum=8) - even
009 (sum=9) - odd
010 (sum=1) - odd
011 (sum=2) - even
012 (sum=3) - odd
013 (sum=4) - even
014 (sum=5) - odd
015 (sum=6) - even
016 (sum=7) - odd
017 (sum=8) - even
018 (sum=9) - odd
019 (sum=10) - even
020 (sum=2) - even

您可以在确认 "(sum=X)" 的输出按预期运行后简单地删除它,并将输出重定向到您的新文件。让我知道我是否正确理解了你的问题,如果你还有其他问题。

你能试试bash版本吗:

parity=("even" "odd")
while IFS= read -r line; do
    mapfile -t ary < <(fold -w1 <<< "$line")
    sum=0
    for i in "${ary[@]}"; do
        (( sum += i ))
    done
    echo "$line" "${parity[sum % 2]}"
done < file01.txt > file92.txt
  • fold -w1 <<< "$line" 将字符串 $line 分成字符行 (每行一个数字)。
  • mapfilearray 分配给由 fold 命令提供的元素。

请注意bash脚本在时间上效率不高,不适合 对于大输入。

纯 awk:

BEGIN {
    for (i=1; i<=999; i++) {
        printf ("%03d\n", i) > ARGV[1]
    }
    close(ARGV[1])

    ARGC = 2
    FS = ""

    result[0] = "even"
    result[1] = "odd"
}

{
    printf("%s: %s\n", [=10=], result[(++) % 2])
}

逐行处理文件并进行数学运算,对于 awk 来说是一项完美的任务。

纯bash:

set -e

printf '%03d\n' {1..999} > "${1:?no path provided}"

result=(even odd)

mapfile -t num_list < ""

for i in "${num_list[@]}"; do
    echo $i: ${result[(${i:0:1} + ${i:1:1} + ${i:2:1}) % 2]}
done

bash中也可以应用类似的方法,但速度较慢。

比较:

bash 大约慢 10 倍。

$ cd ./tmp.Kb5ug7tQTi

$ bash -c 'time awk -f ../solution.awk numlist-awk > result-awk'

real    0m0.108s
user    0m0.102s
sys 0m0.000s

$ bash -c 'time bash ../solution.bash numlist-bash > result-bash'

real    0m0.931s
user    0m0.929s
sys 0m0.000s

$ diff --report-identical result*
Files result-awk and result-bash are identical

$ diff --report-identical numlist*
Files numlist-awk and numlist-bash are identical

$ head -n 5 *
==> numlist-awk <==
001
002
003
004
005

==> numlist-bash <==
001
002
003
004
005

==> result-awk <==
001: odd
002: even
003: odd
004: even
005: odd

==> result-bash <==
001: odd
002: even
003: odd
004: even
005: odd
  • readwhile IFS= read -r line 循环中的瓶颈。更多信息见 this answer.
  • mapfile(结合 for 循环)可以稍快一些,但仍然很慢(它还会先将所有数据复制到一个数组中)。
  • 两种解决方案都在一个新文件(在问题中)中创建一个数字列表,并将 odd/even 结果打印到 stdout。文件的路径作为单个参数给出。
  • awk中,您可以将字段分隔符设置为空(FS="")以处理单个字符。
  • bash中可以通过子字符串扩展来完成(${var:index:length})。
  • 模 2 (number % 2) 得到奇数或偶数。