업데이트: 이 동작은 Linux용 Windows 하위 시스템에서 관찰되었습니다. 여기서는 두 가지 문제를 다루고 있는 것 같습니다.
시스템 내의 일부 오류/경합 상태.이것은 틀렸습니다. 답변을 참조하세요.기본 버퍼 크기입니다
head
.(2)의 경우 @kusalanda가 언급했듯이
head
특정 지점까지 입력을 소비하는 기본 버퍼 크기가 있을 수 있습니다. ArchLinux 에서는i < 10
.tail
Linux용 Windows 하위 시스템의 경우에도 마찬가지입니다(즉, 일관되지 않은 출력 없음tail
).(1)과 관련하여 Linux용 Windows 하위 시스템 자체에 이러한 경쟁 조건을 일으키는 일부 내부 버그가 있을 수 있습니다. ArchLinux에서는 이 동작을 관찰하지 못했기 때문입니다.이것은 틀렸습니다. 답변을 참조하세요. "포인트 1"이 있지만 다릅니다.
bash
버전에서 다음 명령을 실행하려고합니다 4.4.19
.
{ for ((i = 0; i < 1000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
때로는 예상되는 결과가 표시됩니다.
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
999
$ ~
그러나 다음과 같은 경우가 종종 있습니다.
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
$ ~
나는 이것이 경쟁 조건이라고 생각합니다. 그러나 두 번째 명령 블록 시작 부분에 절전 모드를 추가하면 "경합 조건"이 계속 발생합니다.
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done; } | { sleep 10; head -n 1; echo ...; tail -n 1; }
0
...
$ ~
이것이 실제로 경쟁 조건입니까? 두 번째 코드 블록에서 전체 입력을 보려면 어떻게 해야 합니까? 10000
대신 을 사용하면 1000
이 문제가 발생하지 않습니다(단지 운이 좋은 상황일 수도 있음).
$ ~ { for ((i = 0; i < 10000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
9999
$ ~ { for ((i = 0; i < 10000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
9999
$ ~ { for ((i = 0; i < 10000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
9999
$ ~ { for ((i = 0; i < 10000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
9999
$ ~ { for ((i = 0; i < 10000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
9999
$ ~ { for ((i = 0; i < 10000; ++i)); do echo $i; done; } | { head -n 1; echo ...; tail -n 1; }
0
...
9999
$ ~
답변1
이것은 경쟁 조건이 아니며오류 없음WSL 또는 ArchLinux에서.
당신이 언급한 것처럼, 이는 head
"해야 하는" 것 이상을 읽으면 작업할 내용이 충분하지 않거나 전혀 남지 않기 때문입니다 tail
. 그러나 표준이나 다른 곳에서는 head
특정 수의 바이트만 읽어야 한다는 내용이 없습니다 . 전체 파일을 읽고 첫 번째 줄을 제외한 모든 내용을 삭제할 수도 있습니다.
가능한 모든 경우에 이 문제를 "수정"하려면 head
항상 입력을 바이트 단위로 읽어야 합니다(즉, 각 바이트에 대해 시스템 호출을 수행해야 합니다). 이는 매우 비효율적이며 99.999%의 경우 쓸모가 없는 경우 절대 불가능합니다.
이것을 피하고 싶다면 할 수 있습니다
1) 파이프 대신 임시 파일을 사용하십시오.
{ head -n 2 <tmpfile; tail -n 3 <tmpfile; }
예상대로 작동합니다.
2) 머리/꼬리 조합을 다른 것으로 다시 구현하십시오. 존재하다 awk
:
$ seq 10000 20000 | awk -vH=2 -vT=3 '{if(NR<=H)print; else a[i++%T]=$0}END{if((j=i-T)>0)print "..."; else j=0; while(j<i)print a[j++%T]}'
10000
10001
...
19998
19999
20000
답변2
참고: 정보 오류가 있을 경우 댓글로 남겨주시면 수정 또는 삭제할 수 있도록 하겠습니다.
@mosvy와 @MichaelHomer가 댓글에서 언급했듯이 이는 스케줄러가 파이프라인의 각 측면을 서로 다른 시간에 다르게 예약하기 때문입니다. 명확하게 하기 위해 다음 출력이 일관되지 않은 이유에 대해 답하고 있습니다.
{ for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; tail -n 1; }
출력은 다음과 같습니다.
0
...
그리고:
0
...
999
여기에는 두 가지 핵심 사항이 있습니다. 즉, 파이프 오른쪽의 입력이 항상 한꺼번에 사용 가능한 것은 아니기 때문에(포인트 1) head
서로 다른 양이 "소비"됩니다. 전체 입력을 사용할 수 있는 경우(왼쪽이 먼저 완료됨을 의미) head
@Kusalananda 및 @mosvy(포인트 2)에서 설명하는 구현으로 인해 전체 입력이 소비됩니다.
먼저 포인트 1을 보여드리겠습니다. 이를 시연하는 가장 쉬운 방법은 다음 tail
으로 바꾸는 것입니다 head
.
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
878
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
820
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
796
보시다시피 두 번째 출력 head
은 매번 다릅니다. 이는 왼쪽 입력이 항상 동시에 사용 가능한 것은 아니라는 것을 보여줍니다(점 1).
...
숫자가 이어지는 모든 경우에 대해 if의 출력을 얻습니다 . 아무것도 나오지 않는 이후의 경우에도 동일한 내용을 보게 될 것입니다. 이를 증명하기 위해 포인트 2를 보여드리겠습니다.999
tail
...
tail
첫 번째 항목에 대해서는 우리가 할 수 있는 일이 없지만,할 수 있는파일에 기록하여 더욱 안정적으로 만드세요.
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } >input
이 파일의 경우 파이프를 통해 읽습니다(아래 리디렉션 사례 참조).
$ ~ cat input | { head -n 1; echo ...; tail -n 1; }
0
...
사실 head
모든 것이 소모되고 아무것도 남지 않습니다 tail
. 따라서 포인트 2가 있습니다. 따라서 포인트 1과 2를 통해 일관성 없는 동작을 설명할 수 있습니다.
내 버전에서는
head
파이프를 통해 읽는 경우 한 번에 최소 1000개의 행이 소비되고 최소 1000개의 행을 사용할 수 있습니다(적을 경우 모두). 오른쪽이 시작되기 전에 왼쪽의 모든 것이 끝나면head
모든 것이 소모되어 아무것도 남지 않게 됩니다tail
. 단, 왼쪽 부분이 완성되지 않은 경우head
완성된 부분만 소모됩니다. 이는 무언가가 뒤에 남겨져tail
결과가 뒤에 남는다는 것을 의미합니다.
리디렉션
따라서 위의 예에서는 파이프를 사용하여 결과를 제공합니다. 그 이유는 리디렉션을 사용하면 다음과 같은 결과가 발생하기 때문입니다.
$ ~ { head -n 1; echo ...; tail -n 1; } <input
0
...
999
위의 설명과 다릅니다. 그 이유는 이 방법을 사용하면 head
1개의 행만 읽는 것처럼 보이기 때문입니다.
$ ~ { head -n 1; echo ...; head -n 1; } <input
0
...
1
이 질문을 설명하는 방법은 답변을 인용하는 것입니다.여기. 간단히 말해서:
- 파이프는 lseek()를 지원하지 않으므로 명령이 일부 데이터를 읽은 다음 되감을 수 없습니다. 그러나 > 또는 <로 리디렉션하면 일반적으로 lseek()를 지원하는 개체 파일이므로 명령이 다음 위치에서 탐색할 수 있습니다. 할 것이다.
즉, head
파일을 직접 찾을 수 있다면 모든 것을 소비할 필요가 없습니다. 필요한 내용만 읽으면 됩니다. 개행 문자를 찾으면 모든 것을 다시 되돌릴 수 있습니다. 개행 문자 뒤에 1바이트가 있는 파일을 사용하여 이를 증명할 수 있습니다.
$ ~ cat input
0123456789
1
$ ~ { head -n 1; head -c 1; } <input
0123456789
1$ ~
파이프를 사용하면 전체 입력이 소비되고 두 번째 입력에는 아무것도 남지 않습니다 head
.
$ ~ cat input | { head -n 1; head -c 1; }
0123456789
$ ~
참고로 프로세스 대체를 사용하면(내가 아는 한 검색할 수 없는 읽기가 발생함) 동일한 결과를 얻습니다.
$ ~ { head -n 1; head -c 1; } < <(cat input)
0123456789
$ ~