두 번째 사이클 문제

두 번째 사이클 문제

안녕하세요, 다음 루프를 실행하여 여러 위도와 경도에 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

Bash for루프에서 다음 구문을 따르세요.

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, 또는 perl( python또는 쉘이 아닌 거의 모든 언어)를 사용해야 합니다 . 바라보다쉘 루프를 사용하여 텍스트를 처리하는 것이 왜 나쁜 습관으로 간주됩니까?그리고공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?많은 이유가 있습니다.

또한 많은 언어에는 NetCDF 데이터 작업을 위한 라이브러리 모듈이 있습니다. 예를 들어 Perl에는PDL::NetCDF파이썬으로네트워크 CDF4.

NetCDF 처리 라이브러리를 사용하지 않고도 셸에서 수행될 수 있는 일반적인 작업을 스크립팅하는 것이 더 쉽습니다 awk.perl

예를 들어, 이것은 Perl 버전의 스크립트입니다. Perl을 선택한 이유는 sed, awk, cut, tr의 많은 기능을 하나의 언어로 결합하고 매우 유용하기 때문입니다 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공백이나 셸 메타 문자(예: ;, &)가 포함되어 있고 큰따옴표 없이 사용되는 경우 셸 단어 분리 및 해석의 영향을 받습니다.~ 할 것이다스크립트가 중단됩니다(따라서 쉘에서는 항상 큰따옴표로 변수를 인용해야 합니다).


이는 두 개의 연관 배열을 사용하는 대체 버전입니다. 이는 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와 같은 정규 표현식 연산자가 있다는 것을 두려워하기 때문입니다. 정규 표현식 호출 함수에 래핑되지 않으면 약간 무섭고 읽을 수 없습니다) ....IMO, 위의 두 Perl 예제는 여러 명령 대체가 있는 쉘 스크립트보다 더 깨끗하고 읽고 이해하기 쉽습니다. 또한 네 번 sed분기 할 필요가 없기 때문에 더 빠르게 실행됩니다.cut루프 반복(예: coords.txt에 행 수에 관계없이 3회).

관련 정보