逐行读取文件并分别对每一行求和
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
分成字符行
(每行一个数字)。
mapfile
将 array
分配给由 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
read
是 while IFS= read -r line
循环中的瓶颈。更多信息见 this answer.
mapfile
(结合 for
循环)可以稍快一些,但仍然很慢(它还会先将所有数据复制到一个数组中)。
- 两种解决方案都在一个新文件(在问题中)中创建一个数字列表,并将 odd/even 结果打印到
stdout
。文件的路径作为单个参数给出。
- 在
awk
中,您可以将字段分隔符设置为空(FS=""
)以处理单个字符。
- 在
bash
中可以通过子字符串扩展来完成(${var:index:length}
)。
- 模 2 (
number % 2
) 得到奇数或偶数。
我正在尝试创建一个脚本来创建一个文件,比如 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
分成字符行 (每行一个数字)。mapfile
将array
分配给由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
read
是while IFS= read -r line
循环中的瓶颈。更多信息见 this answer.mapfile
(结合for
循环)可以稍快一些,但仍然很慢(它还会先将所有数据复制到一个数组中)。- 两种解决方案都在一个新文件(在问题中)中创建一个数字列表,并将 odd/even 结果打印到
stdout
。文件的路径作为单个参数给出。 - 在
awk
中,您可以将字段分隔符设置为空(FS=""
)以处理单个字符。 - 在
bash
中可以通过子字符串扩展来完成(${var:index:length}
)。 - 模 2 (
number % 2
) 得到奇数或偶数。