列を追加、列全体をファイル名(「.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

シェルループでこのコマンドを実行して、変更したファイルをディレクトリに保存できます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ファイルの内容が標準出力に印刷されます。

awkとは異なり、perl各個々のファイルの行数を追跡しません(変数のある行の総数のみを追跡します$.)。スクリプトはまずBEGINブロックに変数を設定し、$fnr読み取った各行に対して変数をインクリメントし、最後にファイルの終わりに達するたびに再び1にリセットして、この数を手動で維持します。

ファイル名を最初のフィールドではなく最後のフィールドとして追加するように簡単に変更されます。たとえば、print2 つの文を次のように変更します。

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

最初のフィールド以外の行の他の場所にファイル名フィールドを挿入する必要がある場合は、Perlのsplice機能を使用できます。

たとえば、次はファイル名を3番目のフィールドとして挿入します(perl配列のインデックスは1ではなく0から始まるため、3番目のフィールドは$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(これは、入力行を$ 1、$ 2、$ 3などに自動的に分割するawkのデフォルトの動作と似ています)。リテラル文字列 "NAME" または変更されたファイル名を @F で連結し、@Fコンマ文字で連結された配列要素を印刷します。

最後に、ファイルの内容を実際に変更するには、Perlの-iオプションを使用します。-i名前の変更などのオプションとともに、「拡張子」を使用して元のファイルのバックアップを保持することを選択できますfilename.csvfilename.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 
(...)

関連情報