stdin의 마지막 행을 전체 stdin 앞에 추가


9

이 스크립트를 고려하십시오.

tmpfile=$(mktemp)

cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS

cat <(tail -1 "$tmpfile") "$tmpfile"

이것은 작동하고 출력합니다.

line 3
line 1
line 2
line 3

실제 파일이 아닌 입력 소스가 대신 stdin이라고 가정 해 봅시다.

cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS

명령을 어떻게 수정합니까?

cat <(tail -1 "$tmpfile") "$tmpfile"

그래서이 다른 상황에서 여전히 동일한 출력을 생성합니까?

참고 : 내가 여기있는 특정 Heredoc과 Heredoc 자체의 사용은 단지 예시 일뿐입니다. 모든 대답은 stdin을 통해 임의의 데이터를 수신 한다고 가정해야합니다 .


1
stdin은 항상 "실제 파일"입니다 (fifo / socket / etc도 파일입니다. 모든 파일을 검색 할 수있는 것은 아닙니다). 귀하의 질문에 대한 대답은 사소한 "임시 파일 사용"이거나 전체 파일을 메모리에로드하는 공포입니다. " 어디서나 저장하지 않고 스트림에서 오래된 데이터를 검색 할 수 있습니까?" 좋은 대답을 할 수 없습니다.
mosvy 2016 년

1
@mosvy 추가하고 싶다면 완벽하게 수용 가능한 답변입니다.
요나

2
@mosvy Jonah가 말했듯이 답변 상자에 답변을 게시해야합니다. 현재 웹 사이트를 읽는 것이 까다 롭다는 것을 알고 있지만 시력이 서서히 떨어지는 빨간색을 무시하고 하단 텍스트 영역을 사용하십시오.
wizzwizz4

답변:


7

시험:

awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'

입력으로 변수를 정의하십시오.

$ input="line 1
> line 2
> line 3"

우리의 명령을 실행하십시오 :

$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3

물론, 우리는 here-doc을 사용할 수 있습니다.

$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3

작동 원리

  • x=x $0 ORS

    이것은 각 입력 줄을 변수에 추가합니다 x.

    awk ORS에서 출력 레코드 구분 기호 입니다. 기본적으로 개행 문자입니다.

  • END{printf "%s", $0 ORS x}

    전체 파일을 읽은 후에는 마지막 행을 인쇄 한 $0다음 전체 파일의 내용을 인쇄합니다 x.

이것은 전체 입력을 메모리로 읽으므로 큰 ( 예 : 기가 바이트) 입력에는 적합하지 않습니다 .


고마워 존. OP의 명명 된 파일 예제와 비슷한 방식 으로이 작업을 수행 할 수 없습니까? 나는 stdin이 어떻게 든 복제되는 것을 상상하고 tee있었지만 일종의 방식 이 있지만 stdin과 파일은 동일한 stdin을 두 개의 다른 프로세스 대체로 파이핑하고 있습니다. 또는 그와 대략 동등한 것이 있습니까?
요나

5

stdin이 탐색 가능한 파일을 가리킬 경우 (bash 파일 (여기서는 모든 쉘이 아닌) 여기 임시 파일로 구현 된 문서)의 경우 꼬리를 얻은 다음 전체 내용을 읽기 전에 다시 검색 할 수 있습니다.

seek 연산자는 tcl / perl / python과 같은 스크립팅 언어 zsh또는 ksh93쉘 에서 사용할 수 있지만에서 사용할 수는 없습니다 bash. 그러나 당신이 사용해야 할 경우, 당신은 항상 그 고급 통역사에게 전화 할 수 bash있습니다 bash.

ksh93 -c 'tail -n1; cat <#((0))' <<...

또는

zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...

stdin이 파이프 나 소켓과 같이 찾을 수없는 파일을 가리킬 때 작동하지 않습니다. 그런 다음 유일한 옵션은 전체 입력을 읽고 (메모리 또는 임시 파일로) 저장하는 것입니다.

메모리에 저장하기위한 몇 가지 솔루션이 이미 제공되었습니다.

를 사용하여 임시 파일을 사용하면 다음과 zsh같이 할 수 있습니다.

seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'

리눅스와 경우 bash또는 zsh여기-문서에 사용하는 임시 파일, 실제로 출력을 저장하기 위해 여기-문서에 의해 생성 된 임시 파일을 사용할 수있는 쉘 또는 :

seq 10 | {
  chmod u+w /dev/fd/3 # only needed in bash5+
  cat > /dev/fd/3
  tail -n1 /dev/fd/3
  cat <&3
} 3<<EOF
EOF

4
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

이것을 사용하는 것으로 변환하는 문제 tailtail파일의 끝을 찾기 위해 전체 파일을 읽어야한다는 것입니다. 파이프 라인에서 사용하려면

  1. 문서의 전체 내용을에 제공하십시오 tail.
  2. 를 제공 다시cat.
  3. 그와 같은 순서로.

까다로운 비트는 문서의 내용을 복제하는 tee것이 아니라 tail중간 임시 파일을 사용하지 않고 나머지 문서가 출력되기 전에 출력을 얻는 것입니다.

사용 sed(또는 awk같은 John1024하는 일은 ) 메모리에 데이터를 저장하여 데이터와 주문 문제의 이중 파싱를 제거한다.

sed내가 제안 하는 해결책은

  1. 1{h;d;}, 첫 번째 줄을 보류 공간에 그대로 저장하고 다음 줄로 건너 뜁니다.
  2. H개행 문자가 포함 된 보류 공간에 서로 다른 행을 추가하십시오.
  3. ${G;p;}개행 문자가 포함 된 마지막 행에 보류 공간을 추가하고 결과 데이터를 인쇄하십시오.

