添加列,用文件名填充整个列(不带“.csv”) - 对于 BaSH 中的多个文件

添加列,用文件名填充整个列(不带“.csv”) - 对于 BaSH 中的多个文件

警告:绝对的初学者。我需要向 .csv 文件添加一列,其中列标题可以是“名称”,但整个列应该完全相同 - 文件本身的名称,.csv 文件的名称filename。现在每个文件只有 3 个变量,但有 2100 行。

示例:对于文件“bcc1_45Fall_10010002.csv”这就是我所拥有的 -

   HUC8       YEAR    RO_MM
   10010002   1961    74.7
   10010002   1962    69.1
   10010002   1963    52.0
   10010002   1964   130.7
   10010002   1965    32.2
   10010002   1966    85.4

这就是我要的 -

  NAME                   HUC8       YEAR    RO_MM
  bcc1_45Fall_10010002   10010002   1961    74.7
  bcc1_45Fall_10010002   10010002   1962    69.1
  bcc1_45Fall_10010002   10010002   1963    52.0
  bcc1_45Fall_10010002   10010002   1964   130.7
  bcc1_45Fall_10010002   10010002   1965    32.2
  bcc1_45Fall_10010002   10010002   1966    85.4

或这个 -

  HUC8                    YEAR    RO_MM
  bcc1_45Fall_10010002    1961    74.7
  bcc1_45Fall_10010002    1962    69.1
  bcc1_45Fall_10010002    1963    52.0
  bcc1_45Fall_10010002    1964   130.7
  bcc1_45Fall_10010002    1965    32.2
  bcc1_45Fall_10010002    1966    85.4

如果我可以简单地将“HUC8”列中的所有数据替换为,那就filename完美了。它不需要是额外的列。

我需要对数千个文件执行此操作。

如果我知道如何完成第一部分,我就可以创建一个循环。但也许还有更好的方法?

我不知道从哪里开始。

答案1

使用awkcolumn

$ awk '
  NR==1{ sub(/\.csv$/, "", FILENAME) } # remove .csv suffix from FILENAME
  NR>1{ $1=FILENAME }                  # replace the first field with filename
  1                                    # print record
' bcc1_45Fall_10010002.csv | column -t
HUC8                  YEAR  RO_MM
bcc1_45Fall_10010002  1961  74.7
bcc1_45Fall_10010002  1962  69.1
bcc1_45Fall_10010002  1963  52.0
bcc1_45Fall_10010002  1964  130.7
bcc1_45Fall_10010002  1965  32.2
bcc1_45Fall_10010002  1966  85.4

您可以在 shell 循环中运行此命令,将修改后的文件保存到目录中modified_files

mkdir modified_files &&
for i in *.csv; do
  awk 'NR==1{ sub(/\.csv$/, "", FILENAME) } NR>1{ $1=FILENAME }1' "$i" |
    column -t > "./modified_files/$i"
done

如果您需要替换列HUC8并且这不是第一列,请将代码更改为:

awk -v search='HUC8' '
  NR==1{
    for(i=1;i<=NF;i++)
      if ($i==search){ fld=i; sub(/\.csv$/, "", FILENAME); break }
  }
  NR>1{ $fld=FILENAME }
  1
' file.csv | column -t

答案2

使用磨坊主,并假设您的文件是“简单”CSV(没有逗号之内字段等 -如果需要完整的 RFC-4180 支持,您可以更改--csvlite为)--csv

$ cat bcc1_45Fall_10010002.csv
HUC8,YEAR,RO_MM
10010002,1961,74.7
10010002,1962,69.1
10010002,1963,52.0
10010002,1964,130.7
10010002,1965,32.2
10010002,1966,85.4

