sed 표현식이 포함된 셸 파이프라인이 있습니다.
source | sed -e 's:xxxxx:yyyyy:g' | sink
작동하지만 sed
전체 행에 적용된다는 점에서 잠재적인 결함이 있습니다. 이는 sink
개행 문자가 전송될 때까지 아무 것도 볼 수 없음을 의미합니다. source
소스가 줄바꿈을 보내지 않으면 문제가 발생합니다.
쉘은 bash
이지만 관련이 없기를 바랍니다. 문자열 합계는 정규식일 xxxxx
수 있으며 캡처 그룹을 사용하여 일부 콘텐츠 를 .yyyyy
x
y
sed
개행 문자가 포함되지 않은 입력 스트림에 정규식 변환을 사용하거나 적용하는 것이 가능합니까 ?
Ruby에서 필터를 작성하여 이 문제를 해결했는데, 코드를 작성하는 대신 기존 도구를 사용할 수 있는지 궁금했습니다.
답변1
사실 생각해보면 우선적으로 영향을 줄 수 있는 것은모두가능한:
source |
tr '\n<' '<\n' |
paste -sd\\n - -|
sed -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d'\
-e\} -e'x;y/\n</<\n/;s//<&/' \
-ew\ /dev/fd/1 |
filter ... | sink
이렇게 하면 먼저 인스트림 콘텐츠가 중단됩니다.연합 국가<
조건부로 for \n
와 after 모두 교체가정 어구적절하게 다시 교환하세요. 언급한 구분 기호가 다음과 같기 때문에 이는 필요합니다.아니요단일 문자(개행 문자로)따라서 간단한 번역만으로는 편집 내용을 보장하기에 충분하지 않습니다.첫 번째흐름을 정의하세요. 즉, 언급한 편집 내용이 필요할 수 있습니다. 예를 들어 적용되는 것으로 이해되는 그룹 캡처 및 기타 상황에 맞는 일치 항목이 있습니다.기록- 엔드포인트를 확인하기 전까지는 안정적으로 완료할 수 없습니다.
버퍼링되지 않은
sed
정규식 일치 입력이 처음으로 나타나는 경우에만 버퍼링<[0-9]+>
먼저 모두 번역하세요<
ewlines 로 \n
또는 그 반대로 한 다음 입력을 이전 공간에 한 줄씩 쌓습니다 sed
.H
^[0-9]\{1,\}>
일치합니다.
그러나 청크 버퍼 출력은 4kb 청크 이상의 파이프에 쓸 때 수행됩니다 tr
.paste
이 문제를 처리하는 두 가지 버전도 있습니다.
sol1(){
{ cat; printf '\n\4\n'; } |
{ dd obs=1 cbs=512 conv=sync,unblock \
<"$(pts;stty eol \";cat <&3 >&4&)"
} 3<&0 <&- <>/dev/ptmx 2>/dev/null 4>&0 |
sed -ne'/<[0-9]\{1,\}>/!{H;$!d' -e\} \
-e'x;s/\n//g;w /dev/fd/1'
}
그러면 모든 입력이 pty로 푸시되고 dd
그로부터 읽기가 설정됩니다. fd를 pts
잠금 해제하고 할당하기 위해 다른 질문의 작은 C 프로그램을 사용합니다 dd
. 위의 경우 구분은 커널에 의해 수행됩니다. pty 라인 규칙은 다음 "
과 같이 구성됩니다.stty
eol
char - 출력에서 제거되지 않습니다.eof
char은 그렇습니다. 그러나 pty 버퍼를 dd
각 발생에 푸시하고 이를 만족시킵니다.read()
. 모든dd
read()
에스먼저 출력 버퍼의 끝을 512자의 공백으로 채운 다음 모든 후행 공백을 단일 개행 문자로 압축합니다.
다음은 마지막 줄이 차단되는 문제를 해결하는 수정된 버전입니다.
sol1_5(){
{ cat; printf '\n\4\n'; } |
{ dd ibs=16k obs=2 cbs=4k conv=sync,unblock <"$(pts
stty raw isig quit \^- susp \^- min 1 time 2
cat <&3 >&4&)"
} 3<&0 <&- <>/dev/ptmx 2>/dev/null 4>&0 |
sed -ne's/<[0-9]\{1,\}>/\n&/g;/./w /dev/fd/1'
}
tr
버퍼링을 해제 하고 완전히 다른 또 다른 버전은 다음과 같습니다 paste
.
sol2(){
stdbuf -o0 tr '\n<' '<\n' |
stdbuf -o0 paste -sd\\n - -|
sed -ue'/^[0-9]\{1,\}>/!{$!H;1h;$!d'\
-e\} -e'x;y/\n</<\n/;s//<&/'
}
샘플 데이터를 사용하여 두 가지 방법을 모두 테스트했습니다.
for sol in 1 2
do printf '<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"' |
cat - /dev/tty | "sol$sol" | cat
두 경우 모두 처음 세 줄은 즉시 인쇄되지만 네 번째 줄은 버퍼에 유지됩니다. sed
버퍼는 다음 줄의 시작 부분을 찾을 때까지 인쇄되지 않으므로 EOF까지 입력 뒤에 한 줄을 유지합니다. 긴급한CTRL+D
인쇄되었습니다.
<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"
<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"
<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"
<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"
그러나 sol1_5
완전히 다른 접근 방식을 사용합니다. 입력을 분리하기 위해 문자 컨텍스트에 의존하는 대신 각 write()
4k 이하 바이트가 최소한 1개의 완전한 컨텍스트를 나타내야 한다고 생각하므로 적절한 개행이라고 생각하는 것을 각 바이트에 추가하고 출력을 즉시 플러시합니다.
작동 방식은 설정하는 것입니다.stty
min
그리고time
dd
pty의 값입니다. 설정하면min > 0
그리고 time > 0
비표준 터미널 장치에서 터미널은 최소한 수신될 때까지 읽기를 차단합니다.min
문자를 입력한 후 다음까지 계속 차단합니다.time
10분의 1초가 지났습니다. 그렇게 해서 모든 것을 의지할 수 있다면write()
터미널에는 바이트가 너무 많고 완료하는 데 너무 많은 시간이 걸립니다. 개인적으로 로그 쓰기에 4k와 0.2초가 상당히 공정한 가정이라고 생각합니다. 그런 다음 입력을 읽고 출력을 플러시할 수 있습니다.동기적으로.
따라서 sol1_5
4줄이 모두 즉시 인쇄됩니다.
sed 스크립트
이는 실제로 매우 간단한 방법이며 여러 문자 구분 기호 sed
(기본적으로 단일 문자의 레코드만 구분함) 개행을 처리하기 위해 상당히 일반적으로 조정될 수 있습니다.
구분 기호 패턴에서 처음 나타나는 모든 문자를 줄 바꿈 문자로 변환하고, 모든 줄 바꿈 문자를 해당 문자로 변환합니다.
아래에 언급된 복잡성의 일부: 스트림 끝에 개행 문자가 있는지 확인하세요.
tr '\n<' '<\n' | paste -sd\\n - -
구분 기호 패턴의 나머지 부분에 대해 새 줄 바꿈으로 구분된 입력을 검색합니다. 단, 줄 시작 부분에 발생하는 경우에만 해당됩니다.
이는 간단할 뿐만 아니라 매우 효율적입니다. 입력 줄의 처음 몇 문자만 확인하면 됩니다.
sed
작업이 거의 또는 전혀 필요하지 않습니다./^[0-9]\{1,\}>/
H
일치하지 않는 행의 복사본을 이전 공간에 추가!
하고d
삭제합니다. 그러나 일치하지 않는 행의 경우x
편집 및 보존 버퍼를 변경하여 현재 패턴 공간이 완전히 구분된 마지막 레코드의 모든 내용이 되도록 합니다. 예약된 공간에는 구분된 첫 번째 시퀀스의 일부만 포함됩니다.가장 복잡한 부분은 첫 번째 입력 라인과 마지막 입력 라인에 주의를 기울여야 한다는 것입니다. 여기서의 복잡성은
sed
기본 효율성에서 비롯됩니다. 실제로 버퍼당 하나의 레코드를 처리할 수 있습니다.아무 이유 없이 첫 번째 행에 추가 행을 삽입 하고 싶지 않으므로
\n
이 경우h
이전 공간을 추가하는 대신 덮어써야 합니다.H
여러분 모두는
!
아니요H
비어 있거나 보유 버퍼가 없기 때문에 마지막 줄을 삭제 하거나d
삭제하십시오 .$
더 이상 스캔할 입력이 없지만 마지막으로 저장된 기록을 처리해야 합니다./.../!{$!H;1h;$!d;};x
s///
이제 완전히 구분된 컨텍스트를 복원하기 위해 비용이 많이 드는 대체 정규식을 적용하는 대신 자체 음역 기능을 사용하여 저장된 모든 삽입된 ewline 문자를 구분 기호의 첫 번째 문자로 한 번에 더 효율적으로 바꿀 수 있습니다 .sed
y///
\n
y/\n</<\n/
<
마지막으로, 패턴 공간의 선두에 새 줄을 삽입하기만 하면 됩니다.\n
삽입해야 하는 줄줄이는 인쇄할 때 마지막 버퍼 주기 끝에 이미 추가되었기 때문입니다.이를 수행하는 가장 효율적인 방법은
//
입력 행에서 테스트한 것과 동일한 정규식을 재사용하는 것입니다. 이렇게 하면sed
그냥 해야 하는 일에서 벗어날 수 있습니다.regcomp()
단일 정규식을 컴파일하고 반복regexec()
전체 흐름을 안정적으로 묘사하기 위해 동일한 자동 장치가 반복적으로 실행됩니다.s//<&/
이제 이 출력 스트림을 일반 줄로 구분된 텍스트 파일로 \n
처리 할 수 있습니다.
시험
printf '%s\n' the string \
"<9>more $(printf %050s|tr ' ' '<') string" \
and \<9\> "<more<string and <9> more<string" |
tr '<\n' '\n<' |
paste -sd\\n - - |
sed -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d' \
-e\} -e'x;y/\n</<\n/;s//<&/'
the
string
<9>more <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< string
and
<9>
<more<string and
<9> more<string
이제 문자열에 편집 내용을 적용하는 것이 목표라면 다음과 같이 설명할 수 있습니다.^<[0-9]+>(^(<[0-9]+>))*
글쎄, 이 시점에서는 아마도 두 번째 필터가 필요하지 않을 것입니다. 왜냐하면 그것이 sed
작은 스크립트의 끝 부분에 인쇄되기 전에 패턴 공간이 나타내는 것과 정확히 같기 때문입니다( \n
ewlines 등).
이전 예제의 수정된 버전을 다시 사용하여...
문자열>데이터
printf '%s\n' the string \
"<1>more $(printf %050s|tr ' ' '<') string" \
and \<2\> "<more<string and <3> more<string" |
tr '<\n' '\n<' |
paste -sd\\n - - |
sed -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d' \
-e\} -e'x;y/\n</<\n/;s//<&/' \
-e'/^<[2-9][0-9]*>/s/string/data/g'
the
string
<1>more <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< string
and
<2>
<more<data and
<3> more<data
답변2
프로그램이 터미널에 쓸 때 버퍼는 모든 개행 문자에서 플러시되지만 프로그램을 사용할 수 있습니다버퍼링 해제 (일부 배포판에서는 이 명령이 stdbuf라는 점에 유의하세요.)
이런 것을 시도해 보세요
unbuffer source | sed -e 's:xxxxx:yyyyy:g' | sink
답변3
최소한 GNU sed는 최종 개행 없이 입력을 처리할 수 있습니다(마지막 불완전한 행이 전달되면 최종 개행 없이 출력이 생성됩니다). Unix의 텍스트 파일은 정의상 새 줄로 끝나야 하며(비어 있지 않은 경우) sed는 텍스트 유틸리티이므로 텍스트가 아닌 입력에 대한 이러한 관대함은 보장되지 않습니다.
sed는 라인을 변환하도록 설계되었기 때문에 대부분의 구현에서는 변환을 적용하기 전, 특히 해당 입력 라인에 해당하는 출력을 생성하기 전에 전체 입력 라인을 메모리로 읽어올 것으로 예상합니다.
sed를 사용하여 이 입력을 편리하게 처리하려면 패턴과 일치하지 않거나 대체 텍스트로 생성되었지만 입력에서 자주 발생하는 문자를 선택 xxxxx
하세요 yyyyy
. sed를 호출한 후 개행 문자로 변환하거나 그 반대로 변환합니다.
source | tr ':\n' '\n:' | sed -e 's:foo:bar:g' | tr ':\n' '\n:' | sink
선택할 수 있는 좋은 캐릭터가 없다면 sed는 아마도 도움이 되지 않을 것이며 Ruby는 합리적인 선택입니다.
답변4
이 문제를 해결하기 위해 Ruby로 작은 스크립트를 구현했습니다. 사용 방법은 다음과 같습니다.
source | myscript.rb | sink
이것이 소스입니다
$stdout.sync # no outbound buffering by Ruby
buf=''
$stdin.each_char do |c|
if buf.length>0 || c=='<' # buffering starts when '<' received
buf << c # and continues until flushed
buf.gsub!(/(<\d+>)/,"\n\\1") if (c == '>') # regex transform matching buffer
unless (buf =~ /<\d*$/) # flush buffer when regex fails
STDOUT << buf
buf.replace '' # empty buffer stops buffering
end
else
STDOUT << c; # unbuffered output
end
$stdout.flush # no buffering, please!
end
루비 전문가가 이 문제를 개선할 수도 있지만, 여기에 문제를 해결한 빠른 "더러운 해킹"이 있습니다.
기본적으로 한 번에 한 문자씩 stdin을 읽고 일치하는 첫 번째 문자가 있는지 확인합니다. 즉, <
문자를 찾지 못하면 즉시 stdin에 씁니다. 일치하는 경우 버퍼에 쓴 다음 버퍼 내용을 유효한 구분 기호( <
뒤에 0개 이상의 숫자가 옴)가 있는 정규식으로 일치할 수 없는 경우를 제외하고는 버퍼를 플러시하고 버퍼링을 중지합니다. 버퍼링할 때 a를 얻으면 >
정규식을 통해 변환을 수행합니다.
고쳐 쓰다
위 스크립트는 작동하지만 개행 문자를 기다리는 경우 다운스트림 프로세스가 입력을 버퍼링할 수 있습니다. 이는 입력의 마지막 줄이 다운스트림 파이프라인에 걸릴 수 있음을 의미합니다. 아래 버전은 비차단 읽기를 사용하고 입력 블록 시 줄 바꿈을 삽입합니다.
STDOUT.sync # no outbound buffering by Ruby
buf=''
def read_from_stdin()
last=''
while true
begin
c = STDIN.read_nonblock(1) # read 1 character; don't block
rescue Errno::EWOULDBLOCK # exception if nothing to read
yield "\n" unless last=="\n" # send a newline if prior character wasn't
IO.select([STDIN]) # block (wait for input)
retry # go back to 'begin' again
end
yield last=c # remember and send read character
end
end
read_from_stdin do |c|
if buf.length>0 || c=='<' # buffering starts when '<' received
buf << c # and continues until flushed
buf.gsub!(/(<\d+>)/,"\n\\1") if (c == '>') # regex transform matching buffer
unless (buf =~ /<\d*$/) # flush buffer when regex fails
STDOUT << buf
buf.replace '' # empty buffer stops buffering
end
else
STDOUT << c; # unbuffered output
end
STDOUT.flush # no buffering, please!
end