고정 너비 파일을 CSV로 변환하고 후행 공백을 제거합니다.

고정 너비 파일을 CSV로 변환하고 후행 공백을 제거합니다.

내 입력 파일은 다음과 같습니다

$ cat -e myfile.txt 
999a bcd efgh555$
 8 z         7  $
1  xx xx xx  48 $

열에 후행 공백이 없는 CSV가 필요합니다.

999,a bcd efgh,555
 8,z,7
1,xx xx xx,48

지금까지 필요한 곳에 혼수상태를 성공적으로 추가했습니다.

$ gawk '$1=$1' FIELDWIDTHS="3 10 3" OFS=, myfile.txt
999,a bcd efgh,555
 8 ,z         ,7  
1  ,xx xx xx  ,48 

후행 공백을 어떻게 제거할 수 있나요?

편집하다: 데이터에 이미 쉼표가 있을 수 있으므로 다음을 수행해야 합니다. (i) 필드를 큰따옴표로 묶습니다. (ii) 다음을 사용합니다 \"(또는 ""다음을 따릅니다.RFC 4180). 예를 들어 a,aab"bbccc-> "a,aa","b\"bb","ccc".

  • gawk(뿐만 아니라 awk)을 사용할 수 있습니다 .
  • 다른 솔루션(예: )도 열려 있습니다 perl.
  • gawk ... | sed ...처리할 대용량 파일이 많기 때문에 효율적인 솔루션(예: not )이 필요합니다 .
  • 필드 너비를 알고 있으므로 FIELDWIDTHS자동 계산이 필요하지 않습니다.

답변1

그리고 perl:

<your-file perl -C -lnse 'print map {s/\s+$//r} unpack "a3a10a3"' -- -,=,

unpack()gawk 와 동일한 처리를 수행합니다 FIELDWIDTHS.

$,, 여기에서 awk에 해당하는 것은 with 로 OFS설정되어 매개변수가 에 할당된 것으로 이해 됩니다 . 또는 awk에서와 같이 이를 생략 하고 처음에 명령문을 추가 할 수 있습니다 .,-,=,-s-var=valuevalue$var-sBEGIN{$, = ","}BEGIN{OFS = ","}-v OFS=,

-C로케일이 문자 맵으로 UTF-8을 사용하는 경우 입력은 UTF-8 인코딩으로 처리되며, 요즘에는 거의 사용되지 않는 다양한 멀티바이트 문자 맵이 있는 로케일은 무시됩니다.

발견한 대로 잘라내려는 공백 문자가 모두 ASCII 문자인 경우 후행 ASCII 공백(및 NUL)을 제거하는 A대신 지정자를 사용하여 더 단순화 할 수 있습니다.aunpack()

<your-file perl -C -lnse 'print unpack "A3A10A3"' -- -,=,

그건너비문자 수를 생각해보세요.

바이트 수는 제거하십시오 -C.

자소 클러스터 수는 unpack "a3a10a3"로 대체할 수 있습니다 /^(\X{3})(\X{10})(\X{3})/.

표시 너비의 경우 각 문자의 너비(너비 0, 단일 너비 및 이중 너비 문자 포함, TAB1, CR... 등과 같은 제어 문자는 지원하지 않음)를 고려하여 zsh다음을 수행할 수 있습니다. :

