AWK에 변수 전달이 루프 내에서 작동하지 않습니다.

AWK에 변수 전달이 루프 내에서 작동하지 않습니다.

YAML 파일을 읽고 특정 값을 출력하는 awk 문이 있습니다. 값 목록에서 키 값을 읽고 해당 키를 awk에 전달하는 루프에서 이 awk를 반복해야 합니다.

YAML 파일의 구조는 다음과 같습니다.

abc:
  NAME: Bob
  OCCUPATION: Technician
def:
  NAME: Jane
  OCCUPATION: Engineer

abc OCCUPATION키 값을 얻고 싶다고 가정 TECHNICIAN하고 인터넷 검색을 통해 내가 원하는 것을 제공하는 awk 문을 구성했습니다.

> awk 'BEGIN{OFS=""} /^[^ ]/{ f=/^abc:/; next } f{ if (sub(/:$/,"")) abc=$2; else print abc,$1 $2}' test.yml| grep "OCCUPATION:" | cut -d':' -f2
Technician

그러나 이 루프를 사용하면 -v 옵션을 awk에 전달해도 결과가 나오지 않는 것 같습니다.

items="abc,def"
for item in $(echo $items | sed "s/,/ /g"); 
do
 echo $item;
 awk -v name="$item" 'BEGIN{OFS=""} /^[^ ]/{ f=/^\name:/; next } f{ if (sub(/:$/,"")) name=$2; else print name,$1 $2}' test.yml| grep "OCCUPATION:" | cut -d':' -f2; 
done

내가 설정한 디버그 에코만 나타납니다.

abc
def

내가 어디서 잘못됐나요? 변수가 awk에서 올바르게 해석되어야 한다고 생각합니까?

편집: Steeldrivers의 의견을 바탕으로 입력 내용을 일부 변경했습니다.

items="abc,def"
for item in $(echo $items | sed "s/,/ /g"); 
do
 echo $item;
 awk -v name="$item" 'BEGIN{OFS=""} /^[^ ]/{ f=name; next } f{ if (sub(/:$/,"")) name=$2; else print name,$1 $2}' test.yml| grep "OCCUPATION:" | cut -d':' -f2; 
done

하지만 이제 OCCUPATION모든 값이 인쇄됩니다.

abc
Technician
Engineer
def
Technician
Engineer

연산자 를 사용해 보았지만 ~오류가 발생하여 올바르게 사용하지 않는 것 같아서 값을 직접 구문 분석하기로 결정했지만 이로 인해 중복이 생성됩니다.

답변1

YAML, JSON 또는 XML과 같은 구조화된 텍스트로 작업할 때 구조를 "이해"하는 파서를 사용해야 합니다. 다양한 종류의 구조화된 텍스트(예: xmlstarletxml, jqjson 및yqyaml의 경우) 대부분의 프로그래밍/스크립팅 언어에는 구조화된 텍스트를 구문 분석하고 처리하기 위한 라이브러리가 있습니다.

Perl 코어 YAML 모듈을 사용하여 Perl에서 이 작업을 수행하는 방법은 다음과 같습니다.

