내 변수를 사용하여 템플릿에서 yaml 파일을 만들려고 합니다. 내 yaml 템플릿은 다음과 같습니다
number: {{NUMBER}}
name: {{NAME}}
region: {{REGION}}
storenum: {{STORENUM}}
clients: {{CLIENTS}}
tags: {{TAGS}}
storename: {{STORENAME}}
employee: {{EMPLOYEE}}
products: {{PRODUCTS}}
하지만 내 변수는 CSV 파일에 있습니다. 구조는 변수입니다.
Number - Name - Region - Storenum
StoreX - StoreX - New York - 30
이제 가변 매개변수가 있는 템플릿에서 생성하는 작은 스크립트가 생겼으며 템플릿은 다음과 같습니다 script.sh template.yml -f variables.txt
. 내 결과는 다음과 같습니다
number: 37579922
name: Store1
region: New York
storenum: 32
clients: 100
tags: stores
storename: Store newyork
employee: 10
products: 200
하지만 한 번에 하나만 할 수 있어요. CSV 매개변수를 읽고 프로그램으로 전송하여 예를 들어 Template1,Template2,etc
CSV 매개변수에서 생성할 수 있는 방법이 있습니까?
#!/bin/bash
readonly PROGNAME=$(basename $0)
config_file="<none>"
print_only="false"
silent="false"
usage="${PROGNAME} [-h] [-d] [-f] [-s] --
where:
-h, --help
Show this help text
-p, --print
Don't do anything, just print the result of the variable expansion(s)
-f, --file
Specify a file to read variables from
-s, --silent
Don't print warning messages (for example if no variables are found)
examples:
VAR1=Something VAR2=1.2.3 ${PROGNAME} test.txt
${PROGNAME} test.txt -f my-variables.txt
${PROGNAME} test.txt -f my-variables.txt > new-test.txt"
if [ $# -eq 0 ]; then
echo "$usage"
exit 1
fi
if [[ ! -f "${1}" ]]; then
echo "You need to specify a template file" >&2
echo "$usage"
exit 1
fi
template="${1}"
if [ "$#" -ne 0 ]; then
while [ "$#" -gt 0 ]
do
case "$1" in
-h|--help)
echo "$usage"
exit 0
;;
-p|--print)
print_only="true"
;;
-f|--file)
config_file="$2"
;;
-s|--silent)
silent="true"
;;
--)
break
;;
-*)
echo "Invalid option '$1'. Use --help to see the valid options" >&2
exit 1
;;
# an option argument, continue
*) ;;
esac
shift
done
fi
vars=$(grep -oE '\{\{[A-Za-z0-9_]+\}\}' "${template}" | sort | uniq | sed -e 's/^{{//' -e 's/}}$//')
if [[ -z "$vars" ]]; then
if [ "$silent" == "false" ]; then
echo "Warning: No variable was found in ${template}, syntax is {{VAR}}" >&2
fi
fi
# Load variables from file if needed
if [ "${config_file}" != "<none>" ]; then
if [[ ! -f "${config_file}" ]]; then
echo "The file ${config_file} does not exists" >&2
echo "$usage"
exit 1
fi
source "${config_file}"
fi
var_value() {
eval echo \$$1
}
replaces=""
# Reads default values defined as {{VAR=value}} and delete those lines
# There are evaluated, so you can do {{PATH=$HOME}} or {{PATH=`pwd`}}
# You can even reference variables defined in the template before
defaults=$(grep -oE '^\{\{[A-Za-z0-9_]+=.+\}\}' "${template}" | sed -e 's/^{{//' -e 's/}}$//')
for default in $defaults; do
var=$(echo "$default" | grep -oE "^[A-Za-z0-9_]+")
current=`var_value $var`
# Replace only if var is not set
if [[ -z "$current" ]]; then
eval $default
fi
# remove define line
replaces="-e '/^{{$var=/d' $replaces"
vars="$vars
$current"
done
vars=$(echo $vars | sort | uniq)
if [[ "$print_only" == "true" ]]; then
for var in $vars; do
value=`var_value $var`
echo "$var = $value"
done
exit 0
fi
# Replace all {{VAR}} by $VAR value
for var in $vars; do
value=$(var_value $var | sed -e "s;\&;\\\&;g" -e "s;\ ;\\\ ;g") # '&' and <space> is escaped
if [[ -z "$value" ]]; then
if [ $silent == "false" ]; then
echo "Warning: $var is not defined and no default is set, replacing by empty" >&2
fi
fi
# Escape slashes
value=$(echo "$value" | sed 's/\//\\\//g');
replaces="-e 's/{{$var}}/${value}/g' $replaces"
done
escaped_template_path=$(echo $template | sed 's/ /\\ /g')
eval sed $replaces "$escaped_template_path"
답변1
다음 명령을 사용하여 Perl에서 이 작업을 수행하는 매우 간단한 예가 있습니다.텍스트::CSVCSV를 구문 분석하는 모듈입니다.
명령줄 옵션 처리는 수행되지 않습니다(비록 다음을 사용하여 쉽게 수행할 수 있음).GetSelect::표준또는 Getopt::Long이면 충분하지만 기본적(펄에는 포함되어 있음)이거나 다음과 같은 고급 모듈입니다.Getopt::깨어나다, 설치가 필요하지만 옵션을 사용하여 원하는 거의 모든 작업을 수행할 수 있습니다.
이는 단순히 템플릿을 구분된 문서로 스크립트에 포함시키는 것입니다. 더 복잡한 템플릿이 필요한 경우 다음을 사용하세요.텍스트::템플릿라이브러리 모듈.
또한 출력을 표준 출력으로 인쇄합니다. 평소대로 셸에서 리디렉션할 수 있습니다. 또는 각 csv 입력 줄의 출력을 별도의 파일에 저장해야 하는 경우 Perl이 쉽게 쓰기 위해 파일을 열고 출력을 문서에 인쇄하도록 할 수 있습니다.
약 140줄(그 중 약 1/3은 주석, 빈 줄, 사용법 메시지)의 bash 스크립트와 비교하면 이 Perl 스크립트에는 총 35줄이 있으며 그 중 12줄은 템플릿, 6줄은 주석, 8줄은 그 중 빈 줄이 있습니다. Bash의 코드 약 90줄과 비교하면 실제 코드는 9줄입니다.
Bash 스크립트와는 달리 처리할 인용이나 공백 문제가 없으며 sed
빌드된 (grep, sed와 동일), tr 등과 같은 외부 프로그램을 반복적으로 분기할 필요가 없기 때문에 더 빠르게 실행됩니다. -Perl의 경우). 또한 Text::CSV 모듈은 쉼표가 포함된 필드(TAGS 필드)를 쉽게 처리할 수 있습니다. 이는 정규식으로 위조하는 대신 실제 CSV 파서를 사용하는 이점 중 하나입니다.
#!/usr/bin/perl
use strict;
use Text::CSV;
# open the CSV file for read
my $file = 'data.csv';
open(my $fh, "<", $file) or die "Couldn't open $file: $!\n";
# initialise a csv object
my $csv = Text::CSV->new();
# read the header line
my @headers = $csv->getline($fh);
$csv->column_names(@headers);
# iterate over each line of the CSV file, reading
# each line into a hash (associative array) reference.
while (my $row = $csv->getline_hr($fh)) {
print <<__EOF__;
number: $row->{NUMBER}
name: $row->{NAME}
region: $row->{REGION}
storenum: $row->{STORENUM}
clients: $row->{CLIENTS}
tags: $row->{TAGS}
storename: $row->{STORENAME}
employee: $row->{EMPLOYEE}
products: $row->{PRODUCTS}
__EOF__
}
close($fh);
data.csv
다음이 포함된 경우 :
NUMBER,NAME,REGION,STORENUM,CLIENTS,TAGS,STORENAME,EMPLOYEE,PRODUCTS
37579922,Store1,New York,32,100,stores,Store newyork,10,200
2,Store2,Somewhere,2,100,"tag1,tag2,tag3",Somewhere Store,5,10
3,Store3,Elsewhere,3,100,"tag1,tag3",Elsewhere Store,3,100
그런 다음 실행하면 다음과 같은 출력이 생성됩니다.
$ ./template-example.pl
number: 37579922
name: Store1
region: New York
storenum: 32
clients: 100
tags: stores
storename: Store newyork
employee: 10
products: 200
number: 2
name: Store2
region: Somewhere
storenum: 2
clients: 100
tags: tag1,tag2,tag3
storename: Somewhere Store
employee: 5
products: 10
number: 3
name: Store3
region: Elsewhere
storenum: 3
clients: 100
tags: tag1,tag3
storename: Elsewhere Store
employee: 3
products: 100
그건 그렇고, 원한다면 python
Python으로 작성하는 것이 Perl로 작성하는 것만큼 쉽습니다.
답변2
이 gawk
솔루션은 GNU Awk v5.1.0에서 테스트되었습니다.
이는 두 개의 입력 파일을 허용하는 Bash 스크립트로 구성됩니다. ( yaml_template
OP에 제공됨):
$ cat yaml_template
number: {{NUMBER}}
name: {{NAME}}
region: {{REGION}}
storenum: {{STORENUM}}
clients: {{CLIENTS}}
tags: {{TAGS}}
storename: {{STORENAME}}
employee: {{EMPLOYEE}}
products: {{PRODUCTS}}
( data.csv
그의 답변에서 @cas가 제안함):
$ cat data.csv
NUMBER,NAME,REGION,STORENUM,CLIENTS,TAGS,STORENAME,EMPLOYEE,PRODUCTS
37579922,Store1,New York,32,100,stores,Store newyork,10,200
2,Store2,Somewhere,2,100,"tag1,tag2,tag3",Somewhere Store,5,10
3,Store3,Elsewhere,3,100,"tag1,tag3",Elsewhere Store,3,100
Bash 스크립트 yamlit.sh
실행 파일( cmd 사용 $ chmod ug+x yamlit.sh
):
$ cat yamlit.sh
#!/usr/bin/env bash
gawk -F"[,:]" '
FNR==NR {
match($0,/[^[:blank:]]+/); i++;
if (RSTART-1 < 0) {$1="";null++; teenar[i] = ""} else {$1 = substr($1,RSTART); teenar[i] = $1};
teenof[$1] = RSTART - 1;
next;
}
FNR==1 {nteen=i; ncol=split(tolower($0), colhead, ",");next;}
{
for (i=1; i<=nteen; i++) {
offset=teenof[teenar[i]];
if (offset >= 0) {
patsplit($0,datafield,"([^,]*)|(\"[^\"]*\")")
for (j=1; j<=ncol; j++) {
if (tolower(teenar[i]) == colhead[j]) {
printf "%*s: %s\n", length(teenar[i]) + offset, teenar[i], datafield[j];
}
}
}
else {print ""}
}
printf "\n%s\n", "=========================="
}' "$1" "$2"
터미널에서 스크립트를 실행하면 다음이 생성됩니다.
$ yamlit.sh yaml_template data.csv
number: 37579922
name: Store1
region: New York
storenum: 32
clients: 100
tags: stores
storename: Store newyork
employee: 10
products: 200
==========================
number: 2
name: Store2
region: Somewhere
storenum: 2
clients: 100
tags: "tag1,tag2,tag3"
storename: Somewhere Store
employee: 5
products: 10
==========================
number: 3
name: Store3
region: Elsewhere
storenum: 3
clients: 100
tags: "tag1,tag3"
storename: Elsewhere Store
employee: 3
products: 100
==========================
한 문장으로 설명해보세요:
- 스크립트는 출력을 터미널에 인쇄하지만 출력은 CLI에서 쉽게 파일로 리디렉션될 수 있습니다
$ yamlit.sh yaml_template data.csv >| yaml_out
. - yaml 템플릿에서 제공하는 형식과 구조를 엄격하게 준수합니다. 템플릿의 형식 오류는 출력에 표시됩니다. 여기에는 잘못된 공백(즉, 잘못된 들여쓰기)과 빈 줄이 포함됩니다.
- 데이터 파일의 열 순서와 관계없이 템플릿 항목이 제공되는 순서를 존중합니다
data.csv
. - 템플릿은 매우 복잡할 수 있으며 필요한 만큼 중첩 수준이 있을 수 있습니다.,
- 일치하는 템플릿 입력 키와 데이터 열 제목은 대소문자를 구분하지 않습니다.
무엇을 추가할 수 있나요?
스크립트에 추가된 기능은 간단하며 다음을 포함합니다.
- 각 데이터 파일 레코드의 "yaml" 출력을 디스크의 파일로 리디렉션하거나 파일 헤더를 제외한 데이터 레코드(행) 수만큼 다양한 출력 파일로 리디렉션합니다.
data.csv
템플릿 파일의 yaml 템플릿 항목 키 사이에 데이터 불일치가 있는지 확인하기 위해 실행합니다.
암호:
- 첫 번째 블록은 다음
FNR==NR {...}
을 계산합니다.
- 템플릿 파일의 (공백 및 비공백) 행 수,
- 배열에 저장된 각 줄의 들여쓰기는
teenof
"TEMplate ENtry OFFset"의 약어입니다. 음수 값은 템플릿 파일의 빈 줄을 나타냅니다. - 다른 배열의 템플릿 항목 키(레코드의 첫 번째 필드):
teenar
, "TEmplate ENtry ARray"의 약자.
- 두 번째 블록은 파일
FNR==1 {...}
의 각 열 헤더를data.csv
세 번째 배열에 배치합니다colhead
. - 세 번째이자 마지막 블록은
{...}
여러 가지 작업을 수행합니다.
- 템플릿 입력 키(소문자)를 반복합니다.
- 양수 또는 빈 들여쓰기의 경우:
data.csv
레코드의 필드가,
다른 필드 구분 기호를 포함하는 따옴표 붙은 문자열로 구성되어 있는지 알 수 있는 필드 정규식을 기반으로 각 파일 레코드를 분할합니다(여기) . 분할로 인한 구성요소는 네 번째 배열인 에 배치됩니다datafield
.- 템플릿 항목과 일치하는 항목을 찾기 위해 데이터 파일 열 헤더에 대해 중첩 루프를 수행합니다. 발견된 경우 yaml 템플릿에 지정된 들여쓰기 뒤에 해당 줄을 인쇄합니다.
- 음수 들여쓰기의 경우 하나 이상의 빈 줄을 포함하는 템플릿의 원래 순서를 존중하여 빈 줄을 인쇄합니다.
비판:
- 두 개의 중첩 루프가 사용되며 for조차도 계산 관점에서는 이상적이지 않습니다
awk
. 복잡도는 O(n^2)입니다. 그러나 이렇게 하면data.csv
yaml 템플릿 파일의 항목 순서에 따라 임의의 열 순서가 있는 파일을 기반으로 솔루션을 일반화할 수 있습니다. 나는 다른 해결책을 찾는 데 시간을 소비하지 않았습니다. 더 나은 것이 있는지 확실하지 않습니다awk
... - 총 4개의 GNU Awk 배열이 사용되는데, 이는 많은 열과 많은 레코드(또는 행)가 있는 파일과 같이 "매우 큰" 또는 "큰" 파일에 많은 메모리를 차지할 수 있습니다. 이는 해당 배열에서 구성됩니다(
teenof
그리고teenar
yaml 템플릿에는 대략 데이터 열만큼 많은 항목이 있으며, 4개의 배열 중 하나만(datafield
)만 비워지고 각data.csv
파일 레코드에 대해 다시 작성됩니다. 즉, I Gawk 배열이 압축되는지 확실하지 않습니다. 새 메모리로 메모리를 재할당하는 것이 C에서 배열 변수를 삭제/재선언하는 것과 얼마나 효율적인지에 대해서는 저보다 더 잘 아는 사람에게 논평을 맡기겠습니다.
HTH.
답변3
사용방법이 좀 이상하네요GoCSV, 그리고 Go의 템플릿 엔진을 활용한다는 사실도 있습니다.
귀하의 템플릿과 귀하가 제공한 샘플 데이터를 기반으로 모의 CSV를 만들었습니다.
스토리지-x.csv
Number,Name,Region,Storenum,Clients,Tags,Storename,Employee,Products
StoreX,StoreX,New York,30,Foo,store-x,Store X,Alice,X stuff
GoCSV에서 템플릿을 사용하는 것은 원시 YAML 템플릿과 거의 동일합니다.
- 필드 이름은 CSV와 일치하도록 제목 케이스에 표시됩니다.
- 필드 이름에는 선행
.
(Go/GoCSV 필드 이름 표기법)이 있습니다.
template.yaml
number: {{.Number}}
name: {{.Name}}
region: {{.Region}}
storenum: {{.Storenum}}
clients: {{.Clients}}
tags: {{.Tags}}
storename: {{.Storename}}
employee: {{.Employee}}
products: {{.Products}}
마지막으로 GoCSV 파이프라인이 포함된 짧은 셸 스크립트는 다음과 같습니다.
make_yaml.sh
#!/bin/sh
csv_data=$1
yaml_tmpl=$(cat template.yaml)
cat "$csv_data" \
| gocsv add --name 'YAML' --template "$yaml_tmpl" \ # 1.
| gocsv select -c 'YAML' \ # 2.
| gocsv behead # 3.
- 라는 파일을 추가하세요.YAML템플릿을 CSV 데이터로 채웁니다.
- 새로운 것만 선택YAML기둥
- 제목 삭제
...YAML만 남았습니다.
% sh make_yaml.sh data.csv
"number: StoreX
name: StoreX
region: New York
storenum: 30
clients: Foo
tags: store-x
storename: Store X
employee: Alice
products: X stuff"
거의