내 입력 파일은 다음과 같습니다
$ 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=value
value
$var
-s
BEGIN{$, = ","}
BEGIN{OFS = ","}
-v OFS=,
-C
로케일이 문자 맵으로 UTF-8을 사용하는 경우 입력은 UTF-8 인코딩으로 처리되며, 요즘에는 거의 사용되지 않는 다양한 멀티바이트 문자 맵이 있는 로케일은 무시됩니다.
발견한 대로 잘라내려는 공백 문자가 모두 ASCII 문자인 경우 후행 ASCII 공백(및 NUL)을 제거하는 A
대신 지정자를 사용하여 더 단순화 할 수 있습니다.a
unpack()
<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:]]}
모드를 기준으로 앞부분까지 확장됩니다. 공백(동일할 필요는 없음).$field
M
${field%%[[:space:]]#}
set -o extendedglob
아마도 에 비해 속도가 훨씬 느려질 것입니다 perl
.
파일에 ASCII 텍스트만 포함되어 있는 경우(예제와 같이) 모두 동일해야 합니다. 그런 다음 -C
for를 제거 하거나 perl
로케일을 C
/ 로 설정 POSIX
하면 sed
성능이 향상될 수 있습니다.gawk
perl
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의 경우 파이프라인은 서로 동시에 실행될 가능성이 매우 높습니다 . 따라서 성능 관점에서 볼 때 (합리적으로) 가능한 한 작업을 파이프라인으로 분할하는 것이 실제로 더 좋습니다.
따라서 perl
Stephane 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% 더 느립니다.sed
ruby
가장 빠른 속도로(즉, 멀티바이트 문자는 지원되지 않음) 구성.
업데이트된 요구 사항에 더 현실적입니다. 위의 두 번째 파이프라인은 각각 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 솔루션), 기본적으로 또는 프로그래밍 방식으로 멀티 코어 지원을 제공할 수 없다면 파이프라인은예고려해 볼 가치가 있는"빠르고 편리하다"성능상의 이점,적어도언제:
- 여러 작업에서 액션 블록을 식별할 수 있으며, 각 액션 블록에 대해 보다 전문화된 도구를 선택할 수 있습니다.
- 사용 가능한 CPU 코어/스레드 수를 초과하지 않으면서 최대한 많은 파이프라인 암으로 분할할 수 있습니다.
gawk
포인트 1의 예로서, 저는 파이프라인의 첫 번째 부분 에 귀하의 것을 사용했습니다. 그 이유는 sed
작업의 특정 부분을 훨씬 더 빠르게 수행하기 때문이고, gawk
파이프라인의 두 번째 부분에서는 성능이 30M 행만큼 떨어지기 때문입니다. 샘플 데이터의 처리 시간은 20초에서 35초 정도 걸립니다. 또한 업데이트 요구 사항에 대한 파이프라인에서는 이를 gawk
두 개의 하위 작업 으로 분할하지 않았습니다 . 왜냐하면 이를 그대로 유지하는 것이 고정 너비 필드를 얻기 위해 제가 생각할 수 있는 가장 간단하고 효율적인 방법이기 때문입니다.하지만입력 데이터에 있을 수 있는 큰따옴표를 이스케이프합니다.
포인트 2의 예로, sed
두 개의 s
명령을 실행하는 마지막 명령에 주목하세요. 내 컴퓨터에서 사용 가능한 CPU 코어 수와 동일시되는 것을 피하기 위해 더 이상 분할하지 않았습니다. 더 분할하면 성능은 동일하며 추가 개선은 없습니다.
그러나 데이터를 입력하는 데 필요한 요구 사항이 많을수록 파이프라인이 더 복잡해지고, 어색해지고, 오류가 발생하기 쉬워지고, 잠재적으로 단일 도구 솔루션보다 더 복잡해진다는 데 의견이 일치합니다.
1. 각 파이프라인의 암이 별도의 CPU 코어에 의해 실행되는지 확인하는 명령이 있지만 최신 커널에서는 코어 재할당이 자동으로 발생하므로 이는 일반적으로 필요하지 않습니다.