根据 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