이것은 sedPOSIX 표준이 홀드 공간이 최소 8192 바이트 (8 KiB)임을 보장한다는 경고와 함께 John1024의 솔루션을 문자 그대로 번역 한 것입니다. 그러나이 버퍼는 필요에 따라 동적으로 할당되고 확장되는 것이 좋습니다 . sedBSD가하고 있습니다 sed).


명명 된 파이프를 사용하도록 허용하는 경우 :

mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe

이것은 tee데이터를 다운 mypipe받는 동시에 에 전송하는 데 사용 됩니다 cat. cat유틸리티는 첫번째의 출력 읽습니다 tail(에서 읽고 mypipe, tee쓰고있는)을, 다음에서 직접 오는 문서의 복사본을 추가합니다 tee.

그러나 문서 에 파이프의 버퍼 크기보다 큰 파일너무 큰 경우 (이름이없는) 파이프가 비워지기를 기다리는 동안 tee쓰기 mypipe및 쓰기 cat가 차단 된다는 점에서 심각한 결함이 있습니다 . cat읽을 때까지 비워지지 않습니다 . 끝날 cat때까지 읽지 않았습니다 tail. 그리고 tail끝날 때까지 tee끝나지 않을 것 입니다. 이것은 전형적인 교착 상태입니다.

변형

tee >( tail -n 1 >mypipe ) | cat mypipe -

같은 문제가 있습니다.


2
sed입력이 하나의 라인을 (아마도이있는 경우 하나가 작동하지 않습니다 sed '1h;1!H;$!d;G'). 또한 일부 sed구현에는 패턴 및 보유 공간의 크기에 대한 제한이 낮습니다.
Stéphane Chazelas

명명 된 파이프 솔루션은 내가 찾던 것입니다. 한계는 부끄러운 일입니다. "티가 끝날 때까지 꼬리가 끝나지 않습니다"를 제외하고 설명을 이해했습니다. 왜 그런지 자세히 설명해 주시겠습니까?
요나

2

pee일반적으로 "moreutils"라는 이름으로 패키지 된 명령 줄 유틸리티 모음에 이름이 지정된 도구가 있습니다 (또는 홈 웹 사이트 에서 검색 가능 ).

시스템에서 사용할 수 있다면 다음과 같은 예가됩니다.

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

pee제공된 순서대로 실행되므로 명령 실행 순서 가 중요합니다.


1

시험:

cat <<EOS # | what goes here now? Nothing!
line 3
line 1
line 2
line 3
EOS

모든 것이 리터럴 데이터 ( "여기서는 문서")이고 원하는 출력과의 차이점은 사소한 것이기 때문에 출력과 일치하도록 리터럴 데이터를 바로 마사지하면됩니다.

이제 line 3어딘가에서 와서 다음과 같은 변수에 저장되었다고 가정하십시오 lastline.

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

here doc에서 변수를 대체하여 텍스트를 생성 할 수 있습니다. 뿐만 아니라 명령 대체를 사용하여 텍스트를 계산할 수 있습니다.

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

여러 줄을 보간 할 수 있습니다.

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

일반적으로 here doc 템플릿의 텍스트 처리는 피하십시오. 보간 코드를 사용하여 생성하십시오.


1
나는 이것이 농담인지 아닌지 정직하게 말할 수 없습니다. cat <<EOS...OP 의 in은 게시물을 명확하게하고 질문을 명확하게하기 위해 "임의의 파일 catting"에 대한 예입니다. 그것은 당신에게 정말로 분명하지 않았습니까, 아니면 질문을 문자 그대로 해석하는 것이 영리하다고 생각 했습니까?
요나

@Jonah이 질문은 "실제 파일이 아니라 입력 소스가 stdin :"이라고 분명히 말합니다. "임의 파일"에 관한 정보는 없습니다. 여기 문서에 관한 것입니다. here 문서는 임의적이지 않습니다. 프로그램에 대한 입력이 아니라 프로그래머가 선택하는 구문입니다.
Kaz

1
나는 문맥과 기존의 대답이 그 사실을 분명히했다고 생각합니다. 왜냐하면 당신의 해석이 정확하기 때문에 당신이나 말로 대답 한 다른 포스터 중 어느 것도 복사하여 붙여 넣을 수 없다는 것을 깨달았다 고 가정해야했습니다 코드 라인. 그럼에도 불구하고, 질문을 명확하게하기 위해 편집 할 것입니다.
요나

1
Kaz, 답장을 보내 주셔서 감사합니다. 그러나 편집 한 경우에도 질문의 의도가 없습니다. 넌받는 파이프를 통해 임의의 여러 입력 . 당신은 그것이 무엇인지 전혀 모른다. 당신의 임무는 마지막 입력 라인을 출력 한 다음 전체 입력을 출력하는 것입니다.
요나

1
Kaz, 입력은 단지 예일뿐입니다. 나 자신을 포함한 대부분의 사람들은 추상적 질문보다는 실제 입력과 예상 출력의 예를 갖는 것이 도움이된다는 것을 알게되었다. 당신은 이것으로 혼란 스러웠던 유일한 사람입니다.
요나

0

주문에 신경 쓰지 않으면. 그런 다음 작동 cat lines | tee >(tail -1)합니다. 다른 사람들이 말했듯이. 요청한 순서대로 파일을 두 번 읽거나 전체 파일을 버퍼링해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.