2番目のサイクルの問題

2番目のサイクルの問題

こんにちは、次のループを実行して複数の緯度と経度にremapnnコマンドを適用できます。

era_Temperature_2016era_Temperature_2017era_Temperature_2018era_Temperature_2019era_Temperature_2020という名前のNETCDFファイルがたくさんあり、これらすべてのファイルにループを適用したいと思います。

#!/bin/bash

infile="era_temperature_2016.nc"
coords="coords.txt"

while read line
do
   line=$(echo $line | sed -e 's/\r//g')
   station=$(echo $line | cut -d ' ' -f 1)
   #-- skip header line
   if [[ "$station" == "station" ]]; then continue; fi
   #-- select station coordinates
   lat=$(echo $line | cut -d ' ' -f 2)
   lon=$(echo $line | cut -d ' ' -f 3)
   station="${station}_${lat}_${lon}"
   #-- extract the station data
   cdo -remapnn,"lon=${lon}_lat=${lat}" ${infile} ${station}_out.nc
done < $coords

以下を試しましたが、エラーが発生します。

間違い ./values1.sh:行5:coords="coords.txt"' ./values1.sh: line 5:予期しないタグcoords = "coords.txt"'の近くに構文エラーがあります。

#!/bin/bash

my_files=$(ls era_temperature_*.nc)
for f in $my_files
coords="coords.txt"

while read line
do
   line=$(echo $line | sed -e 's/\r//g')
   station=$(echo $line | cut -d ' ' -f 1)
   #-- skip header line
   if [[ "$station" == "station" ]]; then continue; fi
   #-- select station coordinates
   lat=$(echo $line | cut -d ' ' -f 2)
   lon=$(echo $line | cut -d ' ' -f 3)
   station="${station}_${lat}_${lon}"
   #-- extract the station data
   cdo -remapnn,"lon=${lon}_lat=${lat}" ${infile} ${station}_out.nc
done < $coords

皆様のご意見・ご協力ありがとうございます

以下のコードはうまくいきます

#!/bin/bash

for NUM in $(seq 2016 2018)
do
infile=era_temperature_$NUM.nc
coords="coords.txt"

while read line
do
   line=$(echo $line | sed -e 's/\r//g')
   station=$(echo $line | cut -d ' ' -f 1)
   #-- skip header line
   if [[ "$station" == "station" ]]; then continue; fi
   #-- select station coordinates
   lat=$(echo $line | cut -d ' ' -f 2)
   lon=$(echo $line | cut -d ' ' -f 3)
   station="${station}_${NUM}_${lat}_${lon}"
   #-- extract the station data
   cdo -remapnn,"lon=${lon}_lat=${lat}" ${infile} ${station}_out.nc
done < $coords
done 

答え1

Bashforループで、次の構文に従ってください。

for <variable name> in <a list of items> ; do <some command> ; done

それを分析しましょう。

for配列を繰り返すことをシェルに通知します。

<variable name>現在反復している配列の項目を保存する場所をシェルに提供します。

in <a list of items>繰り返す配列を指定します。

;スクリプト内のセミコロンまたは実際の改行文字である可能性がある改行文字を指定します。

do <some command>はループで実行したいコマンドで、以前にforループで定義されていた変数を含めることができますが、必ずしもそうではありません。

;今回はループ終了を準備するために改行します。

doneこれでループが閉じます。

したがって、for f in $my_files追加した内容から、後に改行文字があることがわかりますが、シェルがdo期待したaを定義するのではなく、シェルが予期しない変数を定義しました。シェルは、これが起こるとは思わないため、構文エラーメッセージで終了します。doneループしたいコードの終わりにも終端はありません。ループwhileには終端がありますが、doneループには終端はありませんfor

また、次のことを検討してください。lsの解析を避ける。問題が発生する可能性があります。ファイルの繰り返しなどの簡単な操作の場合は、以下を削除すると同じ操作を簡単に実行できますls

thegs@wk-thegs-01:test$ ls 
test1.txt  test2.txt  test3.txt
thegs@wk-thegs-01:test$ for file in test*.txt ; do echo $file ; done
test1.txt
test2.txt
test3.txt

続行する前にループ構文を調べることも悪くありません。 Redhatは以下を提供します。アクセシビリティ文書Bashのループについては、読書を強くお勧めします(残念ながら構文解析しますlsが、完璧な人はいません)。

答え2

Shell はデータ操作に誤った言語です。awk、またはperlpythonまたはシェル以外のほとんどすべての言語)を使用する必要があります。バラよりシェルループを使用してテキストを処理するのはなぜ悪い習慣と見なされますか?そしてスペースやその他の特殊文字が原因でシェルスクリプトが停止するのはなぜですか?多くの理由があります。