(YAML 모듈이 핵심 모듈 배포의 표준 부분으로 포함되었을 때 perl >= 5.14 버전이 필요합니다. Perl 5.14는 2013년에 출시되었습니다. 이전 버전의 Perl의 경우 를 사용하여 YAML을 설치할 수 있습니다 cpan.

#!/usr/bin/perl

use strict;
use YAML qw(LoadFile);

my $file = shift;            # first arg is the input filename
my $data = LoadFile($file);  # load the yaml data into a hashref variable

# loop over the remaining args (i.e. the keys)
foreach my $item (@ARGV) {
    print "$item\n";
    print $$data{$item}{'OCCUPATION'}, "\n";
}

예를 들어 다른 이름으로 저장 yaml.pl하고 실행 가능하게 만듭니다 chmod +x yaml.pl.

yaml 데이터가 라는 파일에 저장된 경우 input.yaml다음과 같이 실행할 수 있습니다.

$ ./yaml.pl input.yaml abc def
abc
Technician
def
Engineer

awk 또는 sed처럼 이는 이해하기 어려운 한 줄로 압축될 수 있습니다.

$ perl -MYAML=LoadFile -E '$data=LoadFile(shift);foreach (@ARGV) {say $_;say $$data{$_}{"OCCUPATION"}}' input.yaml abc def
abc
Technician
def
Engineer

Perl은 자동으로 매개변수를 분할할 수도 있습니다. 예를 들어, foreach루프를 다음과 같이 변경 하면 :

foreach my $item (split /\s*,\s*/,join(",",@ARGV)) {

다음과 같이 실행할 수 있습니다.

$ ./yaml.pl input.yaml abc def

또는

$ ./yaml.pl input.yaml "abc,def"

또는 임의의 조합(ghi 및 jkl 키가 사용된다고 가정):

$ ./yaml.pl input.yaml "abc,def" ghi jkl

답변2

사용 yq( jq래퍼https://kislyuk.github.io/yq/) 명령줄(또는 스크립트)에서 YAML을 구문 분석하려면 다음을 수행하세요.

$ yq -r '.abc.OCCUPATION' file.yml
Technician

쉘 루프에 제공하십시오 abc.def

$ for thing in abc def; do yq -r --arg node "$thing" '$node,.[$node].OCCUPATION' file.yml; done
abc
Technician
def
Engineer

또는 탭으로 구분된 열의 ​​경우:

$ for thing in abc def; do yq -r --arg node "$thing" '[$node,.[$node].OCCUPATION] | @tsv' file.yml; done
abc     Technician
def     Engineer

yq즉, with 호출 --arg뒤에는 yq설정할 변수 이름과 설정할 값이 옵니다. 그런 다음 해당 변수를 표현식에 사용하십시오 yq. 이게 효과가 있어비슷하게존재하다 jq.

셸 루프가 없으며 대신 최상위 키에서 값을 가져옵니다.

$ yq -r 'foreach keys[] as $node (.;.;[$node,.[$node].OCCUPATION]|@tsv)' file.yml
abc     Technician
def     Engineer

yqYAML 구문 분석에 사용할 수 있는 다른 도구가 있습니다 . Ubuntu에 설치하는 경우 yqMike snapFarah라는 사람으로부터 버전을 받게 됩니다. 다르게 작동합니다. 저는 이를 사용하여 JSON으로 변환한 다음 데이터를 다음으로 파이프하는 경향이 있습니다 jq.

$ yq -j e file.yml | jq -r '.abc.OCCUPATION'
Technician
$ for thing in abc def; do yq -j e file.yml | jq -r --arg node "$thing" '$node,.[$node].OCCUPATION'; done
abc
Technician
def
Engineer

또는 탭으로 구분된 열의 ​​경우:

$ for thing in abc def; do yq -j e file.yml | jq -r --arg node "$thing" '[$node,.[$node].OCCUPATION] | @tsv'; done
abc     Technician
def     Engineer

답변3

적절한 텍스트 처리 도구가 있는 경우 간단한 텍스트를 처리하기 위해 쉘 루프가 필요하지 않습니다.; 아래에서는 GNU를 사용합니다.이를 위해 현재 일치하는 RS에 대한 역참조인 다중 문자 RS 및 RT를 정의할 수 있습니다.

$ awk -v RS='(^|\n)[a-z]+:\n' 'rt ~ /^abc:\n$/ { print $NF; exit } { rt=RT }' infile
Technician

신고된 값이 사실인지 엄격히 확인"직업"키를 지정하고 변수에서 키/헤더를 하드코딩하는 대신 전달하면 다음을 수행할 수 있습니다.

$ awk -v hdr='abc' -v key='OCCUPATION' -v RS='(^|\n)[a-z]+:\n' -F'\n' \
'rt ~ ("^" hdr ":\n") { 
     for(i=1; i<=NF; i++)
         if(match($i, "^\\s*" key ":\\s*" )) { print substr($i, RSTART+RLENGTH); exit }
}
{ rt=RT }' infile
Technician

답변4

또한 사용 awk:

awk -F'[[:space:]]+' '$1 == "" {if (s == "abc:" && $2 == "OCCUPATION:") print $3; next} {s=$1}' file
Technician

직업이 "네트워크 기술자"이거나 공백이 포함된 직업인 경우에는 실패합니다. 이를 방지하려면 다음을 수행하십시오.

awk -F'[[:space:]]+' '$1 == "" {if (s == "abc:" && $2 == "OCCUPATION:") { sub(/[^:]*:[[:space:]]*/,""); print }; next} {s=$1}' file
Technician

Ed Morton의 솔루션은 { sub(/[^:]*:[[:space:]]*/,""); print }여기서도 작동합니다 print $3.

관련 정보