Ubuntu에서 Bash를 사용하고 있으며 질문은 다음과 같습니다.
헤더와 구분 기호가 포함된 큰 텍스트 파일이 있습니다 #|#
.
이 파일에 대한 정보를 얻기 위해 AWK를 사용하려고 합니다. 이제 다음 표현식을 사용하여 열 1 값으로 그룹화된 열 2의 합계를 계산하려고 합니다.
awk 'BEGIN { FS="\\#\\|\\#" }{arr[$1]+=$2} END {for (i in arr) {print i,arr[i]}}' myfile.txt
내가 얻은 출력에는 두 가지 문제가 있습니다.
첫째, 열 1이 두 개의 고유 값 value1과 value2를 사용한다고 가정하면 AWK는 2개가 아닌 3개의 그룹(value1, value2 및 name_column1)을 형성합니다.
파일의 첫 번째 줄이 헤더라는 것을 이해하지 못하는 것 같습니다 ...
두 번째 문제는 내 출력이 다음과 같다는 것입니다.
value1 0 value2 0 name_column1 0
따라서 우리는 출력의 마지막 줄이 예상치 못한 것이라는 것을 알고 있습니다(앞서 언급했듯이). 처음 두 줄에 집중하겠습니다. 여기서는 두 합계가 모두 비어 있지만 그 중 적어도 하나는 엄격하게 0보다 커야 한다는 것을 알고 있습니다.
awk 'BEGIN { FS="\\#\\|\\#" }{sum1+=2;}END{print sum1;}' myfile.txt
나에게주세요
251597850
.
따라서 마지막 명령(일반 합계)에 문제가 있거나 이전 명령(합계 + 그룹 기준)에 문제가 있습니다.
이 문제를 해결하는 방법을 아는 사람이 있나요?
편집: 내 파일 텍스트는 다음과 같습니다.
Column1#|#Column2#|#Column3
0300#|#0.00#|#0000
여기서 0300은 value1
앞서 언급한 것입니다(숫자가 아니라 카테고리임).
편집 2:
awk 'BEGIN { FS="\\#\\|\\#" }{sum1+=2;}END{print sum1;}' myfile.txt
나에게 2*(파일의 줄 수)를 제공하는데 이는 분명히 내가 원하는 것이 아니므로 명령은 다음과 같아야 합니다.
awk 'BEGIN { FS="\\#\\|\\#" }{sum1+=$2;}END{print sum1;}' myfile.txt
편집 3:
구분 기호로 인해 내 명령이 모두 잘못된 것으로 나타났습니다. 따라서 그룹화하는 올바른 명령은 다음과 같습니다.
awk 'BEGIN { FS="#[|]#" } FNR>1 {arr[$1]+=$2} END { for (i in arr) print i,arr[i] }' file.txt
답변1
간단히 대답하자면 이 경우 FS 변수는 RE(정규식 또는 패턴)입니다. 따라서 실제 데이터 문자가 RE 컨텍스트에서 "특수"인 경우 RE에서 이스케이프하여 연산자가 아닌 자체로 처리되도록 해야 합니다.
이 경우 범인은 |
교대 연산자입니다. 양쪽에 있는 항목은 대체 RE이며, 그 중 어느 하나라도 일치하는 것으로 간주됩니다. 예를 들어, 필드 구분 기호는 a|u|o|i|e
각 모음에서 필드를 분할합니다.
따라서 RE는 #|#
다소 중복됩니다. 필드 구분 기호로 두 번 지정되고 #
반복이 무시됩니다.
해결책은 이스케이프하는 것입니다 |
. 제가 선호하는 방법은 그 자체를 나타내기 위해 다운그레이드되는 |
대괄호 표현식(문자 클래스)으로 변환하는 것입니다 .[|]
|
또는 이스케이프 문자를 전달하여 구분 기호 \
를 #\\|#
.
탈출은 \
왜 두 번이나 썼나요? 이것은 또 다른 이상한 규칙입니다(백슬래시가 종종 awk 모드에서 문제를 일으키는 이유이기도 합니다).
awk RE를 작성하는 방법에는 와 같은 패턴으로 작성 /myRE/
하거나 와 같은 문자열로 작성 하는 두 가지 방법이 있습니다 "myRE"
.
이 /myRE/
형식은 (기본적으로) 부울로 작동하며 pattern { action }
awk 소스 모델이나 { if (/myRE/) ...}
. 또한 필드나 변수와 같은 보다 구체적인 대상과 $6 ~ /myRE/
일치시킬 수도 있습니다 myVar ~ /myRE/
. 이 형식에서는 문자가 개별적으로 이스케이프됩니다 \
.
그러나 RE가 문자열로 작성되면 awk는 나중에 RE로 호출될 수 있다는 사실을 모릅니다. 파싱됨두 배: 원래 소스 코드에서 먼저 일반적인 문자열 이스케이프(예: \t
탭, \n
줄 바꿈 및 \\
백슬래시) 를 수행합니다. 그런 다음 ~
연산자 또는 match()
or 함수와 함께 split()
사용될 때 다시 수행합니다 .
FS 문은 문자열로 처리되므로 모든 백슬래시를 두 배로 늘려야 합니다. 이는 명령줄에서 FS를 사용하거나 선언하든지 -F
, 또는 그와 같이 FS를 선언하든 마찬가지입니다.-v FS=
BEGIN { FS = "myRE" }
나는 "짧은 대답"을 언급했는데, 이와 같은 것은 거의 항상 잘못된 것입니다. 예외가 있고, 그 예외에도 예외가 있습니다.
특수 연산자에는 작동할 항목이 필요하기 때문에 단일 문자 정규식을 작성하는 것은 어렵습니다. 따라서 FS의 모든 단일 문자 값은 문자 그대로 처리됩니다. '-F|'
또는 필드를 파이프 기호로 구분하여 작성할 수 있습니다 -v 'FS=|'
.BEGIN { FS = "|" }
단일 문자 규칙의 예외는 단일 공백으로 구성된 FS입니다(기본값). 이것은 줄의 각 단어를 필드로 변환합니다. awk와 마찬가지로 단순은 비교 용어입니다.
(1) 구분 기호는 ASCII 공백, 가로 탭 및 개행 문자가 연속적으로 혼합된 시퀀스로 정의되는 "공백"입니다. (대체 레코드 구분 기호가 유효한 경우에만 개행 문자가 표시됩니다.)
(2) 행 전체의 선행 및 후행 공백은 필드 구분자가 아닙니다. (다른 FS가 줄의 시작이나 끝에 있으면 각각 그 앞이나 뒤에 암시적인 추가 공백 필드가 있습니다.)
제가 참조할 곳은GNU/awk 온라인 매뉴얼.
답변 자체는 엄청나게 길고 복잡하지만 매뉴얼에서는 섹션 3, 정규 표현식에 약 600줄을 할당하고 섹션 4.5, 필드 구분 방법 지정에 또 다른 250줄을 할당합니다.