Bash를 사용하여 중첩된 디렉터리를 반복하고 YAML 파일에서 특정 필드를 추출합니다.

Bash를 사용하여 중첩된 디렉터리를 반복하고 YAML 파일에서 특정 필드를 추출합니다.

bash나는 필요한 것이 디렉터리(그 안에 다른 디렉터리 포함)를 반복하고 이름이 인 디렉터리를 찾는 것임을 배우고 있습니다 example.yaml.

이러한 파일에는 여러 키-값 쌍이 있습니다(아래 예).

name: Andre
age: 13
address: street
weight: 78kgs

나에게 필요한 것은 bash 명령을 사용하여 특정 디렉터리(중첩 디렉터리가 포함되어야 함) 내의 모든 파일을 찾은 다음 example.yaml이름과 나이만 새 파일에 복사하는 것입니다. 이 새 파일은 다음과 같이 생성되어야 합니다.

persons:
  - name: Andre
    age: 13
  - name: Joao
    age: 18
  ...

나는 이런 식으로이 문제를 해결하려고 노력했습니다.

printf 'persons:\n' > output.yml
for i in $(find ./ -name "example.yaml");
do
 name=$(yq '.name' $i)
 age=$(yq '.age' $i)
 
 // append $name and $age to output.yaml
done

답변1

참고: 이 답변의 길이는 YAML 데이터를 구문 분석하기 위해 기능과 표현식 구문이 약간 다른 두 가지 이상의 유틸리티 변형이 있기 때문에 발생하며 yq두 가지를 모두 다루었습니다. 또한 모든 파일을 찾기 위해 단순히 파일 이름 글로빙을 사용하는 방법도 고려했습니다.그리고사용됨 find(입력 파일이 너무 많은 경우) 마지막으로 댓글에서 묻는 다른 질문에 답합니다.


출력을 반복하지 마십시오 find. 대신 find을 호출하는 유틸리티를 사용하십시오 -exec. 이 답변 아래에 예가 있습니다. 일부 확장에 대한 참조도 누락되었습니다.

또한보십시오:


명령줄에 하나 이상의 YAML 파일이 있으면 다음 yq명령은 YAML 데이터 요약 파일을 생성합니다.

yq -y -s '{ persons: map({ name: .name, age: .age }) }' files

이 명령은 모든 입력을 큰 배열(thanks -s또는 --slurp)로 읽은 다음 map()명령에 전달합니다. 이 map()명령은 name배열에 있는 각 요소의 합계 필드를 추출하여 배열에 객체로 추가합니다.agepersons

이는 Andrey Kislyuk의 Python 기반을 사용합니다 yq.https://kislyuk.github.io/yq/, 다목적 JSON 파서용 래퍼입니다 jq. 명령에서 이 옵션을 제거 하면 -yJSON 출력이 표시됩니다.

Mike Farah의 Go 기반 yq대안을 사용하세요.

yq -N '[{ "name": .name, "age": .age }]' files | yq '{ "persons": . }'

셸 에서는 다음과 같이 현재 디렉터리 또는 그 아래의 모든 파일 bash에 출력 파일을 적용하여 현재 디렉터리에 출력 파일을 생성 할 수 있습니다.example.yamloutput.yaml

shopt -s globstar failglob

yq -y -s '{ persons: map({ name: .name, age: .age }) }' ./**/example.yaml >output.yaml

또는 Mike Farah의 다음을 사용하세요 yq.

shopt -s globstar failglob

yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml | yq '{ "persons": . }' >output.yaml

이는 파일이 수천 개 미만이거나 example.yaml명령줄이 너무 긴 명령으로 확장될 것이라고 가정합니다.

먼저 셸 옵션을 활성화하면 pathnames 내에서 일치하는 파일 이름 globbing 패턴을 globstar사용할 수 있습니다 . 또한 일치하는 파일 이름이 없는 경우 전체 명령이 정상적으로 실패하도록 셸 옵션을 활성화합니다.**/failglob

시험:

$ tree
.
├── dir1
│   └── example.yaml
├── example.yaml
└── script-andrey
└── script-mike

1 directory, 4 files
$ cat script-andrey
shopt -s globstar failglob
yq -y -s '{ persons: map({ name: .name, age: .age }) }' ./**/example.yaml >output.yaml
$ bash script-andrey
$ cat output.yaml
persons:
  - name: Joao
    age: 18
  - name: Andre
    age: 13

yq또한 마이크를 테스트하십시오.

$ cat script-mike
shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml | yq '{ "persons": . }' >output.yaml
$ bash script-mike
$ cat output.yaml
persons:
  - name: Joao
    age: 18
  - name: Andre
    age: 13

수천 개의 YAML 입력 파일이 있는 경우 yq더 스마트하게 적용하고 find.

이것은 Andre를 사용하고 있습니다 yq.

find . -name example.yaml -type f \
    -exec yq -y -s 'map({ name: .name, age: .age })' {} + |
yq -y '{ persons: . }' >output.yaml

그러면 이름이 example.yaml. 데이터는 일괄적으로 전달되고 해당 데이터에서 및 필드가 yq추출되어 배열이 생성됩니다. 그런 다음 결과 YAML 배열을 수집하고 이를 최종 출력의 키 값 으로 사용하는 최종 명령이 있습니다 .nameageyqpersons

마찬가지로 Mike는 다음과 같이 말했습니다 yq.

find . -name example.yaml -type f \
    -exec yq -N '[{ "name": .name, "age": .age }]' {} + |
yq '{ "persons": . }' >output.yaml

위와 동일한 파일 세트로 테스트합니다.

