根据 linux 中的文件名更改文件中的某个数字
change a certain number in the file based on file name in linux
我有一个名为 part2.txt
的输入文件,其中输入了数千行,例如
46742 1 48276 48343 48199 48198
46744 1 48343 48344 48200 48199
46746 1 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
我必须将第二列中的所有整数更改为文件名中的数字 (part2.txt
),以便所有整数 1
更改为 2
,而不是 1 也可以是任何其他整数,它不仅仅是 3 行,它可能是数千行,它将变成:
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
请注意,所有列都是 space 分隔的,第一列左侧还有一些 space。我曾尝试将它与 FNR
一起使用,但它不是那么强大,并且要求在 linux.
中使用 sed 或 awk 的某些方法
您可以使用函数来玩 FILENAME
:
awk 'function name(file) {
gsub(/[^0-9]*/, "", file)
return file
}
{digits = name(FILENAME)}
~ /^[0-9]*$/ {=digits}
1' a2
我不明白的是为什么我不能调用 BEGIN{}
中的函数,我猜是因为那时文件名还不可用。问题是这意味着每次都要调用该函数。好吧,我们可以在计算后设置一个标志,但我会把它留作练习:)
更新:我不知道之前我错过了什么导致我编写函数,因为它工作正常:
awk '{digits = FILENAME; gsub(/[^0-9]*/, "", digits) } ~ /^[0-9]*$/ {gsub(/\s\s/,digits)}1' a2.txt
为了防止每次都计算 digits
,您可以使用 NR==1{}
技巧(感谢 Wintermute 的回答,+1)。
测试
$ awk '{digits = FILENAME; gsub(/[^0-9]*/, "", digits) } ~ /^[0-9]*$/ {gsub(/\s\s/,digits)}1' a2.txt
46742 1 48276 48343 48199 48198
46744 1 48343 48344 48200 48199
46746 1 48344 48332 48201 48200
465645 1 48566 48234 45201 48435
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
这可以通过组合 sed
和 shell 变量来完成。以下是三个场景,每个场景都应该按照您的预期进行。此外,如果您想就地更改文件,则可以使用 sed -i
而不是 sed
。
如果您知道文件的编号,那么这将是可行的,假设 $n 具有文件编号(例如,n=2 表示 part2.txt):
n=2; sed 's:^\(\s*[0-9]\+\s\+\)\([0-9]\+\)\(\s\):'"$n"':' part$n.txt
否则,如果您将扩展名为 .txt 的文件名存储在 $f 中(例如 f=part2.txt),那么这应该有效:
f=part2.txt; n=$(sed 's:^\(.*[^0-9]\|\)\([0-9]\+\)\.txt::' <<<"$f"); sed 's:^\(\s*[0-9]\+\s\+\)\([0-9]\+\)\(\s\):'"$n"':' "$f"
如果您使用的是 sh 或 bash 的旧版本,上述代码片段可能会失败。在这种情况下,您可以尝试以下操作。它稍长一些,因为它不使用 $(...) 和 <<<.
f=part2.txt; n=`echo "$f" | sed 's:^\(.*[^0-9]\|\)\([0-9]\+\)\.txt::'`; sed 's:^\(\s*[0-9]\+\s\+\)\([0-9]\+\)\(\s\):'"$n"':' "$f"
使用 gawk(对于 RT
),尽可能保持格式完整:
$ gawk -v RS='\s+' 'NR == 1 { n = FILENAME; gsub(/[^0-9]/, "", n) } NR % 6 == 3 && int([=10=]) == [=10=] { [=10=] = n } { printf [=10=] RT }' part2.txt
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
用RS
作为\s+
,每个字段就是一条记录,记录后面的空格记为RT
,后面打印时用到。密码是
NR == 1 { # First record of the file:
n = FILENAME # isolate the number from the file name
gsub(/[^0-9]/, "", n)
}
NR % 6 == 3 && int([=11=]) == [=11=] { # after that: For every sixth record, if it
# is an integer,
[=11=] = n # replace it with the isolated number.
# it is NR % 6 == 3 instead of == 2 because
# the file begins with whitespaces that our
# RS matches, so the first record is an empty
# one and the first row in the first column
# is the second record.
}
{ printf [=11=] RT } # after that: print everything separated by the
# remembered record terminators.
对 gensub() 使用 GNU awk:
$ cat tst.awk
{
fmt = gensub(/(\s*\S+\s+)\S+/,"\1%s","",[=10=])"\n"
printf fmt, (~/^[0-9]+$/ ? gensub(/[^0-9]/,"","g",FILENAME) : )
}
$
$ awk -f tst.awk part2.txt
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
您可以在任何 awk 中使用 match() 和 substr() 执行相同的操作。
以上通过将每个输入行转换为格式字符串来保留输入间距,只需将您想要更改的特定字段替换为 %s
。如果输入已经包含像 %s
这样的 printf 格式字符串,它将失败,但您没有这种情况,如果您有,您可能可以通过简单的 gsub(/%/,"%%")
作为第一行来解决所有问题将每个输入行中的所有 %
符号转换为文字。
这是一个适用于任何 POSIX awk 的版本:
$ cat tst.awk
{
match([=11=],/[[:space:]]*[^[:space:]]+[[:space:]]+/)
fmt = substr([=11=],1,RLENGTH) "%s"
match([=11=],/[[:space:]]*[^[:space:]]+[[:space:]]+[^[:space:]]+/)
fmt = fmt substr([=11=],RLENGTH+1) "\n"
num = FILENAME
gsub(/[^0-9]/,"",num)
printf fmt, (~/^[0-9]+$/ ? num : )
}
$
$ awk -f tst.awk part2.txt
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
我有一个名为 part2.txt
的输入文件,其中输入了数千行,例如
46742 1 48276 48343 48199 48198
46744 1 48343 48344 48200 48199
46746 1 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
我必须将第二列中的所有整数更改为文件名中的数字 (part2.txt
),以便所有整数 1
更改为 2
,而不是 1 也可以是任何其他整数,它不仅仅是 3 行,它可能是数千行,它将变成:
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
请注意,所有列都是 space 分隔的,第一列左侧还有一些 space。我曾尝试将它与 FNR
一起使用,但它不是那么强大,并且要求在 linux.
您可以使用函数来玩 FILENAME
:
awk 'function name(file) {
gsub(/[^0-9]*/, "", file)
return file
}
{digits = name(FILENAME)}
~ /^[0-9]*$/ {=digits}
1' a2
我不明白的是为什么我不能调用 BEGIN{}
中的函数,我猜是因为那时文件名还不可用。问题是这意味着每次都要调用该函数。好吧,我们可以在计算后设置一个标志,但我会把它留作练习:)
更新:我不知道之前我错过了什么导致我编写函数,因为它工作正常:
awk '{digits = FILENAME; gsub(/[^0-9]*/, "", digits) } ~ /^[0-9]*$/ {gsub(/\s\s/,digits)}1' a2.txt
为了防止每次都计算 digits
,您可以使用 NR==1{}
技巧(感谢 Wintermute 的回答,+1)。
测试
$ awk '{digits = FILENAME; gsub(/[^0-9]*/, "", digits) } ~ /^[0-9]*$/ {gsub(/\s\s/,digits)}1' a2.txt
46742 1 48276 48343 48199 48198
46744 1 48343 48344 48200 48199
46746 1 48344 48332 48201 48200
465645 1 48566 48234 45201 48435
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
这可以通过组合 sed
和 shell 变量来完成。以下是三个场景,每个场景都应该按照您的预期进行。此外,如果您想就地更改文件,则可以使用 sed -i
而不是 sed
。
如果您知道文件的编号,那么这将是可行的,假设 $n 具有文件编号(例如,n=2 表示 part2.txt):
n=2; sed 's:^\(\s*[0-9]\+\s\+\)\([0-9]\+\)\(\s\):'"$n"':' part$n.txt
否则,如果您将扩展名为 .txt 的文件名存储在 $f 中(例如 f=part2.txt),那么这应该有效:
f=part2.txt; n=$(sed 's:^\(.*[^0-9]\|\)\([0-9]\+\)\.txt::' <<<"$f"); sed 's:^\(\s*[0-9]\+\s\+\)\([0-9]\+\)\(\s\):'"$n"':' "$f"
如果您使用的是 sh 或 bash 的旧版本,上述代码片段可能会失败。在这种情况下,您可以尝试以下操作。它稍长一些,因为它不使用 $(...) 和 <<<.
f=part2.txt; n=`echo "$f" | sed 's:^\(.*[^0-9]\|\)\([0-9]\+\)\.txt::'`; sed 's:^\(\s*[0-9]\+\s\+\)\([0-9]\+\)\(\s\):'"$n"':' "$f"
使用 gawk(对于 RT
),尽可能保持格式完整:
$ gawk -v RS='\s+' 'NR == 1 { n = FILENAME; gsub(/[^0-9]/, "", n) } NR % 6 == 3 && int([=10=]) == [=10=] { [=10=] = n } { printf [=10=] RT }' part2.txt
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
用RS
作为\s+
,每个字段就是一条记录,记录后面的空格记为RT
,后面打印时用到。密码是
NR == 1 { # First record of the file:
n = FILENAME # isolate the number from the file name
gsub(/[^0-9]/, "", n)
}
NR % 6 == 3 && int([=11=]) == [=11=] { # after that: For every sixth record, if it
# is an integer,
[=11=] = n # replace it with the isolated number.
# it is NR % 6 == 3 instead of == 2 because
# the file begins with whitespaces that our
# RS matches, so the first record is an empty
# one and the first row in the first column
# is the second record.
}
{ printf [=11=] RT } # after that: print everything separated by the
# remembered record terminators.
对 gensub() 使用 GNU awk:
$ cat tst.awk
{
fmt = gensub(/(\s*\S+\s+)\S+/,"\1%s","",[=10=])"\n"
printf fmt, (~/^[0-9]+$/ ? gensub(/[^0-9]/,"","g",FILENAME) : )
}
$
$ awk -f tst.awk part2.txt
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02
您可以在任何 awk 中使用 match() 和 substr() 执行相同的操作。
以上通过将每个输入行转换为格式字符串来保留输入间距,只需将您想要更改的特定字段替换为 %s
。如果输入已经包含像 %s
这样的 printf 格式字符串,它将失败,但您没有这种情况,如果您有,您可能可以通过简单的 gsub(/%/,"%%")
作为第一行来解决所有问题将每个输入行中的所有 %
符号转换为文字。
这是一个适用于任何 POSIX awk 的版本:
$ cat tst.awk
{
match([=11=],/[[:space:]]*[^[:space:]]+[[:space:]]+/)
fmt = substr([=11=],1,RLENGTH) "%s"
match([=11=],/[[:space:]]*[^[:space:]]+[[:space:]]+[^[:space:]]+/)
fmt = fmt substr([=11=],RLENGTH+1) "\n"
num = FILENAME
gsub(/[^0-9]/,"",num)
printf fmt, (~/^[0-9]+$/ ? num : )
}
$
$ awk -f tst.awk part2.txt
46742 2 48276 48343 48199 48198
46744 2 48343 48344 48200 48199
46746 2 48344 48332 48201 48200
48283 3.58077402e+01 -2.97697746e+00 1.50878647e+02
48282 3.67231688e+01 -2.97771595e+00 1.50419488e+02
48285 3.58558188e+01 -1.98122787e+00 1.50894850e+02
48287 3.67678239e+01 -1.98150619e+00 1.50432492e+02