부모-자식 관계를 달성하기 위해 데이터를 반전해야 하는 시나리오가 있습니다. 내 소스 데이터는 다음과 같습니다.
Key_Col|계층
1|a, b, c, d
2|a, b, c, d, e
내 예상 결과는 다음과 같습니다.
주요 학교 | 어린이 | 부모 |
---|---|---|
1 | 디 | 씨 |
1 | 씨 | 두번째 |
1 | 두번째 | ㅏ |
1 | ㅏ | 유효하지 않은 |
2 | 이자형 | 디 |
2 | 디 | 씨 |
2 | 씨 | 두번째 |
2 | 두번째 | ㅏ |
2 | ㅏ | 유효하지 않은 |
bash 스크립트를 통해 이를 달성하는 방법을 알려주시겠습니까?
내가 사용하는 스크립트는 다음과 같습니다.
Var="1|a,b,c,d";
for i in $Var
do
Key=`echo $i |cut -d'|' -f1`
Hierarchy=`echo $i |cut -d'|' -f2`
Delim_Count=`echo ${Hierarchy} |awk -F',' '{ print NF-1 }'`
for (( c=$Delim_Count+1; c>=1; c-- ))
do
Parent=`echo ${Hierarchy} |cut -d',' -f$c`
Prev=`expr $c - 1`
if [ $Prev -ne 0 ]; then
Child=`echo ${Hierarchy} |cut -d',' -f${Prev}`
echo "${Key}|${Parent}|${Child}"
else
echo "${Key}|${Parent}|"
fi
done
done
그런데 문제는 100줄을 넘으면 스크립트를 완성하는데 오랜 시간이 걸린다는 점이다.
답변1
이런 종류의 일은 일반적으로 텍스트나 구조화된 데이터를 처리하도록 설계된 언어를 사용하면 더 쉽습니다. 표준 텍스트 처리 유틸리티를 사용한 솔루션 awk
및 사용법은 다음과 같습니다.밀러( mlr
)는 구조화된 데이터(데이터는 CSV 형식) 작업을 위해 특별히 설계된 도구입니다.
그리고 awk
:
$ cat file
Key_Col|Hierarchy
1|a,b,c,d
2|a,b,c,d,e
$ awk 'BEGIN { OFS=FS="|" } NR == 1 { print $1, "Child", "Parent"; next } { n=split($2,a,","); a[0]="null"; for (i=n;i>0;i--) print $1,a[i],a[i-1] }' file
Key_Col|Child|Parent
1|d|c
1|c|b
1|b|a
1|a|null
2|e|d
2|d|c
2|c|b
2|b|a
2|a|null
위의 코드는 각 입력 라인을 구분된 필드 awk
집합으로 읽습니다 . |
쉼표의 두 번째 필드를 배열로 분할합니다 a
. 배열의 0번째 요소는 문자열 null
(split()
첫 번째 인덱스 1을 사용하여 배열 만들기, 따라서 데이터를 덮어쓰지 않고 인덱스 0을 사용할 수 있음을 알 수 있습니다. 그런 다음 배열의 끝에서 시작까지 반복하여 첫 번째 필드의 값과 현재 배열 요소 및 배열의 이전 요소를 출력합니다. 마지막 반복에 도달하면 루프 변수의 값은 1이 되어 a[1]
및 a[0]
()가 인쇄됩니다.null
제목이 포함된 입력의 첫 번째 줄은 다르게 처리됩니다. 분할 등을 하는 대신 코드는 입력의 첫 번째 필드와 Child
문자열 및 Parent
. 조건부 NR==1
블록이 이를 수행합니다.
가독성을 위해 코드 형식이 변경되었습니다 awk
.
BEGIN {
OFS = FS = "|"
}
NR == 1 {
print $1, "Child", "Parent"
next
}
{
n = split($2, a, ",")
a[0] = "null"
for (i = n; i > 0; i--)
print $1, a[i], a[i-1]
}
입력은 CSV처럼 보이므로 CSV 인식 도구를 사용하여 처리하는 것이 더 안전할 것입니다. Miller( mlr
)는 다음과 같은 도구입니다.
$ mlr --csv --fs pipe put -q 'm=splitnv($Hierarchy,","); m[0]="null"; for (var i=length(m)-1;i>0;i-=1) { emit {"Key_Col": $Key_Col, "Child": m[i], "Parent": m[i-1] } }' file
Key_Col|Child|Parent
1|d|c
1|c|b
1|b|a
1|a|null
2|e|d
2|d|c
2|c|b
2|b|a
2|a|null
Miller put
표현식은 위의 코드와 동일한 개요를 따르지만 awk
Miller는 이러한 헤더를 읽고 사용하는 방법을 알고 있으므로 헤더를 특별한 경우로 처리할 필요가 없습니다.
m = splitnv($Hierarchy, ",")
m[0] = "null"
for (var i = length(m) - 1; i > 0; i -= 1) {
emit {
"Key_Col": $Key_Col,
"Child": m[i],
"Parent": m[i-1]
}
}
put
Miller를 사용하면 하위 명령 앞의 옵션을 조정하여 다양한 형태의 결과를 생성할 수 있습니다.
예쁘게 인쇄된 "금지된" 출력:
$ mlr --c2p --barred --ifs pipe put ...as above...
+---------+-------+--------+
| Key_Col | Child | Parent |
+---------+-------+--------+
| 1 | d | c |
| 1 | c | b |
| 1 | b | a |
| 1 | a | null |
| 2 | e | d |
| 2 | d | c |
| 2 | c | b |
| 2 | b | a |
| 2 | a | null |
+---------+-------+--------+
JSON:
$ mlr --c2j --ifs pipe put ...as above...
{ "Key_Col": 1, "Child": "d", "Parent": "c" }
{ "Key_Col": 1, "Child": "c", "Parent": "b" }
{ "Key_Col": 1, "Child": "b", "Parent": "a" }
{ "Key_Col": 1, "Child": "a", "Parent": "null" }
{ "Key_Col": 2, "Child": "e", "Parent": "d" }
{ "Key_Col": 2, "Child": "d", "Parent": "c" }
{ "Key_Col": 2, "Child": "c", "Parent": "b" }
{ "Key_Col": 2, "Child": "b", "Parent": "a" }
{ "Key_Col": 2, "Child": "a", "Parent": "null" }
(등.)
답변2
사용행복하다(이전 Perl_6)
~$ raku -e 'my @a; for lines() {.split("|") andthen @a.push: .[0] => ("null".Slip, .[1].split(",").Slip) }; \
put .invert.invert.join("\n") for [Z=>] @a.map(*.key), @a.map(*.value.rotor(2 => -1) );' file
위 내용은 Perl 계열의 프로그래밍 언어인 Raku로 작성된 답변입니다. 즉, 첫 번째 문에서 배열이 선언됩니다. 두 번째 명령문에서는 lines()
먼저 bar에 키를 가져온 다음 값을 쉼표로 나누어 "해시 배열"을 읽습니다. A는 각 값 계열의 시작 부분에 추가됩니다. Raku는 (고맙게도) 배열 요소를 자동으로 평면화하지 않기 때문에 를 호출하여 평면화를 수행합니다. 마지막 문에서는 "빈"으로 채워진 값이 겹쳐서 채워지고 각 행의 키와 값이 쌍으로 압축됩니다. 키를 연관된 각 값으로 확장하기 위해 두 번 편집한 다음 또는로 인쇄합니다.split
|
,
"null"
.Slip
rotor
[Z=>]
invert
say
put
입력 예:
1|a,b,c,d
2|a,b,c,d,e
3|a,b,c
출력 예(비역전 열):
1 null a
1 a b
1 b c
1 c d
2 null a
2 a b
2 b c
2 c d
2 d e
3 null a
3 a b
3 b c
.skip
헤더 행을 처리해야 하는 경우 간단한 방법은 after 호출 lines
(기본적으로 행 건너뛰기)을 추가한 다음 선택한 헤더를 다시 추가하는 것입니다 say()
.
마지막으로 OP의 요청은 실제로 열의 순서를 바꾸는 것인데, 이는 마지막 문에 호출을 삽입하고 .reverse
as에 대한 최종 준비를 작성하여 수행할 수 있습니다.@a.map(*.value.rotor(2 => -1)
@a.map(*.value.reverse.rotor(2 => -1)
출력 예(역방향 열):
1 d c
1 c b
1 b a
1 a null
2 e d
2 d c
2 c b
2 b a
2 a null
3 c b
3 b a
3 a null