$ rm output.yaml
$ find . -name example.yaml -type f -exec yq -y -s 'map({ name: .name, age: .age })' {} + | yq -y '{ persons: . }' >output.yaml
$ cat output.yaml
persons:
  - name: Andre
    age: 13
  - name: Joao
    age: 18

(Mike용으로 설계된 명령을 실행하면 yq동일한 출력이 생성됩니다.)

find출력 순서는 파일이 발견된 순서에 따라 달라집니다.

예를 들어 필드에서 출력 파일을 정렬하려면 name다음과 같이 파일을 제자리에서 정렬합니다(Mike Farah의 Go 기반 코드를 사용하여 이 작업을 수행하는 방법을 모르겠습니다 yq).

yq -i -y '.persons |= sort_by(.name)' output.yaml

역순으로(내부) 정렬하려면 다음을 수행합니다.

yq -i -y '.persons |= (sort_by(.name) | reverse)' output.yaml

댓글에서 사용자는 기존 파일에 데이터를 추가할 수 있는지 물었습니다. 이것은 가능하다.

아래 명령은 마지막 항목이 output.yaml배열의 끝이라고 가정합니다 persons(명령이 배열에 새 배열 항목을 추가할 수 있도록).

Andre의 사용 yq:

shopt -s globstar failglob
yq -y -s 'map({ name: .name, age: .age })' ./**/example.yaml >>output.yaml

또는 find,

find . -name example.yaml -type f \
    -exec yq -y -s 'map({ name: .name, age: .age })' {} + >>output.yaml

Mike의 것을 사용하십시오 yq:

shopt -s globstar failglob
yq -N '[{ "name": .name, "age": .age }]' ./**/example.yaml >>output.yaml

또는 다음을 사용하십시오 find.

find . -name example.yaml -type f \
    -exec yq -N '[{ "name": .name, "age": .age }]' {} + >>output.yaml

답변2

이를 수행하는 방법에는 여러 가지가 있지만 가장 간단한 방법은 아마도find주문하다.

먼저, 새로운 배열 구조를 사용하여 출력 파일을 생성합니다.

echo "persons:" > newfile.yaml

다음으로 각 항목을 식별해야 합니다.문서대상 디렉터리의 파일 이름과 일치합니다 example.yaml(라고 부르겠습니다 /home/user/yaml-files). 이것은 find의 기본 사용 사례이며 이해하기 매우 쉽습니다.

find /home/user/yaml-files -type f -name example.yaml

find-exec일치하는 항목이 발견되면 및 옵션을 사용하여 -execdir쉘 명령을 실행 하는 강력한 내장 함수가 있습니다 . while -exec과 동일한 작업 디렉토리에서 실행되는 것은 쉘 명령이 실행되므로 더 안전한 옵션입니다.find-execdir"안에"일치 항목이 발견된 디렉터리입니다. 단순화를 위해 를 사용하겠습니다 -exec.

example.yaml이러한 파일에서 원하는 줄을 검색하고 형식을 다시 지정한 다음 결과를 출력 파일에 추가 해야 합니다 .

find /home/user/yaml-files -type f -name example.yaml -exec awk '$1 ~ /^name:|^age:/ {gsub(/name:/,"  - name:",$1); gsub(/age:/,"    age:",$1); print $0}' {} \; | tee -a newfile.yaml

여기에 포함된 명령은 공백이나 다른 문자가 앞에 오지 않고 또는 로 시작하는 모든 줄을 awk검색합니다 . 문자열 교체에 유용한 내장 함수 입니다 . 여기 에 일치하는 줄을 인쇄하는 2개의 필터가 있습니다 .example.yamlname:age:gsubawkgsubstdout

일반적으로 리디렉션을 사용하여 출력을 파일에 쓰지만 find -exec그렇게 하면 약간 복잡해집니다. 이 경우 이 tee명령은 훌륭합니다. 출력을 콘솔뿐만 아니라 파일에도 표시합니다. 이 -a옵션 tee추가의그렇지 않으면 매번 파일을 덮어쓰게 되어 마지막으로 파일을 썼을 때의 결과만 남게 됩니다.

이 솔루션은 제가 아는 한 여러분이 접하게 될 모든 Linux 시스템에 존재하는 몇 가지 명령만을 사용합니다. 특별한 요구 사항이 없으며 코드는 이식성이 매우 뛰어납니다.

답변3

특정 이름의 파일을 찾고 있다면 example.yaml매우 쉽게 찾을 수 있습니다. 먼저 새 파일을 만들고 모든 파일로 시작 하거나 모든 파일에서 persons:시작하는 모든 줄을 추가합니다 .name:age:example.yaml

printf 'persons:\n' > personsFile
find /target/directory -name example.yaml -exec grep -E '^(name|age):' {} + >> personsFile

-각 항목 앞에 들여쓰기가 정말로 필요한 경우 name두 번째 단계에서 추가할 수 있습니다.

printf 'persons:\n' > personsFile
find /target/directory -name example.yaml -exec grep -E '^(name|age):' {} + >> personsFile
sed -i 's/^name/  - name/; s/^age/    age/' personsFile

하지만 실제로 YAML과 같은 구조화된 형식을 다루고 있다면 이렇게 해킹하는 대신 전용 도구를 살펴봐야 할 것입니다.

답변4

man find xargs grep bash다음을 읽고 수행하십시오.

printf "%s\n" "persons:" >newfile
find . -type f -name '*.yaml' -print0 | \
    xargs -0 -r \
        grep -E --no-filename 'name:|age:' >>newfile

참고: 이 코드는 테스트되지 않았습니다.

관련 정보