さらに、多くの言語にはNetCDFデータを操作するためのライブラリモジュールがあります。たとえば、PerlPDL::NetCDFPythonでネットワーク CDF4

NetCDF処理ライブラリを使用せずにシェルで実行できる一般的なタスクをスクリプト化する方が簡単ですawkperl

たとえば、これはPerlバージョンのスクリプトです。 Perlを選択した理由は、sed、awk、cut、trの多くの機能を1つの言語に組み合わせて非常に便利なためですsplit()。そして最後に、Perlのsystem()関数は引数の代わりに引数のセットを使うことができるからです。単純な文字列ではありません(シェルと同じ迷惑を引き起こし、同じ解決策が必要です)。

#!/usr/bin/perl

use strict;
my @coords=();

# Read coords.txt into an array, so we don't have to read it
# again for each year.
#
# Yes, you could read coords.txt into an array in bash too - I very
# strongly encourage you to do so if you decide to stick to shell.
# In bash, its probably best to read coords.txt into three arrays, one
# each for station, lon, and lat. Or two associative arrays, one each
# for lon and lat (both with station as the key).
# Anyway, see `help mapfile` in bash.

my $coords = "coords.txt";
open(my $C, "<", $coords) || die "couldn't open $coords for read: $!\n";
while(<$C>) {
  next if /^station/; # skip header
  chomp;              # get rid of \n, \r, or \r\n line-endings
  push @coords, $_;
};
close($C);

# process each year
foreach my $num (2016..2018) {
  my $infile = "era_temperature_$num.nc";

  # process the coords data for the current year
  foreach (@coords) {
    my ($station, $lat, $lon) = split;
    $outfile = "${station}_${num}_${lat}_${lon}_out.nc";

    system("cdo", "-remapnn", "lon=${lon}_lat=${lat}", $infile, $outfile);
  };
};

各変数全体を次のように渡すので、引用符なしでsystem()使用するのは完全に安全です。$infile$outfile一つ主張がcdo何であれ。これはいいえbash は true -$infileまたは$outfileスペースまたはシェルのメタ文字 (たとえば、;)&が含まれ、二重引用符なしで使用される場合、シェルの単語の分離と解釈の影響を受けます。〜するスクリプトは中断されます(したがって、シェルでは常に二重引用符で変数を引用する必要があります)。


これは、2つの連想配列を使用する代替バージョンです。これはsplit()coords.txt の各行に対して一度だけ使用すればよいので、少し早くすることができますが、coords.txt ファイルに何千もの行がないと目立たないでしょう。

#!/usr/bin/perl

use strict;
my %lon = ();
my %lat = ();

# Read coords.txt into two hashes (associative arrays), one
# each for lon and lat.

my $coords = "coords.txt";
open(my $C, "<", $coords) || die "couldn't open $coords for read: $!\n";
while(<$C>) {
  next if /^station/; # skip header
  chomp;              # get rid of \n, \r, or \r\n
  my ($station, $lat, $lon) = split;
  $lat{$station} = $lat;
  $lon{$station} = $lon;
}
close($C);

foreach my $num (2016..2018) {
  my $infile = "era_temperature_$num.nc";
  foreach my $station (sort keys %lat) {
    # Two different ways of constructing a string from other variables.

    # Simple interpolation, as in the first version above:
    my $outfile = "${station}_${num}_${lat{$station}}_${lon{$station}}";

    # And string concatenation with `.`, which can be easier to read
    # in some cases.
    my $lonlat = "lon=" . $lon{$station} . "_lat=" . $lat{$station};

    # Another method is to use sprintf, which can be even easier to read.
    # For example, use the following instead of the line above:
    # my $lonlat = sprintf "lon=%s_lat=%s", $lon{$station}, $lat{$station};
    #
    # note: bash has a printf built-in too.  I highly recommend using it.
    

    system("cdo", "-remapnn", $lonlat, $infile, $outfile);
  };
};

しかし、Perlには非常に便利な参照演算子もあります。たとえば、次の行を次のように書くことがqw()できます。system()

system(qw(cdo -remapnn lon=${lon}_lat=${lat} $infile $outfile));

または(連想配列バージョンの場合):

system(qw(cdo -remapnn $lonlat $infile $outfile));

perldoc -f qw詳細より。

最後に、一部の人々はPerlが読んだり理解したりするのが難しいと無意識のうちに主張しています。 (AFAICTこれは主にPerlにsedのような正規表現演算子があることを恐れているためです。あるシェルスクリプトよりもクリーンで読みやすく理解しやすいです。また、4回sed分岐する必要がないため、より速く実行されます。cutループの繰り返し(例:coords.txtの行数に関係なく3回)。

関連情報