然后

  1. 替换当前HUC8列:

     $ mlr --csvlite put -S '$HUC8 = substr(FILENAME,0,-5)' bcc1_45Fall_10010002.csv
     HUC8,YEAR,RO_MM
     bcc1_45Fall_10010002,1961,74.7
     bcc1_45Fall_10010002,1962,69.1
     bcc1_45Fall_10010002,1963,52.0
     bcc1_45Fall_10010002,1964,130.7
     bcc1_45Fall_10010002,1965,32.2
     bcc1_45Fall_10010002,1966,85.4
    
  2. 添加单独的Name列:

     $ mlr --csvlite put -S '$Name = substr(FILENAME,0,-5)' bcc1_45Fall_10010002.csv
     HUC8,YEAR,RO_MM,Name
     10010002,1961,74.7,bcc1_45Fall_10010002
     10010002,1962,69.1,bcc1_45Fall_10010002
     10010002,1963,52.0,bcc1_45Fall_10010002
     10010002,1964,130.7,bcc1_45Fall_10010002
     10010002,1965,32.2,bcc1_45Fall_10010002
     10010002,1966,85.4,bcc1_45Fall_10010002
    
  3. 添加一Name列作为第一列:

     $ mlr --csvlite put -S '$Name = substr(FILENAME,0,-5)' then reorder -f Name bcc1_45Fall_10010002.csv
     Name,HUC8,YEAR,RO_MM
     bcc1_45Fall_10010002,10010002,1961,74.7
     bcc1_45Fall_10010002,10010002,1962,69.1
     bcc1_45Fall_10010002,10010002,1963,52.0
     bcc1_45Fall_10010002,10010002,1964,130.7
     bcc1_45Fall_10010002,10010002,1965,32.2
     bcc1_45Fall_10010002,10010002,1966,85.4
    

以上所有内容将结果写入标准输出 - 要就地修改文件,请添加该-I选项。您可以使用 shell glob ex 一次传递多个文件。bcc*.csv或者*.csv

[测试时没有 -I标题行不会重复,除非由于记录异质性而需要新的标题;但是,-I适当的标头将添加到每个文件中。]

答案3

$ perl -lne 'BEGIN {$fnr=1};

             if ($fnr == 1) {
               ($fn = $ARGV) =~ s/\.[^.]+$//;
               print "NAME,$_"
             } else {
               print "$fn,$_"
             };

             $fnr++;

             if (eof) {$fnr=1}' *.csv

这会将文件名(不带 .csv“扩展名”)添加为第一个字段,并将 .csv 文件的内容打印到标准输出。

与 不同的是awkperl它不会跟踪每个单独文件的行数(它仅跟踪带有变量的总行数$.)。该脚本手动维护该计数,首先在 BEGIN 块中设置变量$fnr,然后为读取的每一行递增该变量,最后每次到达文件末尾时将其重置回 1。

这很容易修改为将文件名附加为最后一个字段而不是第一个字段。例如将两个print语句更改为:

      print "$_,NAME"
and: 
      print "$_,$fn"

如果您需要将文件名字段插入到行中的其他位置,而不是作为第一个字段,您可以使用 perl 的splice函数。

例如,以下将文件名插入为第三个字段(请注意,perl 数组索引从零开始,而不是 1,因此第三个字段是$F[2],而不是$F[3]):

$ perl -F, -lne 'BEGIN {$fnr=1; $field_num=2};

             if ($fnr == 1) {
               ($fn = $ARGV) =~ s/\.[^.]+$//;
               splice @F, $field_num, 0, "NAME";
             } else {
               splice @F, $field_num, 0, $fn;
             };

             print join(",", @F);

             $fnr++;

             if (eof) {$fnr=1}' *.csv

这使用 perl 的-F选项将逗号设置为字段分隔符。这也使得 Perl 的自动分割功能能够自动将输入行分割成一个名为 的数组@F(这类似于 awk 将输入行自动分割成 $1、$2、$3 等的默认行为)。将文字字符串“NAME”或修改后的文件名拼接到@F中,然后@F打印数组的元素,并用逗号字符连接。

最后,如果您想实际更改文件的内容,请使用 perl 的-i选项。您可以选择使用带选项的“扩展名”来保留原始文件的备份-i,例如重命名filename.csv为.例如:filename.csv.orig-iorig

perl -iorig -lne '......' *.csv

或者

perl -iorig -F, -lne '......' *.csv

答案4

然后使用 awk 循环文件名并打印列

for f in *.csv;
do
    head -1 $f > out/$f
    cat $f | awk -v FIN=${f%.csv} 'NR>1 {print FIN, $2, $3}' >> out/$f
done

HUC8       YEAR    RO_MM
bcc1_45Fall_10010002 1961 74.7 
(...)

相关内容