widths=(3 10 3)
while IFS= read -ru3 line; do
  csv=()
  for width in $widths; do
    field=${(mr[width])line}
    line=${line#$field}
    csv+=("${(M)field##*[^[:space:]]}")
  done
  print -r -- ${(j[,])csv}
done 3< your-file

라이트 패드의 경우 r[width] r텍스트를 주어진 너비로 자르는 경우 m이는 문자 수보다는 표시 너비를 기준으로 수행되며 ${(M)field##*[^[:space:]]}모드를 기준으로 앞부분까지 확장됩니다. 공백(동일할 필요는 없음).$fieldM${field%%[[:space:]]#}set -o extendedglob

아마도 에 비해 속도가 훨씬 느려질 것입니다 perl.

파일에 ASCII 텍스트만 포함되어 있는 경우(예제와 같이) 모두 동일해야 합니다. 그런 다음 -Cfor를 제거 하거나 perl로케일을 C/ 로 설정 POSIX하면 sed성능이 향상될 수 있습니다.gawkperl

UTF-8 로케일에서 입력이 100000번 반복되었습니다. 여기서는 1.1초 perl(변형 0.34 A, 변형 1.7 \X), Paul의 1.3초 gawk, zsh 31초, GNU sed 's/./&,/13;s/./&,/3;s/[[:space:]]*,/,/g;s/[[:space:]]*$//'(표준) 2.1초, 1.1은 sed -E 's/^(.{3})(.{10})/\1,\2,/;s/\s+,/,/g;s/\s+$//'(비표준)입니다.

C 언어 환경에서는 각각 0.9(0.27, 1.2), 0.7, 31, 1.3, 0.5가 됩니다.


이러한 필드에는 ,또는 "문자가 포함되어 있지 않다고 가정합니다. 일부 CSV 형식에는 선행 또는 후행 공백이 있는 인용 필드도 필요합니다.

올바른 CSV 출력을 생성하려면 가장 쉬운 방법은 Text::CSV다음 모듈을 사용하는 것입니다 perl.

<your-file perl -C -MText::CSV -lne '
  BEGIN{$csv = Text::CSV->new({binary => 1})}
  $csv->print(*STDOUT, [unpack "A3A10A3"])'

기본적으로,

  • 구분 기호는 다음과 같습니다.,
  • 따옴표는"..."
  • """따옴표로 탈출
  • 참조가 필요한 필드만 참조

그러나 이는 new()즉석에서 조정될 수 있습니다. perldoc Text::CSV자세히보다.


1 특히 TAB의 경우 입력을 전처리하여 expand이러한 TAB을 다른 항목의 공백 시퀀스로 변환할 수 있지만 이 개념은 다음과 같습니다.너비적용하기가 어렵고 텍스트가 전송되는 디스플레이 장치에 따라 달라지는 경우가 많습니다.

답변2

$ cat txx
9  a bcd     55 # <- 1 trailing space here
48 z         7  # <- 2 trailing spaces here
1  xx xx xx  489
aaabbb   bb bccchh

$ awk 'BEGIN { FIELDWIDTHS="3 10 3"; OFS=","; }
{ for (f = 1; f <= NF; ++f) sub (/[[:space:]]*$/, "", $f); print; }' txx
9,a bcd,55
48,z,7
1,xx xx xx,489
aaa,bbb   bb b,ccc

답변3

문자열 조작 및 정규 표현식을 사용하여 항목 끝 부분에 있는 하나 이상의 공백을 일치시킬 수 있습니다. 그러나 이는 모든 필드에 대해 반복을 의미합니다.

이렇게 하려면 먼저 FIELDWIDTHS다음을 통해 분할할 레코드를 트리거해야 합니다.$1=$1

awk 'BEGIN { FIELDWIDTHS="3 10 3" ; OFS=","}
     {$1=$1 ;
     for (i=1;i<NF;i++) {$i=gensub(/ +$/,"","g",$i)}}
     1' infile

네펠레

9  abc d     55 
48 z         7  
1  x x x   xx489

산출

9,abc d,55 
48,z,7  
1,x x x   xx,489

답변4

  • 처리할 대용량 파일이 많기 때문에 효율적인 솔루션(예: gawk... | sed...가 아님)이 필요합니다.

작동하는 솔루션의 경우 실제로 다음을 수행하는 것이 더 좋습니다.생각하다관로.

실제로 파이프라인에 있는 2개 이상의 명령에는 고유한 설정 비용과 파이프라인을 통해 흐르는 데이터의 지속적인 오버헤드가 있지만 실제로는 멀티 스레드 멀티 코어 CPU의 경우 파이프라인은 서로 동시에 실행될 가능성이 매우 높습니다 . 따라서 성능 관점에서 볼 때 (합리적으로) 가능한 한 작업을 파이프라인으로 분할하는 것이 실제로 더 좋습니다.

따라서 perlStephane Chazelas의 솔루션만큼 깔끔하지는 않지만 컴팩트합니다(그리고 합리적으로 빠릅니다).원래더 간단한 요구 사항) 효율성을 높이려면 다음 사항도 고려할 수 있습니다.

(귀하의 원래 요청에 대한 응답으로)

<input-data gawk '{$1=$1; print}' FIELDWIDTHS="3 10 3" OFS=, | gsed 's/ *,/,/g' | gsed 's/ \+$//'

(큰따옴표 필드 및 포함된 큰따옴표 이스케이프에 대한 추가 요구 사항)

<input-data gawk '/"/{for(i=1;i<=NF;i++) gsub(/"/, "\"\"", $i)} {$1=$1; print}' FIELDWIDTHS="3 10 3" OFS='","' | gsed 's/ *","/","/g' | gsed 's/ *$/"/;s/^/"/'

여기서는 작업을 파이프라인의 각 분기마다 하나씩 3개의 작업 블록으로 분할했으며, 아마도 작업 블록은 별도의 CPU 스레드/코어에서 실행될 것입니다.

내 쿼드 코어 시스템에서 첫 번째 파이프라인(원래 요구 사항)은 /dev/null입력 샘플(예: 300,000개 행)의 100,000배를 씹는 데 항상 0.21초(로 리디렉션)가 걸리고, 10Mx 샘플(30M 라인)을 소화하는 데 동일한 20초가 걸립니다. 비교를 위해 Stephane은 perl -C -lnse 'print unpack "A3A10A3"' -- -,=,동일한 시스템에서 각각 0.31초와 31초가 걸립니다. 즉, 멀티바이트 지원의 경우 모든 것이 동일할 때 약 50% 느린 반면, 비 perl버전 -C(멀티바이트 문자를 지원하지 않음)은 항상 0.23초와 23초가 걸립니다. 이는 위의 첫 번째 파이프라인보다 여전히 10% 느립니다. 다시 말하지만, // - gawk유일한 솔루션은 100%에서 200% 더 느립니다.sedruby가장 빠른 속도로(즉, 멀티바이트 문자는 지원되지 않음) 구성.

업데이트된 요구 사항에 더 현실적입니다. 위의 두 번째 파이프라인은 각각 0.26초와 25초가 걸리는 반면 Text perl::CSV(Text::CSV_XS로 가속화됨) 솔루션은 각각 0.76초와 1분 17초가 걸립니다.

C위의 예에서 GNU 도구(BSD 도구는 경우가 다를 수 있음)를 사용하면 추가 속도를 얻기 위해 로케일을 설정할 필요조차 없다는 것을 알 수 있습니다 . 대신, 나는 가지치기가 필요하다는 사실을 이용했습니다.공간단, 다른 유형의 공백 문자는 사용되지 않으므로 단순하게 사용됩니다 s/ */. 분명히 C로케일을 올바르게 설정하지 않으면 데이터에서 가능한 멀티바이트 문자가 지원됩니다. 문자 세트 s/[[:space:]]*/(또는 이와 유사한 문자 세트)를 사용할 s/\s*/때 우수한 성능을 유지하려면 관련된 모든 공백 유사 문자를 에 명시적으로 지정하는 것이 좋습니다 .s/ */C[]

아마도 단일 도구 솔루션을 더 압축하여 추가 속도를 얻을 수 있지만(특히 perl확실히 최적화할 수 있는 Text ::CSV 솔루션), 기본적으로 또는 프로그래밍 방식으로 멀티 코어 지원을 제공할 수 없다면 파이프라인은고려해 볼 가치가 있는"빠르고 편리하다"성능상의 이점,적어도언제:

  1. 여러 작업에서 액션 블록을 식별할 수 있으며, 각 액션 블록에 대해 보다 전문화된 도구를 선택할 수 있습니다.
  2. 사용 가능한 CPU 코어/스레드 수를 초과하지 않으면서 최대한 많은 파이프라인 암으로 분할할 수 있습니다.

gawk포인트 1의 예로서, 저는 파이프라인의 첫 번째 부분 에 귀하의 것을 사용했습니다. 그 이유는 sed작업의 특정 부분을 훨씬 더 빠르게 수행하기 때문이고, gawk파이프라인의 두 번째 부분에서는 성능이 30M 행만큼 떨어지기 때문입니다. 샘플 데이터의 처리 시간은 20초에서 35초 정도 걸립니다. 또한 업데이트 요구 사항에 대한 파이프라인에서는 이를 gawk두 개의 하위 작업 으로 분할하지 않았습니다 . 왜냐하면 이를 그대로 유지하는 것이 고정 너비 필드를 얻기 위해 제가 생각할 수 있는 가장 간단하고 효율적인 방법이기 때문입니다.하지만입력 데이터에 있을 수 있는 큰따옴표를 이스케이프합니다.

포인트 2의 예로, sed두 개의 s명령을 실행하는 마지막 명령에 주목하세요. 내 컴퓨터에서 사용 가능한 CPU 코어 수와 동일시되는 것을 피하기 위해 더 이상 분할하지 않았습니다. 더 분할하면 성능은 동일하며 추가 개선은 없습니다.

그러나 데이터를 입력하는 데 필요한 요구 사항이 많을수록 파이프라인이 더 복잡해지고, 어색해지고, 오류가 발생하기 쉬워지고, 잠재적으로 단일 도구 솔루션보다 더 복잡해진다는 데 의견이 일치합니다.


1. 각 파이프라인의 암이 별도의 CPU 코어에 의해 실행되는지 확인하는 명령이 있지만 최신 커널에서는 코어 재할당이 자동으로 발생하므로 이는 일반적으로 필요하지 않습니다.

관련 정보